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

Commit

Permalink
Merge pull request #297 from riotkit-org/feat-declarative-access-keys
Browse files Browse the repository at this point in the history
Declarative access keys
  • Loading branch information
blackandred committed Nov 1, 2023
2 parents 09daa6d + f5bc845 commit d4b613e
Show file tree
Hide file tree
Showing 78 changed files with 1,574 additions and 1,003 deletions.
25 changes: 0 additions & 25 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,28 +105,3 @@ jobs:

app_version: "${{github.ref_name}}"
chart_version: "${{github.ref_name}}"

release-archlinux-pkg:
runs-on: ubuntu-latest
needs: ["build"]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/download-artifact@v2
with:
name: binary
path: .build/

- name: Create Arch Linux package
uses: 2m/arch-pkgbuild-builder@v1.18
with:
target: 'pkgbuild'
pkgname: './'

- name: Release Arch Linux package
uses: softprops/action-gh-release@v1
with:
files: "backup-repository-*.pkg.tar.zst"
13 changes: 1 addition & 12 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,6 @@ jobs:
github-token: ${{ secrets.github_token }}
path-to-lcov: coverage.lcov

# ===================
# Arch Linux package
# ===================

- name: Create Arch Linux package to check if PKGBUILD works
uses: 2m/arch-pkgbuild-builder@v1.18
if: "!contains(github.event.head_commit.message, '!pkg skip')"
with:
target: 'pkgbuild'
pkgname: './'

# =======
# Docker
# =======
Expand Down Expand Up @@ -82,4 +71,4 @@ jobs:
uses: riotkit-org/.github/.github/workflows/python.release.yaml@main
with:
pythonVersion: 3.11
testCmd: "make k3d integration-test"
testCmd: "make k3d skaffold-deploy integration-test"
18 changes: 17 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ _pytest: ## Shortcut for E2E tests without setting up the environment
pipenv run pytest -s

_prepare-env:
kubectl apply -f "helm/backup-repository-server/templates/crd.yaml"
kubectl apply -f "docs/examples/" -n backups

run:
Expand All @@ -41,7 +42,7 @@ run_with_local_config_storage:
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE; \
export AWS_SECRET_ACCESS_KEY=wJaFuCKtnFEMI/CApItaliSM/bPxRfiCYEXAMPLEKEY; \
\
backup-repository \
./.build/backup-repository \
--db-password=postgres \
--db-user=postgres \
--db-password=postgres \
Expand All @@ -54,6 +55,21 @@ run_with_local_config_storage:
--config-local-path=$$(pwd)/docs/examples-filesystem/\
--storage-url="s3://mybucket?endpoint=localhost:9000&disableSSL=true&s3ForcePathStyle=true&region=eu-central-1"

postgres: ## Runs local PostgreSQL for running project as local binary
docker run -p 5432:5432 -d --rm --name postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres:15.3-alpine

minio: ## Runs local Min.io for running project as local binary
docker run -d \
--name br_minio \
-p 9000:9000 \
-p 9001:9001 \
-v $$(pwd)/.build/minio:/data \
-e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
-e "MINIO_ROOT_PASSWORD=wJaFuCKtnFEMI/CApItaliSM/bPxRfiCYEXAMPLEKEY" \
--entrypoint /bin/sh \
quay.io/minio/minio:RELEASE.2022-02-16T00-35-27Z -c 'mkdir -p /data/mybucket && minio server /data --console-address 0.0.0.0:9001'


lint:
export GO111MODULE=on; \
golangci-lint run \
Expand Down
29 changes: 0 additions & 29 deletions PKGBUILD

This file was deleted.

37 changes: 33 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Backup Repository
[![Test](https://github.com/riotkit-org/backup-repository/actions/workflows/test.yaml/badge.svg)](https://github.com/riotkit-org/backup-repository/actions/workflows/test.yaml)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/riotkit-org)](https://artifacthub.io/packages/search?repo=riotkit-org)

Cloud-native, zero-knowledge, multi-tenant, security-first backup storage with minimal footprint.
Cloud-native, zero-knowledge, multi-tenant, compliance-strict, security-first backup storage with minimal footprint.

_TLDR; Primitive backup storage for E2E GPG-encrypted files, with multi-user, quotas, versioning, using a object storage (S3/Min.io/GCS etc.) and deployed on Kubernetes or standalone. No fancy stuff included, lightweight and stable as much as possible is the project target._

Expand All @@ -15,6 +15,8 @@ _TLDR; Primitive backup storage for E2E GPG-encrypted files, with multi-user, qu
- Configuration via GitOps (Configuration as a Code)
- Multi-tenancy with configurable Quotas
- Multiple cloud providers as a backend storage (all supported by [GO Cloud](https://gocloud.dev/howto/blob/#services))
- (Security) JWT tokens with restricted scope (login endpoint can apply additional restrictions to user session)
- (Security) Extra pairs of username & passwords with different restrictions applied - for single user

**Notice:**
- Project is more focusing on security than on performance
Expand All @@ -30,7 +32,7 @@ _TLDR; Primitive backup storage for E2E GPG-encrypted files, with multi-user, qu
- Kubernetes (if wanting to use Kubernetes)
- PostgreSQL
- About 128Mb ram for small scale usage (**Note**: _We use Argon2di and performing file uploads + calculations on buffers_)
- Storage provider (S3, GCS, Min.io, local filesystem, and others supported by https://gocloud.dev/howto/blob/#services)
- Storage provider (S3, GCS, Min.io, local filesystem, or others supported by https://gocloud.dev/howto/blob/#services)

**Support:**
- Any Kubernetes 1.20+
Expand Down Expand Up @@ -68,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 @@ -116,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 Expand Up @@ -177,7 +206,7 @@ Domain objects should implement a logic that checks given `Actor` if it can act
```go
func (u User) CanViewMyProfile(actor User) bool {
// rbac
if actor.Spec.Roles.HasRole(security.RoleUserManager) {
if actor.GetRoles().HasRole(security.RoleUserManager) {
return true
}

Expand All @@ -190,7 +219,7 @@ func (u User) CanViewMyProfile(actor User) bool {

```go
func (c Collection) CanUploadToMe(user *users.User) bool {
if user.Spec.Roles.HasRole(security.RoleBackupUploader) {
if user.GetRoles().HasRole(security.RoleBackupUploader) {
return true
}

Expand Down
71 changes: 71 additions & 0 deletions docs/api/users/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ curl -s -X POST -d '{"username":"admin","password":"admin"}' -H 'Content-Type: a
{
"data": {
"expire": "2032-02-25T00:32:56+01:00",
"msg": "Use this sessionId to revoke this token anytime",
"sessionId": "2d0aa5db61c02ea9a9d7fe6d768021aca98fff1fe75fe214a65ccd6926bb8b77",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5NjEyNzgzNzYsImxvZ2luIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0NTkxODM3Nn0.my0WXXMxKCetkomtzRDNIKLWUm4cJ2gxyUCkuAHT6M4"
},
Expand All @@ -34,6 +35,76 @@ curl -s -X POST -d '{"username":"admin","password":"admin"}' -H 'Content-Type: a

The `.data.token` from response is a session secret key, use it to authenticate next API requests using `Authorization: Bearer token-here` header.

### Feature: Generating a JWT token with limited permissions

User can create a restricted session with `/api/stable/auth/login` endpoint, by using extra parameter `operationsScope`.

`operationsScope` extra parameter is a working like an allowlist/whitelist - optional, when specified, then User permissions
are restricted to roles specified there.

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

**Example:**

```bash
curl -s -X POST -d '{"username":"some-user","password":"test", "operationsScope": {"elements": [{"type": "collection", "name": "iwa-ait", "roles": ["collectionManager"]}]}}' -H 'Content-Type: application/json' 'http://localhost:8080/api/stable/auth/login'
```

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

### 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.

**Example configuration:**

```yaml
---
apiVersion: backups.riotkit.org/v1alpha1
kind: BackupUser
metadata:
name: some-user
spec:
# (...)
passwordFromRef:
name: backup-repository-passwords
entry: admin
accessKeys:
#
# 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: iwa
# password: ""
passwordFromRef:
name: backup-repository-passwords
entry: admin_access_key_1
objects:
- name: iwa-ait
type: collection
roles: ["backupUploader"]
# (...)
```

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

**Example JSON payload:**

```json
{"username":"some-user$uploader","password":"test"}
```

When using login endpoint you need to specify **Access Key** name after `$` in username field, and use password specified for that Access Key - it's not a regular password associated with the account.

**Example:**

```bash
curl -s -X POST -d '{"username":"some-user$uploader","password":"test", "operationsScope": {"elements": []}}' -H 'Content-Type: application/json' 'http://localhost:8080/api/stable/auth/login'
```

## GET `/api/stable/auth/user/some-user`

Displays information about user **of given username**.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ spec:
entry: iwa-ait

accessControl:
- userName: admin
- name: admin
type: user
roles:
- collectionManager
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,19 @@ spec:
passwordFromRef:
name: backup-repository-passwords
entry: admin
accessKeys:
#
# login: some-user$uploader
# password: test
#
- name: uploader
# password: ""
passwordFromRef:
name: backup-repository-passwords
entry: admin_access_key_1
objects:
- name: iwa-ait
type: collection
roles: ["backupUploader"]
roles:
- collectionManager
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ data:
# admin: admin
# to generate: `backup-repository --encode-password "admin"
admin: "JGFyZ29uMmlkJHY9MTkkbT02NTUzNix0PTEscD00JHpuVy9IT2Y4Q3RkdStvNSttYlR2REE9PSRaZlVpRGl2QWV2T2RZNndKYWJBb0FQdmM1a1hsemxDNkg2OFY2dGVmNUY0PQ=="

# to generate: `backup-repository --encode-password "admin"`
admin_access_key_1: "JGFyZ29uMmlkJHY9MTkkbT02NTUzNix0PTEscD00JERzUzlPTzFOc0JVREhvR1RmQ01wemc9PSRrcXh4bFliS3A4Um81MXZEb0FQaUdBeFhkNTgrY1ZzdERyZ3p3NG16bjFVPQ=="
4 changes: 3 additions & 1 deletion docs/examples/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ metadata:
type: Opaque
data:
# admin: admin
# to generate: `backup-repository --encode-password "admin"
# to generate: `backup-repository --encode-password "admin"`
admin: "JGFyZ29uMmlkJHY9MTkkbT02NTUzNix0PTEscD00JHpuVy9IT2Y4Q3RkdStvNSttYlR2REE9PSRaZlVpRGl2QWV2T2RZNndKYWJBb0FQdmM1a1hsemxDNkg2OFY2dGVmNUY0PQ=="
# to generate: `backup-repository --encode-password "admin"`
admin_access_key_1: "JGFyZ29uMmlkJHY9MTkkbT02NTUzNix0PTEscD00JERzUzlPTzFOc0JVREhvR1RmQ01wemc9PSRrcXh4bFliS3A4Um81MXZEb0FQaUdBeFhkNTgrY1ZzdERyZ3p3NG16bjFVPQ=="

---
apiVersion: v1
Expand Down
14 changes: 14 additions & 0 deletions docs/examples/user.second-actor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,19 @@ spec:
passwordFromRef:
name: backup-repository-passwords
entry: admin
accessKeys:
#
# login: some-user$uploader
# password: test
#
- name: uploader
# password: ""
passwordFromRef:
name: backup-repository-passwords
entry: admin_access_key_1
objects:
- name: iwa-ait
type: collection
roles: ["backupUploader"]
roles:
- collectionManager
1 change: 1 addition & 0 deletions env.mk
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ skaffold-deploy: prepare-tools ## Deploys app with dependencies using Skaffold
skaffold deploy -p app --tag e2e --assume-yes=true --default-repo ${ENV_CLUSTER_NAME}-registry:5000

export KUBECONFIG=~/.k3d/kubeconfig-${ENV_CLUSTER_NAME}.yaml
killall kubectl || true
kubectl port-forward svc/${ENV_APP_SVC} -n ${ENV_NS} ${ENV_PORT_FORWARD} &

dev: ## Runs the development environment in Kubernetes
Expand Down
Loading

0 comments on commit d4b613e

Please sign in to comment.