Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
refactor: collectionAccessTokens -> accessKeys (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
B&R committed Oct 29, 2023
1 parent fec4386 commit acf4881
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 32 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ Maturity

Star a repo, subscribe for releases to get informed.

Security/Compliance demo
------------------------

### Are my backups created in specific time?

Every **Backup Collection** has HTTP health check endpoint you can monitor and trigger alerts in case when expected backup was not submitted or is invalid.

### Attacker got my Kubernetes cluster and wants to overwrite remote backups

- Good practice is to **limit how often versions can be submitted**. Attacker would need to be very patient to overwrite your past backups with malicious ones.

### Attacker got my Backup Repository credentials from target environment

- **Access Keys** feature allows to generate additional pair of username & password for same user, but with fewer privileges
- Use *Backup Maker Operator* which injects JWT credentials on-the-fly just before the backup is made. Those credentials are restricted to upload to single collection at a time
- You may specify ranges of IP addresses from which backup could be submitted (if the server is reachable from the internet)

### Attacker wants to upload a terabyte file to generate cloud costs or exhaust disk space

Backup Repository operates on disk quotas. Every incoming byte stream is calculated on the fly and cancelled, when the limit is exhausted.

### Storage of my Backup Repository server leaked!

End-To-End backup encryption makes your backup unreadable for people not having your GPG private key.

Running
-------

Expand Down Expand Up @@ -118,6 +143,8 @@ Security

- For authentication JSON Web Token was used
- Tokens are long-term due to usage nature
- Support for scoped JSON Web Tokens (a single requested token can have restricted permissions to perform less than defined in User profile)
- User can have multiple username & passwords pairs, each one with additional restrictions (e.g. username `mycluster$collection1` -> only uploads to collection1, username `mycluster$collection2` -> only uploads to collection2)
- All JWT's can be revoked anytime. There is a list of generated tokens stored in configuration (only sha256 shortcuts)
- Passwords are encoded with `argon2di` (winner of the 2015 Password Hashing Competition, recommended by OWASP)
- All objects are managed by RBAC (Role Based Access Control) and ACL (Access Control Lists)
Expand Down
20 changes: 13 additions & 7 deletions docs/api/users/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ curl -s -X POST -d '{"username":"some-user","password":"test", "operationsScope"

_Notice `"operationsScope": {"elements": [{"type": "collection", "name": "iwa-ait", "roles": ["collectionManager"]}]}` in the request body._

### Feature: Access Tokens
### Feature: Access Keys

Second way to restrict user session is by creating **Access Keys** that are separate passwords associated with same User account, but with additional restrictions.

Expand All @@ -69,21 +69,27 @@ spec:
passwordFromRef:
name: backup-repository-passwords
entry: admin
collectionAccessKeys:
accessKeys:
#
# login: some-user$uploader
# This entry creates a "sub-user" some-user$uploader which is restricted to only upload to collection 'iwa-ait'
# This "sub-user" uses a different password. You may create as many "sub-users" (access keys) as you wish - per collection, per function.
#
# login: some-user$iwa
# password: test
#
- name: uploader
collections: ["iwa-ait"]
roles: ["backupUploader"]
- name: iwa
# password: ""
passwordFromRef:
name: backup-repository-passwords
entry: admin_access_key_1
objects:
- name: iwa-ait
type: collection
roles: ["backupUploader"]
# (...)
```

_Notice: Roles specified in `collectionAccessKeys` cannot be higher than specified in User's profile or in collection ACL._
_Notice: Roles specified in `accessKeys` cannot be higher than specified in User's profile or in collection ACL._

**Example JSON payload:**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ spec:
passwordFromRef:
name: backup-repository-passwords
entry: admin
collectionAccessKeys:
accessKeys:
#
# login: some-user$uploader
# password: test
#
- name: uploader
collections: ["iwa-ait"]
roles: ["backupUploader"]
# password: ""
passwordFromRef:
name: backup-repository-passwords
entry: admin_access_key_1
objects:
- name: iwa-ait
type: collection
roles: ["backupUploader"]
roles:
- collectionManager
6 changes: 4 additions & 2 deletions docs/examples/user.second-actor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ spec:
# password: test
#
- name: uploader
collections: ["iwa-ait"]
roles: ["backupUploader"]
# password: ""
passwordFromRef:
name: backup-repository-passwords
entry: admin_access_key_1
objects:
- name: iwa-ait
type: collection
roles: ["backupUploader"]
roles:
- collectionManager
21 changes: 13 additions & 8 deletions helm/backup-repository-server/templates/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,22 +155,27 @@ spec:
type: string
entry:
type: string
collectionAccessTokens:
accessKeys:
type: array
items:
type: object
properties:
name:
type: string
collections:
objects:
type: array
items:
type: string
roles:
type: array
items:
type: string
enum: ["collectionManager", "backupUploader", "backupDownloader"]
type: object
required: ["name", "type"]
properties:
name:
type: string
type:
type: string
roles:
type: array
items:
type: string
password:
type: string
passwordFromRef:
Expand Down
20 changes: 10 additions & 10 deletions pkg/users/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import (
"github.com/sirupsen/logrus"
)

type CollectionAccessKey struct {
type AccessKey struct {
Name string `json:"name"`
Password string `json:"password"` // master password to this account, allows access limited by
PasswordFromRef security.PasswordFromSecretRef `json:"passwordFromRef"`
Objects []security.AccessControlObject `json:"objects"`
}

type Spec struct {
Id string `json:"id"`
Email string `json:"email"`
Roles security.Roles `json:"roles"`
Password string `json:"password"` // master password to this account, allows access limited by
PasswordFromRef security.PasswordFromSecretRef `json:"passwordFromRef"`
CollectionAccessKeys []*CollectionAccessKey `json:"collectionAccessKeys"`
Id string `json:"id"`
Email string `json:"email"`
Roles security.Roles `json:"roles"`
Password string `json:"password"` // master password to this account, allows access limited by
PasswordFromRef security.PasswordFromSecretRef `json:"passwordFromRef"`
AccessKeys []*AccessKey `json:"accessKeys"`
}

type User struct {
Expand Down Expand Up @@ -77,10 +77,10 @@ type SessionAwareUser struct {
*User

// Dynamic property: Copy of .spec.CollectionAccessKeys with password field filled up
accessKeysFromSecret []*CollectionAccessKey
accessKeysFromSecret []*AccessKey

// Dynamic property: Access key used in current session
currentAccessKey *CollectionAccessKey
currentAccessKey *AccessKey

// Dynamic property: Read from JWT token - operations scope, limited per session/token
sessionScope *security.SessionLimitedOperationsScope
Expand Down Expand Up @@ -114,7 +114,7 @@ func (sau *SessionAwareUser) GetAccessKeyRolesInContextOf(subject security.Subje
}

// IsPasswordValid is checking if User supplied password matches User's main password,
// or CollectionAccessKey password - depending on accessKeyName parameter
// or AccessKey password - depending on accessKeyName parameter
func (sau *SessionAwareUser) IsPasswordValid(password string, accessKeyName string) bool {
if accessKeyName != "" {
for _, accessKey := range sau.accessKeysFromSecret {
Expand Down
4 changes: 2 additions & 2 deletions pkg/users/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func (a *Service) LookupSessionUser(identity security.UserIdentity, scope *secur

func (a *Service) fillUpAccessToken(saUser *SessionAwareUser, currentlyUsedAccessKeyName string) error {
// access keys
accessKeys := make([]*CollectionAccessKey, 0)
accessKeys := make([]*AccessKey, 0)
saUser.currentAccessKey = nil
for _, accessKey := range saUser.Spec.CollectionAccessKeys {
for _, accessKey := range saUser.Spec.AccessKeys {
ak := *accessKey
if ak.Password == "" && ak.PasswordFromRef.Name != "" {
hashSetter := func(password string) {
Expand Down

0 comments on commit acf4881

Please sign in to comment.