feat: deploy Authentik with age-encrypted secrets

This commit is contained in:
Philip Henning 2026-08-21 14:04:26 +02:00
parent 9f8f619f3e
commit e966ec09ae
9 changed files with 240 additions and 260 deletions

3
.gitignore vendored
View file

@ -1,9 +1,12 @@
# Project
.env
lego.env
.komodo/
data/*
data/nginx/*
!data
!data/authentik
!data/authentik/custom-templates
!data/nginx
!data/nginx/default.conf.template

341
README.md
View file

@ -1,269 +1,120 @@
# `sso.s1q.dev` - Base23 SSO for all services
# Authentik deployment
[Authentik](https://goauthentik.io/) based SSO for our sevices.
This repository owns the production Authentik Compose deployment and its
Komodo Stack declaration. The central `komodo-bootstrap` repository owns the
child Resource Sync and the ordered deployment Procedure.
## Table of Contents
## Layout
- [`sso.s1q.dev` - Base23 SSO for all services](#ssos1qdev---base23-sso-for-all-services)
- [Table of Contents](#table-of-contents)
- [Prerequisites](#prerequisites)
- [Tailscale](#tailscale)
- [CrowdSec](#crowdsec)
- [Setup CrowdSec Repo](#setup-crowdsec-repo)
- [Install CrowdSec](#install-crowdsec)
- [Configure CrowdSec](#configure-crowdsec)
- [Installation](#installation)
- [Clone \& configure initially](#clone--configure-initially)
- [Fist run](#fist-run)
- [Upgrade](#upgrade)
- [Test](#test)
- [Prod](#prod)
- [Disaster recovery / restore](#disaster-recovery--restore)
- [Rebuild containers locally](#rebuild-containers-locally)
## Prerequisites
- dokploy
### Tailscale
```shell
printf "Enter preauthkey for Tailscale: " \
&& read -rs TAILSCALE_PREAUTHKEY \
&& curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null \
&& curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list \
&& apt-get update \
&& apt-get install tailscale \
&& tailscale up --login-server https://vpn.s1q.dev --authkey ${TAILSCALE_PREAUTHKEY} \
&& sleep 2 \
&& tailscale status \
&& unset TAILSCALE_PREAUTHKEY
```text
.
├── docker-compose.yml
├── docker-compose.override.yml
├── komodo.toml
├── env/
│ └── common.env
├── secrets/
│ └── prod.env.age
├── scripts/
│ ├── compose.sh
│ └── init.sh
└── tests/
└── compose-secrets.sh
```
### CrowdSec
`env/common.env` contains tracked, non-secret configuration and explicit image
versions. `secrets/prod.env.age` contains only secrets and is encrypted to the
`sbx0docker01` SSH host key and the personal `agenix-phg` SSH key.
#### Setup CrowdSec Repo
The `authentik-prod` Stack follows protected `main`. Its direct webhook is
disabled. A Forgejo webhook calls the central `authentik-deploy` Procedure,
which runs the child Sync before `DeployStackIfChanged`.
```shell
apt update \
&& apt upgrade -y \
&& apt install -y debian-archive-keyring \
&& apt install -y curl gnupg apt-transport-https \
&& mkdir -p /etc/apt/keyrings/ \
&& curl -fsSL https://packagecloud.io/crowdsec/crowdsec/gpgkey | gpg --dearmor > /etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg \
&& cat << EOF > /etc/apt/sources.list.d/crowdsec_crowdsec.list \
&& apt update
deb [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
deb-src [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
EOF
Komodo passes `docker-compose.yml` first and `docker-compose.override.yml`
second. The base and deployment-specific override remain separate by design.
## Secrets
The encrypted production bundle contains:
```text
PG_PASS
AUTHENTIK_SECRET_KEY
AUTHENTIK_EMAIL__PASSWORD
GEOIPUPDATE_LICENSE_KEY
```
#### Install CrowdSec
To edit it locally, decrypt to a mode-`0600` temporary file, edit it, then
encrypt it to both recipients:
Install CrowdSec:
```bash
tmp=$(mktemp)
chmod 0600 "$tmp"
trap 'rm -f -- "$tmp"' EXIT HUP INT TERM
```shell
printf "Enter CrowdSec context: " \
&& read -rs CROWDSEC_CONTEXT \
&& apt install -y crowdsec crowdsec-firewall-bouncer-iptables \
&& cscli completion bash | tee /etc/bash_completion.d/cscli \
&& source ~/.bashrc \
&& cscli console enroll -e context ${CROWDSEC_CONTEXT} \
&& unset CROWDSEC_CONTEXT
age --decrypt \
--identity ~/.ssh/identities/agenix-phg \
--output "$tmp" \
secrets/prod.env.age
${EDITOR:-vi} "$tmp"
age --encrypt \
--recipient 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4umkUY5CG5aNJBhUjVcU8TWbh453N0pGHZhYDTOGQa' \
--recipient 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIr4Dji0wWzwSXyqbxdGa8LdWkhP+0T7kDKrLbyCoyos' \
--output secrets/prod.env.age.new \
"$tmp"
mv secrets/prod.env.age.new secrets/prod.env.age
```
Restart CordSec Service, after accepting the enrollment on the [CrowdSec Console](https://app.crowdsec.net/):
Do not add a plaintext secret file to Git. The Stack config tracks the
encrypted bundle so a rotation counts as a deployment change, but does not
track the temporary `.komodo/prod.env` file.
```shell
systemctl restart crowdsec; systemctl status crowdsec.service
```
The decrypted values are Compose secret sources rather than service
environment values. Containers receive only their declared files under
`/run/secrets`; Authentik reads them through `file://` configuration values,
while PostgreSQL and GeoIP Update use their `_FILE` variables. Consequently,
`docker compose config` renders secret source names and file paths without
persisting the values in Komodo's merged configuration.
#### Configure CrowdSec
## Compose wrapper
Whitelist Tailscale IPs:
Komodo wraps `config`, `pull`, `up`, and `run` with
the NixOS-provided `with-age-env` command. The wrapper:
```shell
cat << EOF > /etc/crowdsec/parsers/s02-enrich/01-s1q-dev-tailscale.yaml \
&& systemctl restart crowdsec; journalctl -xef -u crowdsec.service
name: s1q-dev/tailscale ## Must be unqiue
description: "Whitelist events from Tailscale Subnet"
whitelist:
reason: "Tailscale clients"
cidr:
- "100.64.0.0/10"
EOF
```
- obtains an exclusive per-bundle lock;
- removes stale plaintext and temporary files;
- decrypts through the NixOS-provided systemd `age-identity` credential;
- creates plaintext with mode `0600`; and
- removes plaintext on success, failure, or a handled signal.
Whitelist my current Public IPs:
The plaintext target and matching `.lock` and `.tmp.*` paths are exclusively
owned by the wrapper. A `SIGKILL` or power loss can leave plaintext behind;
the next invocation removes it before decrypting again.
```shell
mkdir -p /etc/crowdsec/postoverflows/s01-whitelist/ \
&& cat << EOF > /etc/crowdsec/postoverflows/s01-whitelist/01-s1q-dev-public-ips.yaml \
&& crowdsec -t && systemctl restart crowdsec; systemctl status crowdsec.service
name: s1q-dev/public-ips ## Must be unqiue
description: "Whitelist events from s1q-dev public IPs"
whitelist:
reason: "s1q-dev Public IPs"
expression:
- evt.Overflow.Alert.Source.IP in LookupHost("r3w.de")
EOF
```
NixOS owns the wrapper, its runtime dependencies, the host-key systemd
credential, Periphery's Podman permissions, and the external
`dokploy-network`. Workload containers do not receive the age identity, but
commands launched by Periphery can access it. Revisit that trust boundary if
deployment authority is ever granted to another operator.
Add Authentik integration:
Run `bash tests/compose-secrets.sh` to verify that sentinel secret values do
not appear in the rendered Compose model and that every service receives only
its intended secret files.
```shell
cscli collections install firix/authentik \
&& cat << EOF > /etc/crowdsec/acquis.d/authentik.yaml \
&& crowdsec -t && systemctl restart crowdsec
---
source: docker
container_name_regexp:
- sso-s1q-dev-de-server-*
- sso-s1q-dev-de-worker-*
labels:
type: authentik
EOF
```
## Optional test environment
Enable increasing ban time:
Do not create a test Stack or dummy bundle until there is a real long-lived
`test` branch. When it is needed:
```shell
sed -i -e 's/^#duration_expr/duration_expr/g' /etc/crowdsec/profiles.yaml \
&& crowdsec -t && systemctl restart crowdsec
```
1. add `secrets/test.env.age`, encrypted to the test host and personal keys;
2. keep both Stack declarations in `komodo.toml` on `main`;
3. point `authentik-test` at branch `test` and its own encrypted/decrypted
paths; and
4. add `authentik-test` to the Procedure's deployment stage in the central
bootstrap repository.
Setup notifications:
## Installation
### Clone & configure initially
1. [Create a Storage Box sub account](https://confluence.base23.de/pages/viewpage.action?pageId=27820074).
2. Enter the username to `env.template`.
3. Run the initial configuration script:
```shell
cd /root/apps \
&& git clone ssh://git@git.base23.de:222/base23/sso.s1q.dev.git \
&& cd sso.s1q.dev \
&& ./scripts/init.sh
```
4. Use the generated SSH key and copy it to the Hetzner Storage box for backups:
```shell
TARGET_DOMAIN=cloud.backup.base23.de \
TARGET_KEY_TYPES="ecdsa-sha2-nistp521,ed25519,ed25519-sk,rsa,dsa,ecdsa,ecdsa-sk" \
TARGET_IPV4=$(dig +short "${TARGET_DOMAIN}" A | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') \
TARGET_IPV6=$(dig +short "${TARGET_DOMAIN}" AAAA | grep -E '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$') \
&& cat ./data/restic/ssh/id_ed25519.pub | ssh -p23 u291924-sub4@${TARGET_DOMAIN} install-ssh-key \
&& ssh-keyscan -p 23 -t ${TARGET_KEY_TYPES} ${TARGET_DOMAIN} > ./data/restic/ssh/known_hosts \
&& ssh-keyscan -p 23 -t ${TARGET_KEY_TYPES} ${TARGET_IPV4} >> ./data/restic/ssh/known_hosts \
&& ssh-keyscan -p 23 -t ${TARGET_KEY_TYPES} ${TARGET_IPV6} >> ./data/restic/ssh/known_hosts \
&& ssh-keyscan -p 22 -t ${TARGET_KEY_TYPES} ${TARGET_DOMAIN} >> ./data/restic/ssh/known_hosts \
&& ssh-keyscan -p 22 -t ${TARGET_KEY_TYPES} ${TARGET_IPV4} >> ./data/restic/ssh/known_hosts \
&& ssh-keyscan -p 22 -t ${TARGET_KEY_TYPES} ${TARGET_IPV6} >> ./data/restic/ssh/known_hosts
```
### Fist run
```shell
./scripts/compose.sh build --no-cache \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg SRC_REV=$(git rev-parse --short HEAD) \
&& ./scripts/compose.sh up -d; ./scripts/compose.sh logs -f
```
## Upgrade
### Test
This is intended for testing the upgrades before rollout on prod.
1. Check if the backups are up to date: `./scripts/compose.sh run --rm restore-cli /usr/local/bin/restic snapshots`
2. Create a new branch `git checkout -b <version>`.
3. Download the the docker compose for the version you want to update:
`curl -fsSL -o docker-compose.yml https://goauthentik.io/version/<version>/docker-compose.yml`
4. Update `AUTHENTIK_TAG` to the desired tag in `env.test.template`.
5. Check the upstream `docker-compose.yml` file against ours for changes made in the configuration. Check the Postgresql and Redis docker tags. Minor revisions of Postgresql *should be fine*, check the changelogs for any issues and if none are present raise to the latest Minor version (e.g. 16.6 -> 16.9). Redis should be less problematic for upgrades, check nonetheless.
6. Run `diff --color='auto' env.test.template .env` to display the diff between
`env.test.template` and `.env`.
7. Port the changes made to `.env`.
8. `./scripts/compose.sh pull`
9. `./scripts/compose.sh down`
10. `./scripts/compose.sh up -d; ./scripts/compose.sh logs -f`
9. Check the logs for any issues during during startup. Check if https://sso.test.base23.de is available and test the sso login (e.g https://whoami.test.base23.de)
10. Apply changes for test to prod files (`docker-compose.<stage>.yml`, `env.<stage>.template`), commit & push changes to the Repo in a new branch and create a merge request in preparation for the prod upgrade
### Prod
It is expected that the Upgrade has already been performed and tested on https://sso.test.base23.de, and the changes have been merged into main
1. Check if the backups are up to date: `./scripts/compose.sh run --rm restore-cli /usr/local/bin/restic snapshots`
2. Create a new branch `git checkout -b <version>`.
3. Download the the docker compose for the version you want to update:
`curl -fsSL -o docker-compose.yml https://goauthentik.io/version/<version>/docker-compose.yml`
4. Update `AUTHENTIK_TAG` to the desired tag in `env.prod.template`.
5. Commit & push changes to the Repo.
6. Run `diff --color='auto' env.prod.template .env` to display the diff between
`env.prod.template` and `.env`.
7. Port the made changes to `.env`.
8. `./scripts/compose.sh pull`
9. `./scripts/compose.sh down`
10. `./scripts/compose.sh up -d; ./scripts/compose.sh logs -f`
## Disaster recovery / restore
**IMPORTANT:**
You've to use different docker cli clients on prod/test.
- Prod
- Docker: `docker`
- Docker compose: `docker compose`
- Test
- Docker: `docker`
- Docker compose: `docker-compose-2.32.4`
For the ease of readability I'll use `docker` and `docker compose` in the documentation below,
please repalce it for restores on test!
1. Run the restore cli
```shell
docker compose run --rm restore-cli
```
2. Run the restore command and follow it's instructions
```shell
restore
```
3. If the restore was successfull, exit the restore container.
**DO NOT START THE APPLICATION YET!**
4. Run the PostgreSQL container without starting the main application
```shell
docker compose run --rm postgresql
```
5. Open another shell in the sso git directory.
6. Execute a shell in the running PostgreSQL container (replace `<containerid>` with the actual container id)
```shell
docker exec -it sso-base23-de-postgresql-run-<containerid> bash
```
7. If the database already contains data, delete an recreate it:
```shell
dropdb -U ${PG_USER:-authentik} ${PG_DB:-authentik}
createdb -U ${PG_USER:-authentik} ${PG_DB:-authentik}
```
8. Restore the database
```shell
psql ${PG_USER:-authentik} -d ${PG_DB:-authentik} -f /var/lib/postgresql/backups/authentik.sql
````
9. After the database is restored, exit the container
10. Now it's safe to start the complete application stack again
```shell
docker compose up -d; docker compose logs -f
```
## Rebuild containers locally
```shell
docker compose build --no-cache \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg SRC_REV=$(git rev-parse --short HEAD)
```
The repository webhook should use the Procedure's `__ANY__` endpoint so pushes
to either protected environment branch enter the same ordered flow.

View file

View file

@ -2,7 +2,7 @@
services:
geoipupdate:
image: "maxmindinc/geoipupdate:latest"
image: "maxmindinc/geoipupdate:${GEOIPUPDATE_TAG:?GEOIPUPDATE_TAG is not configured}"
restart: unless-stopped
volumes:
- "geoip:/usr/share/GeoIP"
@ -10,7 +10,9 @@ services:
GEOIPUPDATE_EDITION_IDS: "GeoLite2-City GeoLite2-ASN"
GEOIPUPDATE_FREQUENCY: "8"
GEOIPUPDATE_ACCOUNT_ID: "${GEOIPUPDATE_ACCOUNT_ID:?MaxMind GeoIP account ID required}"
GEOIPUPDATE_LICENSE_KEY: "${GEOIPUPDATE_LICENSE_KEY:?MaxMind GeoIP license key required}"
GEOIPUPDATE_LICENSE_KEY_FILE: /run/secrets/geoip_license_key
secrets:
- geoip_license_key
postgresql:
image: docker.io/library/postgres:${POSTGRES_TAG:?POSTGRES_TAG is not configured}
@ -24,8 +26,6 @@ services:
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:?AUTHENTIK_TAG is not configured}
environment:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:?PG_PASS is required. - Password for authentik's postgresql database}
ports: []
volumes:
- media:/media
@ -58,8 +58,6 @@ services:
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:?AUTHENTIK_TAG is not configured}
environment:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:?PG_PASS is required. - Password for authentik's postgresql database}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- media:/media
@ -80,4 +78,4 @@ volumes:
networks:
backend:
dokploy-network:
external: true
external: true

View file

@ -1,10 +1,10 @@
services:
postgresql:
env_file:
- .env
- env/common.env
environment:
POSTGRES_DB: ${PG_DB:-authentik}
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_USER: ${PG_USER:-authentik}
healthcheck:
interval: 30s
@ -16,6 +16,8 @@ services:
timeout: 5s
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
secrets:
- postgres_password
volumes:
- database:/var/lib/postgresql/data
redis:
@ -40,19 +42,24 @@ services:
redis:
condition: service_healthy
env_file:
- .env
- env/common.env
environment:
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__PASSWORD: file:///run/secrets/postgres_password
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_SECRET_KEY: file:///run/secrets/authentik_secret_key
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.1}
ports:
- ${COMPOSE_PORT_HTTP:-9000}:9000
- ${COMPOSE_PORT_HTTPS:-9443}:9443
restart: unless-stopped
secrets:
- authentik_email_password
- authentik_secret_key
- postgres_password
volumes:
- ./media:/media
- ./custom-templates:/templates
@ -64,22 +71,36 @@ services:
redis:
condition: service_healthy
env_file:
- .env
- env/common.env
environment:
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__PASSWORD: file:///run/secrets/postgres_password
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_SECRET_KEY: file:///run/secrets/authentik_secret_key
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.1}
restart: unless-stopped
secrets:
- authentik_email_password
- authentik_secret_key
- postgres_password
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./media:/media
- ./certs:/certs
- ./custom-templates:/templates
secrets:
authentik_email_password:
environment: AUTHENTIK_EMAIL__PASSWORD
authentik_secret_key:
environment: AUTHENTIK_SECRET_KEY
geoip_license_key:
environment: GEOIPUPDATE_LICENSE_KEY
postgres_password:
environment: PG_PASS
volumes:
database:
driver: local

31
env/common.env vendored Normal file
View file

@ -0,0 +1,31 @@
# Deployment identity
DEPLOYMENT_ENVIRONMENT=prod
COMPOSE_PROJECT_NAME=sso-s1q-dev
PUBLIC_DOMAIN=sso.s1q.dev
TLS_DOMAIN=*.s1q.dev
# Explicit image versions. Update these values through Git.
AUTHENTIK_TAG=2025.8.1
POSTGRES_TAG=17.6-alpine
REDIS_TAG=8.0-alpine
GEOIPUPDATE_TAG=v7.1.1
# Authentik logging and error reporting
AUTHENTIK_ERROR_REPORTING__ENABLED=true
AUTHENTIK_LOG_LEVEL=warning
# Email
AUTHENTIK_EMAIL__HOST=mail.s1q.dev
AUTHENTIK_EMAIL__PORT=25
AUTHENTIK_EMAIL__USERNAME=smtp@s1q.dev
AUTHENTIK_EMAIL__USE_TLS=true
AUTHENTIK_EMAIL__USE_SSL=false
AUTHENTIK_EMAIL__TIMEOUT=10
AUTHENTIK_EMAIL__FROM=sso@s1q.dev
# MaxMind GeoIP
GEOIPUPDATE_ACCOUNT_ID=765001
# PostgreSQL
PG_USER=authentik
PG_DB=authentik

29
komodo.toml Normal file
View file

@ -0,0 +1,29 @@
[[stack]]
name = "authentik-prod"
description = "Production Authentik identity provider"
tags = ["sync:authentik", "application:authentik", "environment:production"]
deploy = false
[stack.config]
server = "sbx0docker01"
project_name = "sso-s1q-dev"
git_provider = "git.s1q.dev"
git_https = true
repo = "docker-compose/authentik"
branch = "main"
run_directory = ""
file_paths = ["docker-compose.yml", "docker-compose.override.yml"]
additional_env_files = [
{ path = "env/common.env", track = true },
{ path = ".komodo/prod.env", track = false },
]
config_files = [
{ path = "secrets/prod.env.age", requires = "Redeploy" },
]
compose_cmd_wrapper = "with-age-env secrets/prod.env.age .komodo/prod.env -- [[COMPOSE_COMMAND]]"
compose_cmd_wrapper_include = ["config", "pull", "up", "run"]
webhook_enabled = false
webhook_force_deploy = false
auto_update = false
poll_for_updates = false
send_alerts = true

BIN
secrets/prod.env.age Normal file

Binary file not shown.

47
tests/compose-secrets.sh Normal file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
test_dir=$(mktemp -d)
trap 'rm -rf -- "$test_dir"' EXIT
install -d "$test_dir/env" "$test_dir/.komodo"
cp -- "$repo_root/docker-compose.yml" "$test_dir/docker-compose.yml"
cp -- "$repo_root/docker-compose.override.yml" "$test_dir/docker-compose.override.yml"
cp -- "$repo_root/env/common.env" "$test_dir/env/common.env"
cat > "$test_dir/.komodo/prod.env" <<'EOF'
PG_PASS=SENTINEL_POSTGRES_PASSWORD
AUTHENTIK_SECRET_KEY=SENTINEL_AUTHENTIK_SECRET_KEY
AUTHENTIK_EMAIL__PASSWORD=SENTINEL_EMAIL_PASSWORD
GEOIPUPDATE_LICENSE_KEY=SENTINEL_GEOIP_LICENSE_KEY
EOF
(
cd "$test_dir"
docker compose \
--env-file env/common.env \
--env-file .komodo/prod.env \
--file docker-compose.yml \
--file docker-compose.override.yml \
config --format json
) > "$test_dir/rendered.json"
if grep -F 'SENTINEL_' "$test_dir/rendered.json"; then
printf 'Rendered Compose configuration contains plaintext secrets\n' >&2
exit 1
fi
jq -e '
.services.postgresql.environment.POSTGRES_PASSWORD_FILE == "/run/secrets/postgres_password" and
(.services.postgresql.environment | has("POSTGRES_PASSWORD") | not) and
.services.server.environment.AUTHENTIK_POSTGRESQL__PASSWORD == "file:///run/secrets/postgres_password" and
.services.server.environment.AUTHENTIK_SECRET_KEY == "file:///run/secrets/authentik_secret_key" and
.services.server.environment.AUTHENTIK_EMAIL__PASSWORD == "file:///run/secrets/authentik_email_password" and
.services.geoipupdate.environment.GEOIPUPDATE_LICENSE_KEY_FILE == "/run/secrets/geoip_license_key" and
([.services.postgresql.secrets[].source] | sort) == ["postgres_password"] and
([.services.server.secrets[].source] | sort) == ["authentik_email_password", "authentik_secret_key", "postgres_password"] and
([.services.worker.secrets[].source] | sort) == ["authentik_email_password", "authentik_secret_key", "postgres_password"] and
([.services.geoipupdate.secrets[].source] | sort) == ["geoip_license_key"] and
(.services.redis | has("secrets") | not)
' "$test_dir/rendered.json" >/dev/null