feat: enhance Authentik deployment with age-encrypted secrets; change deployment to komodo
This commit is contained in:
parent
c3689404bd
commit
71736f8dd9
9 changed files with 254 additions and 369 deletions
12
.age-docker.toml
Normal file
12
.age-docker.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
[keys]
|
||||||
|
phg = "age1uyag8gskstp590dlsw5wej3wdzu0turr9pjn0ln734nn23csu36q9dt0fw"
|
||||||
|
sbx0docker01 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4umkUY5CG5aNJBhUjVcU8TWbh453N0pGHZhYDTOGQa"
|
||||||
|
|
||||||
|
[groups]
|
||||||
|
users = ["phg"]
|
||||||
|
production = ["sbx0docker01"]
|
||||||
|
|
||||||
|
[secrets]
|
||||||
|
"secrets/prod.env.age" = ["users", "production"]
|
||||||
216
README.md
216
README.md
|
|
@ -1,37 +1,68 @@
|
||||||
# Authentik deployment
|
# Authentik deployment
|
||||||
|
|
||||||
This repository owns the production Authentik Compose deployment and its
|
This repository owns the production deployment of
|
||||||
Komodo Stack declaration. The central `komodo-bootstrap` repository owns the
|
[Authentik](https://goauthentik.io/) for `sso.s1q.dev`, including its Compose
|
||||||
child Resource Sync and the ordered deployment Procedure.
|
configuration, encrypted secrets, and Komodo Stack declaration.
|
||||||
|
|
||||||
## Layout
|
## Deployment architecture
|
||||||
|
|
||||||
|
The `authentik-prod` Stack follows the protected `main` branch. Its direct
|
||||||
|
webhook is disabled. A Forgejo webhook calls the central `authentik-deploy`
|
||||||
|
Procedure, which first runs the child Resource Sync and then runs
|
||||||
|
`DeployStackIfChanged`. The central `docker-compose/komodo-bootstrap`
|
||||||
|
repository owns that child Sync and Procedure.
|
||||||
|
|
||||||
|
Komodo passes `docker-compose.yml` first and `docker-compose.override.yml`
|
||||||
|
second. The upstream-derived base and deployment-specific override remain
|
||||||
|
separate by design. A regular deployment therefore follows this path:
|
||||||
|
|
||||||
|
1. merge a reviewed change into `main`;
|
||||||
|
2. Forgejo calls the `authentik-deploy` Procedure webhook;
|
||||||
|
3. the Procedure syncs `komodo.toml`; and
|
||||||
|
4. Komodo deploys `authentik-prod` if a tracked file changed.
|
||||||
|
|
||||||
|
For a first deployment or a manual retry, run `authentik-deploy` in Komodo
|
||||||
|
instead of deploying the Stack directly. This keeps resource synchronization
|
||||||
|
and deployment in the intended order.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before the first deployment, ensure that:
|
||||||
|
|
||||||
|
- NixOS has provisioned the `sbx0docker01` Komodo Server, Periphery's container
|
||||||
|
permissions, the `with-age-env` wrapper, and its `age-identity` credential;
|
||||||
|
- the external `dokploy-network` exists on the deployment host;
|
||||||
|
- the root `bootstrap` Resource Sync has registered the `authentik` child Sync
|
||||||
|
and the `authentik-deploy` Procedure; and
|
||||||
|
- the Forgejo repository webhook uses the Procedure's `__ANY__` webhook URL.
|
||||||
|
|
||||||
|
Host provisioning, Tailscale, CrowdSec, and registry setup belong to NixOS or
|
||||||
|
their respective infrastructure repositories and are intentionally not
|
||||||
|
duplicated here.
|
||||||
|
|
||||||
|
## Layout and ownership
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.
|
.
|
||||||
├── docker-compose.yml
|
├── .age-docker.toml # Secret recipients and policy
|
||||||
├── docker-compose.override.yml
|
├── docker-compose.yml # Upstream-derived Compose base
|
||||||
├── komodo.toml
|
├── docker-compose.override.yml # Production networks, routing, and images
|
||||||
├── env/
|
├── env/
|
||||||
│ └── common.env
|
│ └── common.env # Tracked non-secret configuration and versions
|
||||||
|
├── komodo.toml # authentik-prod Stack declaration
|
||||||
├── secrets/
|
├── secrets/
|
||||||
│ └── prod.env.age
|
│ └── prod.env.age # Encrypted production secrets
|
||||||
├── scripts/
|
└── data/authentik/custom-templates/
|
||||||
│ ├── compose.sh
|
|
||||||
│ └── init.sh
|
|
||||||
└── tests/
|
|
||||||
└── compose-secrets.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`env/common.env` contains tracked, non-secret configuration and explicit image
|
NixOS owns Komodo, Periphery, host credentials, Podman permissions, and the
|
||||||
versions. `secrets/prod.env.age` contains only secrets and is encrypted to the
|
external `dokploy-network`. This repository owns the application definition.
|
||||||
`sbx0docker01` SSH host key and the personal `agenix-phg` SSH key.
|
Persistent database, Redis, media, certificate, and GeoIP data live in named
|
||||||
|
container volumes on the deployment host and are not stored in Git.
|
||||||
|
|
||||||
The `authentik-prod` Stack follows protected `main`. Its direct webhook is
|
`env/common.env` is the authoritative source for tracked runtime settings and
|
||||||
disabled. A Forgejo webhook calls the central `authentik-deploy` Procedure,
|
explicit image versions. The old local `.env`/template and init-script
|
||||||
which runs the child Sync before `DeployStackIfChanged`.
|
workflow is no longer part of deployment.
|
||||||
|
|
||||||
Komodo passes `docker-compose.yml` first and `docker-compose.override.yml`
|
|
||||||
second. The base and deployment-specific override remain separate by design.
|
|
||||||
|
|
||||||
## Secrets
|
## Secrets
|
||||||
|
|
||||||
|
|
@ -44,33 +75,33 @@ AUTHENTIK_EMAIL__PASSWORD
|
||||||
GEOIPUPDATE_LICENSE_KEY
|
GEOIPUPDATE_LICENSE_KEY
|
||||||
```
|
```
|
||||||
|
|
||||||
To edit it locally, decrypt to a mode-`0600` temporary file, edit it, then
|
Local secret management uses only `age-docker`. Recipient keys, groups, and
|
||||||
encrypt it to both recipients:
|
the rule for the encrypted bundle live in `.age-docker.toml`. The bundle is
|
||||||
|
currently encrypted for the personal `phg` age key and the `sbx0docker01` SSH
|
||||||
|
host key.
|
||||||
|
|
||||||
|
Edit the bundle with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tmp=$(mktemp)
|
age-docker edit secrets/prod.env.age
|
||||||
chmod 0600 "$tmp"
|
|
||||||
trap 'rm -f -- "$tmp"' EXIT HUP INT TERM
|
|
||||||
|
|
||||||
age --decrypt \
|
|
||||||
--identity ~/.ssh/identities/agenix-phg \
|
|
||||||
--output "$tmp" \
|
|
||||||
secrets/prod.env.age
|
|
||||||
|
|
||||||
${EDITOR:-vi} "$tmp"
|
|
||||||
|
|
||||||
age --encrypt \
|
|
||||||
--armor \
|
|
||||||
--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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not add a plaintext secret file to Git. The Stack config tracks the
|
When a configured recipient changes, re-encrypt the bundle and validate the
|
||||||
encrypted bundle so a rotation counts as a deployment change, but does not
|
complete policy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
age-docker rekey secrets/prod.env.age
|
||||||
|
age-docker check
|
||||||
|
```
|
||||||
|
|
||||||
|
`age-docker` creates mode-`0600` temporary plaintext, removes it after the
|
||||||
|
editor exits or a handled signal occurs, produces armored ciphertext, and
|
||||||
|
verifies the new ciphertext with the personal identity before atomically
|
||||||
|
replacing the tracked file. It does not add implicit recipients: every key
|
||||||
|
with access is declared in `.age-docker.toml`.
|
||||||
|
|
||||||
|
Never add a plaintext secret file to Git. The Stack tracks the encrypted
|
||||||
|
bundle so that secret rotation counts as a deployment change, but it does not
|
||||||
track the temporary `.komodo/prod.env` file.
|
track the temporary `.komodo/prod.env` file.
|
||||||
|
|
||||||
The decrypted values are Compose secret sources rather than service
|
The decrypted values are Compose secret sources rather than service
|
||||||
|
|
@ -80,10 +111,10 @@ while PostgreSQL and GeoIP Update use their `_FILE` variables. Consequently,
|
||||||
`docker compose config` renders secret source names and file paths without
|
`docker compose config` renders secret source names and file paths without
|
||||||
persisting the values in Komodo's merged configuration.
|
persisting the values in Komodo's merged configuration.
|
||||||
|
|
||||||
## Compose wrapper
|
## Komodo Compose wrapper
|
||||||
|
|
||||||
Komodo wraps `config`, `pull`, `up`, and `run` with
|
Komodo wraps `config`, `pull`, `up`, and `run` with the NixOS-provided
|
||||||
the NixOS-provided `with-age-env` command. The wrapper:
|
`with-age-env` command. The wrapper:
|
||||||
|
|
||||||
- obtains an exclusive per-bundle lock;
|
- obtains an exclusive per-bundle lock;
|
||||||
- removes stale plaintext and temporary files;
|
- removes stale plaintext and temporary files;
|
||||||
|
|
@ -95,15 +126,86 @@ The plaintext target and matching `.lock` and `.tmp.*` paths are exclusively
|
||||||
owned by the wrapper. A `SIGKILL` or power loss can leave plaintext behind;
|
owned by the wrapper. A `SIGKILL` or power loss can leave plaintext behind;
|
||||||
the next invocation removes it before decrypting again.
|
the next invocation removes it before decrypting again.
|
||||||
|
|
||||||
NixOS owns the wrapper, its runtime dependencies, the host-key systemd
|
Workload containers do not receive the age identity, but commands launched by
|
||||||
credential, Periphery's Podman permissions, and the external
|
Periphery can access it. Revisit that trust boundary if deployment authority
|
||||||
`dokploy-network`. Workload containers do not receive the age identity, but
|
is ever granted to another operator.
|
||||||
commands launched by Periphery can access it. Revisit that trust boundary if
|
|
||||||
deployment authority is ever granted to another operator.
|
|
||||||
|
|
||||||
Run `bash tests/compose-secrets.sh` to verify that sentinel secret values do
|
## Upgrade
|
||||||
not appear in the rendered Compose model and that every service receives only
|
|
||||||
its intended secret files.
|
There is currently no long-lived test environment. Prepare and review every
|
||||||
|
upgrade on a branch before merging it into production. If a test Stack is
|
||||||
|
added later, perform the upgrade there first.
|
||||||
|
|
||||||
|
1. Confirm that the host backups are current and restorable.
|
||||||
|
2. Read the target version's [Authentik release notes](https://docs.goauthentik.io/releases/)
|
||||||
|
and upgrading section. Note all manual migrations, breaking configuration
|
||||||
|
changes, and outpost compatibility requirements before changing files.
|
||||||
|
3. Create an upgrade branch.
|
||||||
|
4. Download the target version's upstream Compose file alongside the tracked
|
||||||
|
file. Replace the example version with the intended target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
authentik_version="2025.8"
|
||||||
|
curl -fsSL \
|
||||||
|
"https://goauthentik.io/version/${authentik_version}/lifecycle/container/compose.yml" \
|
||||||
|
-o docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Compare `docker-compose.yml` with the previously tracked version.
|
||||||
|
6. Review `docker-compose.override.yml` against the new base structure. Keep
|
||||||
|
the production networks, Traefik routing, volumes, and explicit image
|
||||||
|
declarations working with the new release.
|
||||||
|
7. Update `AUTHENTIK_TAG` in `env/common.env`. Review the supported PostgreSQL
|
||||||
|
and Redis versions and update `POSTGRES_TAG`, `REDIS_TAG`, and
|
||||||
|
`GEOIPUPDATE_TAG` only after checking their respective release and migration
|
||||||
|
notes. Plan to update all Authentik outposts to the same Authentik version.
|
||||||
|
8. Remove the downloaded comparison file and validate the resulting policy
|
||||||
|
and Compose model. Dummy values satisfy Compose's secret-source checks
|
||||||
|
without decrypting production secrets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm docker-compose.upstream.yml
|
||||||
|
age-docker check
|
||||||
|
PG_PASS=dummy \
|
||||||
|
AUTHENTIK_SECRET_KEY=dummy \
|
||||||
|
AUTHENTIK_EMAIL__PASSWORD=dummy \
|
||||||
|
GEOIPUPDATE_LICENSE_KEY=dummy \
|
||||||
|
docker compose \
|
||||||
|
--env-file env/common.env \
|
||||||
|
--file docker-compose.yml \
|
||||||
|
--file docker-compose.override.yml \
|
||||||
|
config --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
9. Review the complete diff, commit it, push the branch, and merge it through a
|
||||||
|
merge request. Do not run `docker compose pull`, `down`, or `up` manually on
|
||||||
|
the host; the merge triggers the ordered Komodo deployment flow.
|
||||||
|
10. Follow the `authentik-deploy` Procedure and `authentik-prod` Stack logs in
|
||||||
|
Komodo. Verify that all services become healthy, `https://sso.s1q.dev` is
|
||||||
|
reachable, and an SSO login succeeds. Update outposts to the matching
|
||||||
|
version and verify their connectivity.
|
||||||
|
|
||||||
|
Record any repository-specific manual migration steps in this README before
|
||||||
|
the rollout. None are currently documented.
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
For a configuration or image regression, revert the merge on `main`; the
|
||||||
|
webhook will run the same ordered deployment flow with the previous tracked
|
||||||
|
configuration. Do not assume that an application or database migration can be
|
||||||
|
downgraded. Follow the target release's rollback guidance and restore a tested
|
||||||
|
backup when a migration is not reversible.
|
||||||
|
|
||||||
|
## Backup and disaster recovery
|
||||||
|
|
||||||
|
This Compose project does not define a backup or `restore-cli` service. The
|
||||||
|
old container-based restore commands therefore no longer apply. Backups and
|
||||||
|
volume recovery must be provided and documented by the host infrastructure.
|
||||||
|
|
||||||
|
Before relying on the deployment in production, ensure that the PostgreSQL
|
||||||
|
data and all named volumes required by Authentik are covered by a tested
|
||||||
|
backup and recovery runbook. Keep the runbook with the infrastructure that
|
||||||
|
owns those backups and link it here once it exists.
|
||||||
|
|
||||||
## Optional test environment
|
## Optional test environment
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
services:
|
services:
|
||||||
geoipupdate:
|
geoipupdate:
|
||||||
image: "maxmindinc/geoipupdate:${GEOIPUPDATE_TAG:?GEOIPUPDATE_TAG is not configured}"
|
image: "maxmindinc/geoipupdate:v7.1.1"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- "geoip:/usr/share/GeoIP"
|
- "geoip:/usr/share/GeoIP"
|
||||||
|
|
@ -14,29 +13,47 @@ services:
|
||||||
secrets:
|
secrets:
|
||||||
- geoip_license_key
|
- geoip_license_key
|
||||||
|
|
||||||
postgresql:
|
postgresql: !reset []
|
||||||
image: docker.io/library/postgres:${POSTGRES_TAG:?POSTGRES_TAG is not configured}
|
# env_file: !override []
|
||||||
networks:
|
# environment:
|
||||||
- backend
|
# POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||||
|
# secrets:
|
||||||
|
# - postgres_password
|
||||||
|
# networks:
|
||||||
|
# - backend
|
||||||
|
# ports: !override []
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: docker.io/library/redis:${REDIS_TAG:?REDIS_TAG is not configured}
|
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:?AUTHENTIK_TAG is not configured}
|
depends_on:
|
||||||
ports: []
|
postgresql: !reset []
|
||||||
|
env_file: !reset []
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_POSTGRESQL__HOST:?Authentik PostgreSQL host required}
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT: ${AUTHENTIK_POSTGRESQL__PORT:?Authentik PostgreSQL port required}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: file:///run/secrets/postgres_password
|
||||||
|
AUTHENTIK_POSTGRESQL__SSLMODE: ${AUTHENTIK_POSTGRESQL__SSLMODE:-disable}
|
||||||
|
AUTHENTIK_POSTGRESQL__SSLROOTCERT: ${AUTHENTIK_POSTGRESQL__SSLROOTCERT:-/etc/ssl/certs/ca-certificates.crt}
|
||||||
|
AUTHENTIK_SECRET_KEY: file:///run/secrets/authentik_secret_key
|
||||||
|
ports: !reset []
|
||||||
|
secrets:
|
||||||
|
- authentik_email_password
|
||||||
|
- authentik_secret_key
|
||||||
|
- postgres_password
|
||||||
volumes:
|
volumes:
|
||||||
- media:/media
|
- media:/media
|
||||||
- ./data/authentik/custom-templates:/templates
|
- ./data/authentik/custom-templates:/templates
|
||||||
- geoip:/geoip
|
- geoip:/geoip
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
- dokploy-network
|
- frontend
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.docker.network=dokploy-network"
|
- "traefik.docker.network=frontend"
|
||||||
|
|
||||||
- "traefik.http.services.sso-server.loadbalancer.server.port=9443" # set port the container listenes to
|
- "traefik.http.services.sso-server.loadbalancer.server.port=9443" # set port the container listenes to
|
||||||
- "traefik.http.services.sso-server.loadbalancer.server.scheme=https"
|
- "traefik.http.services.sso-server.loadbalancer.server.scheme=https"
|
||||||
|
|
@ -57,7 +74,19 @@ services:
|
||||||
|
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:?AUTHENTIK_TAG is not configured}
|
env_file: !reset []
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_POSTGRESQL__HOST:?Authentik PostgreSQL host required}
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT: ${AUTHENTIK_POSTGRESQL__PORT:?Authentik PostgreSQL port required}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: file:///run/secrets/postgres_password
|
||||||
|
AUTHENTIK_POSTGRESQL__SSLMODE: ${AUTHENTIK_POSTGRESQL__SSLMODE:-disable}
|
||||||
|
AUTHENTIK_POSTGRESQL__SSLROOTCERT: ${AUTHENTIK_POSTGRESQL__SSLROOTCERT:-/etc/ssl/certs/ca-certificates.crt}
|
||||||
|
AUTHENTIK_SECRET_KEY: file:///run/secrets/authentik_secret_key
|
||||||
|
secrets:
|
||||||
|
- authentik_email_password
|
||||||
|
- authentik_secret_key
|
||||||
|
- postgres_password
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- media:/media
|
- media:/media
|
||||||
|
|
@ -67,6 +96,16 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
|
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:
|
volumes:
|
||||||
geoip:
|
geoip:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
@ -77,5 +116,5 @@ volumes:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
backend:
|
backend:
|
||||||
dokploy-network:
|
frontend:
|
||||||
external: true
|
external: true
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
services:
|
services:
|
||||||
postgresql:
|
postgresql:
|
||||||
env_file:
|
env_file:
|
||||||
- env/common.env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${PG_DB:-authentik}
|
POSTGRES_DB: ${PG_DB:-authentik}
|
||||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
|
||||||
POSTGRES_USER: ${PG_USER:-authentik}
|
POSTGRES_USER: ${PG_USER:-authentik}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|
@ -16,8 +16,6 @@ services:
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
image: docker.io/library/postgres:16-alpine
|
image: docker.io/library/postgres:16-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
secrets:
|
|
||||||
- postgres_password
|
|
||||||
volumes:
|
volumes:
|
||||||
- database:/var/lib/postgresql/data
|
- database:/var/lib/postgresql/data
|
||||||
redis:
|
redis:
|
||||||
|
|
@ -42,24 +40,19 @@ services:
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file:
|
env_file:
|
||||||
- env/common.env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
|
|
||||||
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||||
AUTHENTIK_POSTGRESQL__PASSWORD: file:///run/secrets/postgres_password
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||||
AUTHENTIK_REDIS__HOST: redis
|
AUTHENTIK_REDIS__HOST: redis
|
||||||
AUTHENTIK_SECRET_KEY: file:///run/secrets/authentik_secret_key
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.6}
|
||||||
ports:
|
ports:
|
||||||
- ${COMPOSE_PORT_HTTP:-9000}:9000
|
- ${COMPOSE_PORT_HTTP:-9000}:9000
|
||||||
- ${COMPOSE_PORT_HTTPS:-9443}:9443
|
- ${COMPOSE_PORT_HTTPS:-9443}:9443
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
secrets:
|
|
||||||
- authentik_email_password
|
|
||||||
- authentik_secret_key
|
|
||||||
- postgres_password
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./media:/media
|
- ./media:/media
|
||||||
- ./custom-templates:/templates
|
- ./custom-templates:/templates
|
||||||
|
|
@ -71,36 +64,22 @@ services:
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file:
|
env_file:
|
||||||
- env/common.env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
AUTHENTIK_EMAIL__PASSWORD: file:///run/secrets/authentik_email_password
|
|
||||||
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||||
AUTHENTIK_POSTGRESQL__PASSWORD: file:///run/secrets/postgres_password
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||||
AUTHENTIK_REDIS__HOST: redis
|
AUTHENTIK_REDIS__HOST: redis
|
||||||
AUTHENTIK_SECRET_KEY: file:///run/secrets/authentik_secret_key
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.6}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
secrets:
|
|
||||||
- authentik_email_password
|
|
||||||
- authentik_secret_key
|
|
||||||
- postgres_password
|
|
||||||
user: root
|
user: root
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./media:/media
|
- ./media:/media
|
||||||
- ./certs:/certs
|
- ./certs:/certs
|
||||||
- ./custom-templates:/templates
|
- ./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:
|
volumes:
|
||||||
database:
|
database:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
|
||||||
17
env/common.env
vendored
17
env/common.env
vendored
|
|
@ -1,18 +1,17 @@
|
||||||
# Deployment identity
|
# Deployment identity
|
||||||
DEPLOYMENT_ENVIRONMENT=prod
|
|
||||||
COMPOSE_PROJECT_NAME=sso-s1q-dev
|
COMPOSE_PROJECT_NAME=sso-s1q-dev
|
||||||
PUBLIC_DOMAIN=sso.s1q.dev
|
PUBLIC_DOMAIN=sso.s1q.dev
|
||||||
TLS_DOMAIN=*.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 logging and error reporting
|
||||||
AUTHENTIK_ERROR_REPORTING__ENABLED=true
|
AUTHENTIK_ERROR_REPORTING__ENABLED=false
|
||||||
AUTHENTIK_LOG_LEVEL=warning
|
AUTHENTIK_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Authentik database configuration
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST=sbx0db00.int.s1q.dev
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT=5432
|
||||||
|
AUTHENTIK_POSTGRESQL__SSLMODE=verify-full
|
||||||
|
AUTHENTIK_POSTGRESQL__SSLROOTCERT=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
AUTHENTIK_EMAIL__HOST=mail.s1q.dev
|
AUTHENTIK_EMAIL__HOST=mail.s1q.dev
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euf -o pipefail
|
|
||||||
|
|
||||||
# Check if yq is installed
|
|
||||||
if ! command -v yq &>/dev/null; then
|
|
||||||
echo "yq is required but not installed. Please install it manually."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
STAGE="${1:-}"
|
|
||||||
shift || true
|
|
||||||
|
|
||||||
if [[ -z "${STAGE,,}" ]]; then
|
|
||||||
echo "Usage: $0 <stage>"
|
|
||||||
echo "Example: $0 test"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$(dirname "$(realpath "$0")")/../"
|
|
||||||
AUTHENTIK_DOCKER_COMPOSE_PATH="$(realpath "$(pwd)")"
|
|
||||||
|
|
||||||
# Merge docker-compose files using yq
|
|
||||||
# 1st merger is docker-compose.override.yml on top of the base docker-compose.yml
|
|
||||||
# 2nd merger is the stage-specific docker-compose file on top of the result of the first merger
|
|
||||||
# The final result is piped to docker compose command
|
|
||||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' \
|
|
||||||
docker-compose.yml \
|
|
||||||
docker-compose.override.yml \
|
|
||||||
| yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' \
|
|
||||||
- \
|
|
||||||
docker-compose.${STAGE,,}.yml \
|
|
||||||
| ${DOCKER_COMPOSE_CLI} -f- ${@:-}
|
|
||||||
167
scripts/init.sh
167
scripts/init.sh
|
|
@ -1,167 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euf -o pipefail
|
|
||||||
|
|
||||||
# Ask if initialized for production or test
|
|
||||||
while true; do
|
|
||||||
read -p "Do you want to init a [P]roduction or [T]est environment? (P/T): " DEPLOYMENT_ENVIRONMENT
|
|
||||||
case "$DEPLOYMENT_ENVIRONMENT" in
|
|
||||||
[Pp]* )
|
|
||||||
DEPLOYMENT_ENVIRONMENT="PRODUCTION"
|
|
||||||
ENV_TEMPLATE="env.prod.template"
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
[Tt]* )
|
|
||||||
DEPLOYMENT_ENVIRONMENT="TEST"
|
|
||||||
ENV_TEMPLATE="env.test.template"
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
echo "Please answer with P or T."
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
source $(dirname "$(readlink -f "$0")")/../${ENV_TEMPLATE}
|
|
||||||
|
|
||||||
SERVICE_DOMAIN="${RESTIC_TAG:?Restic backup tag is missing -- RESTIC_TAG}"
|
|
||||||
BACKUP_TARGET_DOMAIN="${RESTIC_REPO_ADDRESS:?Restic backup target domain is missing -- RESTIC_REPO_ADDRESS}"
|
|
||||||
BACKUP_TARGET_USER="${RESTIC_REPO_USER:?Restic backup target user is missing -- RESTIC_REPO_USER}"
|
|
||||||
HOSTNAME=$(hostname -f)
|
|
||||||
|
|
||||||
# Function to securely query user for a password, verify it, and return it for further use
|
|
||||||
prompt_password() {
|
|
||||||
local purpose="$1"
|
|
||||||
local password password_confirm
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
printf "Enter password for %s: " "$purpose"
|
|
||||||
read -rs password
|
|
||||||
printf "\nConfirm password for %s: " "$purpose"
|
|
||||||
read -rs password_confirm
|
|
||||||
printf "\n"
|
|
||||||
|
|
||||||
# Check if passwords match
|
|
||||||
if [[ "$password" == "$password_confirm" ]]; then
|
|
||||||
RETURNED_PASSWORD="$password"
|
|
||||||
printf "Password verified for %s.\n" "$purpose"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
printf "Error: Passwords do not match. Please try again.\n" >&2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Trap SIGINT to exit gracefully if the user aborts with CTRL+C
|
|
||||||
trap 'printf "\nOperation aborted by user.\n" >&2; exit 1' SIGINT
|
|
||||||
|
|
||||||
cd "$(dirname "$(realpath "$0")")/../"
|
|
||||||
AUTHENTIK_DOCKER_COMPOSE_PATH="$(realpath "$(pwd)")"
|
|
||||||
|
|
||||||
if [[ ! -f ./docker-compose.yml ]]; then
|
|
||||||
[[ "${DEPLOYMENT_ENVIRONMENT}" == "PRODUCTION" ]] && ln -s ./docker-compose.prod.yml ./docker-compose.yml
|
|
||||||
[[ "${DEPLOYMENT_ENVIRONMENT}" == "TEST" ]] && ln -s ./docker-compose.test.yml ./docker-compose.yml
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if .env exists and exit if it is
|
|
||||||
if [[ ! -f ./.env ]]; then
|
|
||||||
cat ./${ENV_TEMPLATE} >> ./.env
|
|
||||||
echo "# SECRETS" >> ./.env
|
|
||||||
prompt_password "PG_PASS (leave empty to generate a password)"; echo "PG_PASS=$([[ -n ${RETURNED_PASSWORD} ]] && echo -n "${RETURNED_PASSWORD}" || openssl rand -base64 36 | tr -d '\n')" >> ./.env; unset RETURNED_PASSWORD
|
|
||||||
prompt_password "AUTHENTIK_SECRET_KEY (leave empty to generate a password)"; echo "AUTHENTIK_SECRET_KEY=$([[ -n ${RETURNED_PASSWORD} ]] && echo -n "${RETURNED_PASSWORD}" || openssl rand -base64 60 | tr -d '\n')" >> ./.env; unset RETURNED_PASSWORD
|
|
||||||
prompt_password "AUTHENTIK_EMAIL__PASSWORD"; echo "AUTHENTIK_EMAIL__PASSWORD=${RETURNED_PASSWORD}" >> ./.env; unset RETURNED_PASSWORD
|
|
||||||
prompt_password "GEOIPUPDATE_LICENSE_KEY"; echo "GEOIPUPDATE_LICENSE_KEY=${RETURNED_PASSWORD}" >> ./.env; unset RETURNED_PASSWORD
|
|
||||||
prompt_password "RESTIC_REPO_PASSWORD (leave empty to generate a password)"; echo "RESTIC_REPO_PASSWORD=$([[ -n ${RETURNED_PASSWORD} ]] && echo -n "${RETURNED_PASSWORD}" || openssl rand -base64 60 | tr -d '\n')" >> ./.env; unset RETURNED_PASSWORD
|
|
||||||
echo "" >> ./.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if lego.env exists and exit if it is
|
|
||||||
if [[ ! -f ./lego.env && "${DEPLOYMENT_ENVIRONMENT}" == "PRODUCTION" ]]; then
|
|
||||||
echo "# Lego - Let's Encrypt certificate tool" >> ./lego.env
|
|
||||||
prompt_password HETZNER_API_KEY; echo "HETZNER_API_KEY=${RETURNED_PASSWORD}" >> ./lego.env; unset RETURNED_PASSWORD
|
|
||||||
echo "" >> ./.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
BACKUP_TARGET_KEY_TYPES="ed25519,rsa"
|
|
||||||
BACKUP_TARGET_IPV4=$(dig +short "${BACKUP_TARGET_DOMAIN}" A | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
|
|
||||||
BACKUP_TARGET_IPV6=$(dig +short "${BACKUP_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]))$')
|
|
||||||
|
|
||||||
# Check if ssh key already exists, otherwise generate one
|
|
||||||
[[ ! -d ./data/restic/ssh/ ]] && mkdir -p ./data/restic/ssh/ && chmod 700 ./data/restic/ssh/
|
|
||||||
if [[ ! -f ./data/restic/ssh/id_ed25519 ]]; then
|
|
||||||
ssh-keygen -t ed25519 -C "${SERVICE_DOMAIN}" -f ./data/restic/ssh/id_ed25519 && chmod 600 ./data/restic/ssh/id_ed25519
|
|
||||||
|
|
||||||
# Copy SSH key to backup target
|
|
||||||
cat ./data/restic/ssh/id_ed25519.pub | ssh -p23 ${BACKUP_TARGET_USER}@${BACKUP_TARGET_DOMAIN} install-ssh-key
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Setup known_hosts for backup container
|
|
||||||
if [[ ! -f ./data/restic/ssh/known_hosts ]]; then
|
|
||||||
ssh-keyscan -p 23 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_DOMAIN} > ./data/restic/ssh/known_hosts
|
|
||||||
ssh-keyscan -p 23 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV4} >> ./data/restic/ssh/known_hosts
|
|
||||||
ssh-keyscan -p 23 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV6} >> ./data/restic/ssh/known_hosts
|
|
||||||
ssh-keyscan -p 22 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_DOMAIN} >> ./data/restic/ssh/known_hosts
|
|
||||||
ssh-keyscan -p 22 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV4} >> ./data/restic/ssh/known_hosts
|
|
||||||
ssh-keyscan -p 22 -t ${BACKUP_TARGET_KEY_TYPES} ${BACKUP_TARGET_IPV6} >> ./data/restic/ssh/known_hosts
|
|
||||||
chmod 600 ./data/restic/ssh/known_hosts
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [[ "${DEPLOYMENT_ENVIRONMENT}" == "PRODUCTION" ]]; then
|
|
||||||
# Generate dhparam, if not existing
|
|
||||||
[[ ! -d ./data/nginx/certs ]] && mkdir -p ./data/nginx/certs && chmod 700 ./data/nginx/certs && chown 101:101 ./data/nginx/certs || true
|
|
||||||
[[ ! -f ./data/nginx/dhparams.pem ]] && echo "" && openssl dhparam -out ./data/nginx/dhparams.pem 4096 && chown 101:101 ./data/nginx/dhparams.pem \
|
|
||||||
&& echo "" && echo "Checking generated dhparams" && openssl dhparam -check -in ./data/nginx/dhparams.pem || true
|
|
||||||
|
|
||||||
|
|
||||||
# Create certificate
|
|
||||||
if [[ ! -d ./data/.lego ]]; then
|
|
||||||
echo ""
|
|
||||||
echo "Create certificate"
|
|
||||||
lego \
|
|
||||||
--path ./data/.lego \
|
|
||||||
--accept-tos \
|
|
||||||
--email="acme@base23.de" \
|
|
||||||
--domains="*.base23.de" \
|
|
||||||
--dns hetzner \
|
|
||||||
run \
|
|
||||||
&& install -m 400 -o 101 -g 101 "./data/.lego/certificates"/{_.base23.de.crt,_.base23.de.issuer.crt,_.base23.de.key} "./data/nginx/certs"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Setup directory for acme cheallenges
|
|
||||||
[[ ! -d ./data/nginx/acme ]] && mkdir -p ./data/nginx/acme
|
|
||||||
|
|
||||||
# Setup cronjob to automatically renew certificates
|
|
||||||
[[ ! -f /etc/systemd/system/lego-renew-wildcard-base23-de.service ]] && cat <<EOF > /etc/systemd/system/lego-renew-wildcard-base23-de.service && systemctl daemon-reload
|
|
||||||
[Unit]
|
|
||||||
Description=SSL Certificate renewal for *.base23.de with LEGO
|
|
||||||
Documentation=https://go-acme.github.io/lego/
|
|
||||||
Wants=network-online.target
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
EnvironmentFile=${AUTHENTIK_DOCKER_COMPOSE_PATH}/lego.env
|
|
||||||
ExecStart=${AUTHENTIK_DOCKER_COMPOSE_PATH}/scripts/cert_renew.sh
|
|
||||||
WorkingDirectory=${AUTHENTIK_DOCKER_COMPOSE_PATH}/
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
RemainAfterExit=no
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
[[ ! -f /etc/systemd/system/lego-renew-wildcard-base23-de.timer ]] && cat <<EOF > /etc/systemd/system/lego-renew-wildcard-base23-de.timer && systemctl daemon-reload && systemctl enable --now lego-renew-wildcard-base23-de.timer
|
|
||||||
[Unit]
|
|
||||||
Description=SSL Certificate renewal for *.base23.de with LEGO Timer
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnCalendar=*-*-* 01:32:00
|
|
||||||
# add extra delay, here up to 1 hour:
|
|
||||||
RandomizedDelaySec=1h
|
|
||||||
Persistent=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE0yRy94ZyBQRnc4
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpRVlEMXB3b1ZsTDlqZ00z
|
||||||
V1d0c1RoU1Baa1VKcmcvRlJmNDVRcko0MC95Y25YU0FkZVNHTHpVCmlCc28yWGp5
|
djZQaXU5dG9BTGhNT2VDcEdqKytoalZ3M2dBCjZEQjJLZHBrbytzdXN1YitMLy9V
|
||||||
Tk1lKzFSUDdDSFdOc1JnM3lIUWx6ZUkxeUtFMDF0TldVRlkKLT4gc3NoLWVkMjU1
|
bGxKOXpvSjl2eWZFSEwxVGU3dnZYU2cKLT4gc3NoLWVkMjU1MTkgTTJHL3hnIEtY
|
||||||
MTkgUzVoZU1RIHRTejhlTmlHMGpEU0lCWnBJbDd1clVLcU9NZUZ0VXhqbWxtZGxM
|
WGd5b3dqQ3NsZ0JkbS83SGJPQyt3cHAzT1F3M0l3bklPMmgvbXBneEEKaTMxUlBS
|
||||||
RFZqWGMKbFQ3b1RwRXBvZllYVEpyeHZTdkFQVTJCaVQ0bmYxRmRtWjlBS2dSRXpn
|
dk1JNC9kMGNVN25UVUdiREZ5VXVlRTFnK3RITUJhRGI4WTVScwotLS0gYmFrRC9z
|
||||||
dwotLS0gN0RpUFZIeGZkQUxZMnN6aHpPcmVqdWpXSlBjTmhHeXJmQjJsK3NtNVZw
|
UmVBZUVEdWozTXEzakVhWTlQczRQTmZNdnVITy94NThacmFxTQqjb3dg++cz4TFP
|
||||||
ZwqUQnen/0lpSkPthpgteS3q0WoNYbUgz4oQo8rGJffyLIezLlwC1s20QboMEr1p
|
ozngALxv/9V3A5ps+lL+p2zGUkiMX6eWS97cX7q9klwUP91yRuhP2MnON5BsFRSj
|
||||||
oPuwJ8nPymQWoSjasydDTqtAl+Ke1cDPx1Qo/FLI1bB4jcFO/ck/g2g5AG+X0SJa
|
DPyt70lKwHON04wT2VwqHGE7oL0/Qmi1U+E4+Eo68lJaKc8f+S9FP5WryQhwTik0
|
||||||
F6UgInwGdoMqThKg3i3eJI/0EjBfHBmp5zjzZbART4Sr54Cb8AaEpKV8S28NJGec
|
HAQA+iafcNvHolXoQrPBsgngpyTHUV9ywg9Df0Da5kSoHSYX+tZ0oUi3R9L7JWs0
|
||||||
+Z00zS/7MnEPKhTSiNrIWNp8dKFqXS2GR6sSoEHhThXUVoXz7VwK6HWLbbWCUgMh
|
Pf64dxntZkuM1i5C8LRnilDuV9Ys2wAQ0jObsUEnV4kpLsMvJbSF6DpQMJdINa7i
|
||||||
LoEYOXBu3AFE8+7bS57MQH2uGx/+7Rt3MhcXXpOz5e8//2NhWla63R8T+I0GHsVk
|
0m250w9V4GSFnVKo7OSYR/BREVUBEuME0rlKlJVdLaC1SrTltPOq67xAdsaneNiV
|
||||||
ObLnyoHu7nE7OuIVdMHD0kZnKBpZy1GE6Zj4JqTRcUtcVXr/SHyz5r3GbvbU37LI
|
1vYZ/O6SsglDQYodtjg1bnGjpybRmVIQ1mWNsIdjb43UFFDlfw47IDyGbwh9bkW6
|
||||||
IjGoP4QukixeqbEIag==
|
ekhDBHca7MRBL5+c+KGZUW8=
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#!/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