diff --git a/ansible/services/authelia/README.md b/ansible/services/authelia/README.md index e6b522f..95aa8df 100644 --- a/ansible/services/authelia/README.md +++ b/ansible/services/authelia/README.md @@ -2,12 +2,49 @@ SSO authentication portal with LLDAP directory and MariaDB backend. -- **Host:** helsinki-a -- **URL:** https://auth.pez.sh (integrated via Caddy forward_auth) +- **Host:** helsinki-a (100.67.6.27) +- **URL:** https://auth.pez.sh / https://auth.pez.solutions - **Components:** - **Authelia** — SSO portal (port 9091, localhost only) - **LLDAP** — Lightweight LDAP directory (port 3890 LDAP, port 17170 web UI) - **MariaDB 11** — Session/config storage -- **Config:** `/root/authelia/config/` -- **Secrets:** `/root/authelia/secrets/` (JWT, session, encryption keys, passwords) +- **Config:** `/root/authelia/config/configuration.yml` +- **Secrets:** `/root/authelia/secrets/` (mounted into containers) - **LDAP base DN:** `dc=pez,dc=sh` + +## Secrets + +All secrets are stored in `config.enc.yml` (SOPS-encrypted with age). + +To decrypt: `sops -d config.enc.yml` + +Secret files expected in `/root/authelia/secrets/` on helsinki-a: + +| File | Source key in config.enc.yml | Used by | +|------|------------------------------|---------| +| `JWT_SECRET` | `jwt_secret` | Authelia (password reset JWT) | +| `SESSION_SECRET` | `session_secret` | Authelia (session encryption) | +| `STORAGE_ENCRYPTION_KEY` | `storage_encryption_key` | Authelia (DB encryption) | +| `MYSQL_PASSWORD` | `mysql_password` | Authelia + MariaDB | +| `MYSQL_ROOT_PASSWORD` | `mysql_root_password` | MariaDB | +| `LLDAP_ADMIN_PASSWORD` | `lldap_admin_password` | LLDAP + Authelia (LDAP bind) | +| `LLDAP_JWT_SECRET` | `lldap_jwt_secret` | LLDAP | +| `SMTP_PASSWORD` | `smtp_password` | Authelia (email notifications) | + +## Access Control + +Default policy: **deny**. Per-service access via LLDAP groups (e.g. `pez_grafana_users`). +Domains covered: `*.pez.sh` and `*.pez.solutions` (mirrors). + +## Deployment + +1. Decrypt secrets: `sops -d config.enc.yml > /tmp/secrets.yml` +2. Write each key as a file to `/root/authelia/secrets/` +3. Copy `configuration.yml` to `/root/authelia/config/` +4. Copy `docker-compose.yml` to `/root/authelia/` +5. `docker compose up -d` + +> **Note:** The current deployment lives at `/root/authelia/` (not `/opt/docker/authelia/`). +> The Ansible `docker_services` role deploys to `/opt/docker/` — if adding authelia +> to `docker_services` in host_vars, the paths in docker-compose.yml or the deploy +> target would need to be reconciled. diff --git a/ansible/services/authelia/config.enc.yml b/ansible/services/authelia/config.enc.yml new file mode 100644 index 0000000..c463255 --- /dev/null +++ b/ansible/services/authelia/config.enc.yml @@ -0,0 +1,34 @@ +#ENC[AES256_GCM,data:f4XAqp11z+JcQ1A2e7rsATxc+tnVCWg/JtBkcLxHcZU=,iv:ZoV32fp3Qqa4NpfCxGIr9aBN58WWGKsPY4ejLAYHpSA=,tag:sGNmx6TLU8c33TKrgq97cg==,type:comment] +#ENC[AES256_GCM,data:EkK1hWr3EBWDTlmOKyLZRnHSNApZIGjik/ZzXcU2toSYu/E7DvXi8w6SXZYXN1tIbhH/r/4QF2PF,iv:0RWjiWaYqfbnTpWreP0Fsa1U4bTMosxSK4DLBxTuu0s=,tag:2UrxDFqaRyfIiHo02xonmA==,type:comment] +#ENC[AES256_GCM,data:CsMgleHImUSz6KC4BEc4M96s1Y+m/hAmNnAL3C/cAwYcnWhj7YDR1n2iHqPYEEWkSwhpoLYGzRJdlpEjyIPEgetSo7utb3su,iv:zoX+VKI1RC2gYpdY92cN5hVc372clLJu/mve8x2atiM=,tag:UG6fi/dyK3Y9nWyvdWA3Ow==,type:comment] +#ENC[AES256_GCM,data:cfPV8c2VcH8rBzzU11bvmYZ8OO3SmIXGO1Tig0SEiO/FjI/dOPmIGNSgTmNUk00XRD5dNtXLeUd1fznodrOanzCWxTiVAI1F,iv:cBGoO9Mr7dYUpqPB1ioIB2sNAdLalcKY7z3jfPG6NKc=,tag:Teit2AbaY+PLus1PIpUwpg==,type:comment] +#ENC[AES256_GCM,data:vAB4tNVW8eh1bYXw60nwDEMQ9DjNayzNCN/R20om795rqyspx0X1MO3I+W7k2LzW/n1xJ+KtksAEZsdhXO4K3CvgPKZxJAY=,iv:NEp+L8uh0Kr5r8Uns/Lo3j/hO6Sd5f7T6b7RvZjcmdM=,tag:hJH+A2UWHUj8EZkwEHN7Aw==,type:comment] +#ENC[AES256_GCM,data:Z1fFA2XXQrPMQNzDZRkxc9gUuWql2W9Tbhg/TMVsHUphrhfSVzH8Am99BQ==,iv:NXIeTN9RiesRo3MKJS5L4rNHCaekD66NXG1DB86OzaE=,tag:TwpWL/BmfmDp+zAqQEa1mg==,type:comment] +jwt_secret: ENC[AES256_GCM,data:L7A61rbtbIHsvhXuvZ/pVTJV0opnbR69IxXCaxi9BttYik0xi3INq2mD82VSw6llVf9QwguXK/hiVoFkZ11rdrNmxXInBwDQeSRPcQ==,iv:XSJEte+tdUNv5046HVRdjJsPiqhFvSzs0NIG6KrONLg=,tag:9yZXEM2gHwz8qP3rzSYg1Q==,type:str] +session_secret: ENC[AES256_GCM,data:geGxpL6UOF6OzmWAt+QP9lGSBEFWuZ5iKEvsk6N8U79p2/sc2dTNyrbovM6nSDaFsL0C5aSdVdbJIW1eplyk8OQFrf19Z8B3lzTvzA==,iv:VVxm2VXX82U4HFtJs/gOVQYd8+q/eFRoP5ZcHssKgvs=,tag:MB10TdI6tSYNfVl0qe+6Sw==,type:str] +storage_encryption_key: ENC[AES256_GCM,data:JvNDndLFS+kgnv5p5bDQXNSwyXdBJQu7+UI5kEu3U9NCg1RJfTY9tNnHeOZ43Ijy8SK56JndzEG7yH/pRqUawVwRxvvTNGK1kGJmCg==,iv:a69EyxwABrKyXfEHPx3bZ9YxDzk50L+xy6WCb93vG4M=,tag:yXwVyh5Vh67WtuNzXgtYJg==,type:str] +mysql_password: ENC[AES256_GCM,data:gYdzDcN1nzxGLKm5kVTCxDwGiSNk3OYG/0p7yyCgqxyRZ60zGwYx65BJRlg=,iv:XioHoIF1w1+mUnYQpBi/YlIVp/wv2ESWg/TBUgWS4XA=,tag:qtjJq/Qxk02JqmGcXIsgIw==,type:str] +mysql_root_password: ENC[AES256_GCM,data:JZ7N/EA3loQ1L1Cu5JwTANkHuuItBf3UpkEa9ZEJkJmqArtjAwmjJm1tpLc=,iv:Xmqyg2iT2n1g/8yKORWg1e/W7xGlbCCBkvgP6LSkTSM=,tag:PQIsjOf87Y0WdOIc+ToT9A==,type:str] +lldap_admin_password: ENC[AES256_GCM,data:xcSWQzn3YuKYGylXAApzC1C7jGzn+MXV1f6fveQGpLVSKqkRK64cJqHfX1k=,iv:8Y8BIaETeOpYn4yRx8mILfR7h32mjlZqu0b0VbcKxXg=,tag:OqHtG648vAhWaI1p54KB6A==,type:str] +lldap_jwt_secret: ENC[AES256_GCM,data:2LOlBwoj8IeZZhRUKs+4BIN46cDJYVLi3a3nJj7tY81RK40cEBrRdSP9Aak=,iv:QKQl3wkg9nli0wtemOxOido1EvozUjiXCDtdnKaDllw=,tag:htQHhRCkT9TguP6vizwEbA==,type:str] +smtp_password: ENC[AES256_GCM,data:JLpSehtbZDwo,iv:fNQViKgcm399HrH51QbOWZld4vAfFxlMbq2PFd9sBD8=,tag:KJ29U5QopPThw/iqUCNYvQ==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1r8uh2w2qad2z5sgq9q7l73962q2sp8zz9hdnh6sjuvanxl565vmswn8squ + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJWndmK2Y1L05IQURqbFJl + c1ZpNFpiakEvUldzL2RxckZ1YXdsbExwTFJnCk54eFRJTEw0V1I1QWRyZFBHU3Z2 + ekZUcCtaWVFZdVRoOGlTb3R0bTB4bmcKLS0tIGZyNFNOb01wRk5xbW1YanFZSlUr + TXE0dXNFV2tDd041TUpKblFUWmpvT0kKXCoy2S2gkB1329x9vYVq5xh+j8hc+daL + oMt05DKN7v3uMe8ScFnXGdoAq72HbVQRZE+46fTl02JsNH1787/6rw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-03-28T17:39:56Z" + mac: ENC[AES256_GCM,data:1g6jER1nu+DlDIXL89Y8nm5sat/Ig20wdcfWEr4QzPpos+HOMtGLlFC98lZWLMd63wkBpP9U+lbj7Rx66AJ7lJIjaiTXcNRR9awjLu0/9voNOmzZ0aaa120xH3KnYMI7aHBBfBUtUQzcHa/1vRhS5zLVMzDBEEK9qdsxvQWEGc0=,iv:14WPvCdQXBBmq6OSLBNrHf/v9h8KOp0/4MFYLy3PZQQ=,tag:50+ZhmZIOmN99MR3Q+/NDQ==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.4 diff --git a/ansible/services/authelia/config.enc.yml.example b/ansible/services/authelia/config.enc.yml.example deleted file mode 100644 index b759e95..0000000 --- a/ansible/services/authelia/config.enc.yml.example +++ /dev/null @@ -1,10 +0,0 @@ -# Example: services/authelia/config.enc.yml -# Authelia secrets — encrypt with: sops -e -i config.enc.yml ---- -jwt_secret: CHANGEME -session_secret: CHANGEME -storage_encryption_key: CHANGEME -lldap_admin_password: CHANGEME -mariadb_root_password: CHANGEME -mariadb_authelia_password: CHANGEME -oidc_hmac_secret: CHANGEME diff --git a/ansible/services/authelia/configuration.yml b/ansible/services/authelia/configuration.yml new file mode 100644 index 0000000..a1892d2 --- /dev/null +++ b/ansible/services/authelia/configuration.yml @@ -0,0 +1,151 @@ +--- +############################################################################### +## Authelia Configuration — pez.sh ## +############################################################################### +# Host: helsinki-a (100.67.6.27) +# URL: https://auth.pez.sh +# +# Secrets are mounted via Docker environment variables pointing to /secrets/. +# The LDAP bind password and SMTP password are referenced from the same +# secrets directory. See config.enc.yml for encrypted values. +# +# This file is deployed to /root/authelia/config/configuration.yml + +server: + address: 'tcp://:9091/' + +log: + level: 'info' + format: 'text' + file_path: '/config/authelia.log' + keep_stdout: true + +identity_validation: + reset_password: + +## +## Authentication Backend — LLDAP +## +authentication_backend: + ldap: + address: 'ldap://lldap:3890' + implementation: 'lldap' + timeout: '20 seconds' + start_tls: false + base_dn: 'dc=pez,dc=sh' + additional_users_dn: 'ou=people' + additional_groups_dn: 'ou=groups' + user: 'cn=admin,ou=people,dc=pez,dc=sh' + # Password provided via AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE env var + +## +## Access Control — default deny, per-service groups +## +access_control: + default_policy: 'deny' + rules: + # pez.sh domains + - domain: 'grafana.pez.sh' + subject: 'group:pez_grafana_users' + policy: 'one_factor' + - domain: 'prometheus.pez.sh' + subject: 'group:pez_prometheus_users' + policy: 'one_factor' + - domain: 'radarr.pez.sh' + subject: 'group:pez_radarr_users' + policy: 'one_factor' + - domain: 'sonarr.pez.sh' + subject: 'group:pez_sonarr_users' + policy: 'one_factor' + - domain: 'lidarr.pez.sh' + subject: 'group:pez_lidarr_users' + policy: 'one_factor' + - domain: 'readarr.pez.sh' + subject: 'group:pez_readarr_users' + policy: 'one_factor' + - domain: 'download.pez.sh' + subject: 'group:pez_download_users' + policy: 'one_factor' + - domain: 'rss.pez.sh' + subject: 'group:pez_rss_users' + policy: 'one_factor' + - domain: 'soulseek.pez.sh' + subject: 'group:pez_soulseek_users' + policy: 'one_factor' + - domain: 'prowlarr.pez.sh' + subject: 'group:pez_prowlarr_users' + policy: 'one_factor' + - domain: 'git.pez.sh' + subject: 'group:pez_git_users' + policy: 'one_factor' + + # pez.solutions domains (mirrors) + - domain: 'grafana.pez.solutions' + subject: 'group:pez_grafana_users' + policy: 'one_factor' + - domain: 'prometheus.pez.solutions' + subject: 'group:pez_prometheus_users' + policy: 'one_factor' + - domain: 'radarr.pez.solutions' + subject: 'group:pez_radarr_users' + policy: 'one_factor' + - domain: 'sonarr.pez.solutions' + subject: 'group:pez_sonarr_users' + policy: 'one_factor' + - domain: 'lidarr.pez.solutions' + subject: 'group:pez_lidarr_users' + policy: 'one_factor' + - domain: 'readarr.pez.solutions' + subject: 'group:pez_readarr_users' + policy: 'one_factor' + - domain: 'download.pez.solutions' + subject: 'group:pez_download_users' + policy: 'one_factor' + - domain: 'soulseek.pez.solutions' + subject: 'group:pez_soulseek_users' + policy: 'one_factor' + - domain: 'prowlarr.pez.solutions' + subject: 'group:pez_prowlarr_users' + policy: 'one_factor' + + # Shared apps portals + - domain: 'apps.pez.sh' + subject: 'group:pez_plebs' + policy: 'one_factor' + - domain: 'apps.pez.solutions' + subject: 'group:pez_plebs' + policy: 'one_factor' + +## +## Session — cookie domains +## +session: + cookies: + - domain: 'pez.sh' + authelia_url: 'https://auth.pez.sh' + - domain: 'pez.solutions' + authelia_url: 'https://auth.pez.solutions' + +## +## Storage — MariaDB +## +storage: + mysql: + address: 'tcp://mariadb:3306' + database: 'authelia' + username: 'authelia' + timeout: '10 seconds' + # Password provided via AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE env var + +## +## Notifier — SMTP via poste.io on nuremberg-a +## +notifier: + disable_startup_check: true + smtp: + address: 'smtp://mail.pez.sh' + username: 'pez' + # Password provided via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE env var + sender: 'Authelia ' + tls: + server_name: 'mail.pez.sh' diff --git a/ansible/services/authelia/docker-compose.yml b/ansible/services/authelia/docker-compose.yml index 4ed6fac..36a261e 100644 --- a/ansible/services/authelia/docker-compose.yml +++ b/ansible/services/authelia/docker-compose.yml @@ -16,6 +16,8 @@ services: AUTHELIA_SESSION_SECRET_FILE: /secrets/SESSION_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/STORAGE_ENCRYPTION_KEY AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE: /secrets/MYSQL_PASSWORD + AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE: /secrets/LLDAP_ADMIN_PASSWORD + AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE: /secrets/SMTP_PASSWORD TZ: UTC volumes: - /root/authelia/config:/config