feat: deploy Authentik with age-encrypted secrets
This commit is contained in:
parent
9f8f619f3e
commit
e966ec09ae
9 changed files with 240 additions and 260 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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
341
README.md
|
|
@ -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.
|
||||
|
|
|
|||
0
data/authentik/custom-templates/.gitkeep
Normal file
0
data/authentik/custom-templates/.gitkeep
Normal 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
|
||||
|
|
|
|||
|
|
@ -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
31
env/common.env
vendored
Normal 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
29
komodo.toml
Normal 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
BIN
secrets/prod.env.age
Normal file
Binary file not shown.
47
tests/compose-secrets.sh
Normal file
47
tests/compose-secrets.sh
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue