feat: добавить аддон authelia — SSO forward-auth и OIDC provider

Helm chart + Ansible role для Authelia 4.38:
- Forward-auth для ingress-nginx через аннотации auth-url/auth-signin
- OIDC provider: Gitea, Grafana, ArgoCD, MinIO, Vault, Nextcloud
- SQLite default или PostgreSQL; опциональный Redis для сессий
- RSA ключ OIDC генерируется автоматически если не задан в vault
- ConfigMap authelia-forward-auth с готовыми аннотациями для любого сервиса
- README: install, users, protect service, OIDC per-service, debug, test
This commit is contained in:
Sergey Antropoff
2026-04-26 18:18:46 +03:00
parent f3dfe87d03
commit 225f77598a
23 changed files with 1949 additions and 1 deletions

494
addons/authelia/README.md Normal file
View File

@@ -0,0 +1,494 @@
# authelia
Self-hosted authentication system providing **forward-auth** for ingress-nginx and an **OIDC provider** for Gitea, Grafana, ArgoCD, MinIO, Vault, and Nextcloud.
## Architecture
```
User → ingress-nginx → (auth-url) → Authelia :9091 → allowed/denied
Authelia portal
auth.home.local
users_database.yml
(argon2id passwords)
OIDC flow:
Service → Authelia /api/oidc/authorization → login → token → Service
```
```
Traffic routes:
auth.home.local → authelia ClusterIP :9091 (Ingress, NO forward-auth)
sonarr.home.local → sonarr service (Ingress + forward-auth annotations)
gitea.home.local → gitea service (Ingress, no forward-auth — OIDC handles it)
Kubernetes objects:
Deployment: authelia
Service: authelia (ClusterIP :9091)
Secrets: authelia-secrets (jwt, session, storage_encryption, oidc keys)
authelia-config (configuration.yml — contains OIDC client secrets)
authelia-users (users_database.yml)
PVC: authelia-data (SQLite db + notification.txt)
Ingress: authelia (auth.home.local)
ConfigMap: authelia-forward-auth (copy-paste annotation reference)
Deployment: authelia-redis (optional, redis.enabled=true)
```
---
## 1. Installation
### Step 1 — Generate secrets
Run these commands and save the output:
```bash
# Core secrets
openssl rand -base64 64 # → authelia_jwt_secret
openssl rand -base64 64 # → authelia_session_secret
openssl rand -base64 32 # → authelia_storage_encryption_key
openssl rand -base64 48 # → authelia_oidc_hmac_secret
# OIDC client secrets (one per enabled client)
openssl rand -hex 32 # → authelia_oidc_secret_gitea
openssl rand -hex 32 # → authelia_oidc_secret_grafana
```
> The **OIDC RSA private key** is auto-generated during deploy. Leave `authelia_oidc_private_key: ""` in vault. After first deploy, retrieve and save it (see note at end of deploy output).
### Step 2 — Generate admin password hash
```bash
docker run --rm authelia/authelia:latest authelia hash-password 'your-password'
# Output: $argon2id$v=19$m=65536,t=3,p=4$...
```
Copy the full `$argon2id$...` string.
### Step 3 — Edit vault.yml
```yaml
# group_vars/all/vault.yml (ansible-vault encrypted)
authelia_jwt_secret: "<output of openssl rand -base64 64>"
authelia_session_secret: "<output of openssl rand -base64 64>"
authelia_storage_encryption_key: "<output of openssl rand -base64 32>"
authelia_oidc_hmac_secret: "<output of openssl rand -base64 48>"
authelia_oidc_private_key: "" # auto-generated on first deploy
authelia_oidc_secret_gitea: "<output of openssl rand -hex 32>"
authelia_oidc_secret_grafana: "<output of openssl rand -hex 32>"
authelia_user_admin_password_hash: "$argon2id$v=19$m=65536,t=3,p=4$..."
```
### Step 4 — Configure addons.yml
```yaml
# group_vars/all/addons.yml
addon_authelia: true
authelia_host: "auth.home.local"
authelia_domain: "home.local"
# OIDC clients to enable
authelia_oidc_gitea_enabled: true
authelia_oidc_grafana_enabled: true
# Domains to protect
authelia_protected_domains:
- sonarr.home.local
- radarr.home.local
- lidarr.home.local
- prowlarr.home.local
- pgadmin.home.local
# Domains requiring admin group
authelia_admin_domains:
- argocd.home.local
- vault.home.local
# Public bypass (no auth)
authelia_bypass_domains:
- plex.home.local
```
### Step 5 — Deploy
```bash
make addon-authelia
```
### Step 6 — Add DNS record
Add `auth.home.local` pointing to the kube-vip/ingress-nginx IP in Technitium DNS (or your DNS server).
---
## 2. Managing users
### Add a new user
1. Generate password hash:
```bash
docker run --rm authelia/authelia:latest authelia hash-password 'newpassword'
```
2. Add to `group_vars/all/addons.yml`:
```yaml
authelia_users:
admin:
displayname: "Administrator"
email: "admin@home.local"
groups: [admins, users]
alice:
displayname: "Alice"
email: "alice@home.local"
groups: [users]
```
3. Add to `vault.yml`:
```yaml
authelia_user_alice_password_hash: "$argon2id$..."
```
4. Redeploy: `make addon-authelia`
### Groups
- `admins` — access to `authelia_admin_domains` (ArgoCD, Vault, Harbor, Dashboard)
- `users` — access to `authelia_protected_domains` (Sonarr, Radarr, etc.)
OIDC claims include the `groups` scope, so Grafana/Gitea can use group-based role mapping.
---
## 3. Protect a new service with forward-auth
### Step 1 — Add annotations to the service's Ingress
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myservice
namespace: myservice
annotations:
kubernetes.io/ingress.class: nginx
# ── Authelia forward-auth ──────────────────────────────────────────────
nginx.ingress.kubernetes.io/auth-url: "http://authelia.authelia.svc.cluster.local:9091/api/authz/forward-auth"
nginx.ingress.kubernetes.io/auth-signin: "http://auth.home.local/?rd=$scheme://$host$escaped_request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "Remote-User,Remote-Name,Remote-Groups,Remote-Email"
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Method $request_method;
spec:
rules:
- host: myservice.home.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myservice
port:
number: 8080
```
> Get the exact URLs for your deployment:
> ```bash
> kubectl get cm authelia-forward-auth -n authelia -o jsonpath='{.data.annotations\.yaml}'
> ```
### Step 2 — Add to access control
Add `myservice.home.local` to the appropriate list in `addons.yml`:
```yaml
authelia_protected_domains:
- myservice.home.local
# ... existing entries
```
Then redeploy: `make addon-authelia`
---
## 4. OIDC client configuration per service
### Gitea
In Gitea Admin → Site Administration → Authentication Sources → Add OAuth2:
| Field | Value |
|-------|-------|
| Name | `Authelia` |
| OAuth2 Provider | `OpenID Connect` |
| Client ID | `gitea` |
| Client Secret | value of `authelia_oidc_secret_gitea` in vault |
| OpenID Connect Auto Discovery URL | `http://auth.home.local/.well-known/openid-configuration` |
| Scopes | `openid profile email groups` |
Restart Gitea. Users can now click "Sign in with Authelia" on the login page.
Optional — auto-create users and map admin group:
```ini
# app.ini
[oauth2]
USERNAME = preferred_username
UPDATE_AVATAR = true
[openid]
ENABLE_OPENID_SIGNIN = true
```
---
### Grafana
Add to Grafana's `grafana.ini` (or Helm values):
```ini
[auth.generic_oauth]
enabled = true
name = Authelia
client_id = grafana
client_secret = <authelia_oidc_secret_grafana from vault>
scopes = openid profile email groups
auth_url = http://auth.home.local/api/oidc/authorization
token_url = http://auth.home.local/api/oidc/token
api_url = http://auth.home.local/api/oidc/userinfo
# Role mapping via Authelia groups
role_attribute_path = contains(groups[*], 'admins') && 'GrafanaAdmin' || 'Viewer'
allow_sign_up = true
```
Or in Helm values for the prometheus-stack addon:
```yaml
# group_vars/all/addons.yml
prometheus_grafana_oauth_enabled: true
prometheus_grafana_oauth_client_id: "grafana"
prometheus_grafana_oauth_client_secret: "..." # from vault
prometheus_grafana_oauth_auth_url: "http://auth.home.local/api/oidc/authorization"
prometheus_grafana_oauth_token_url: "http://auth.home.local/api/oidc/token"
prometheus_grafana_oauth_api_url: "http://auth.home.local/api/oidc/userinfo"
```
---
### ArgoCD
Enable ArgoCD OIDC in `argocd-cm` ConfigMap:
```yaml
# values override for argocd Helm chart
server:
config:
oidc.config: |
name: Authelia
issuer: http://auth.home.local
clientID: argocd
clientSecret: $oidc.authelia.clientSecret
requestedScopes:
- openid
- profile
- email
- groups
requestedIDTokenClaims:
groups:
essential: true
```
Map Authelia groups to ArgoCD RBAC:
```yaml
# argocd-rbac-cm
policy.csv: |
g, admins, role:admin
policy.default: role:readonly
```
Store the secret in a K8s Secret `argocd-secret`:
```yaml
data:
oidc.authelia.clientSecret: <base64 of authelia_oidc_secret_argocd>
```
---
### MinIO
In MinIO Console → Identity → OpenID:
| Field | Value |
|-------|-------|
| Config URL | `http://auth.home.local/.well-known/openid-configuration` |
| Client ID | `minio` |
| Client Secret | value of `authelia_oidc_secret_minio` |
| Claim Name | `groups` |
| Scopes | `openid,profile,email` |
Map Authelia group `admins` to MinIO policy `consoleAdmin`.
---
### Vault
Configure Vault OIDC auth method:
```bash
vault auth enable oidc
vault write auth/oidc/config \
oidc_discovery_url="http://auth.home.local" \
oidc_client_id="vault" \
oidc_client_secret="<authelia_oidc_secret_vault>" \
default_role="reader"
vault write auth/oidc/role/reader \
bound_audiences="vault" \
allowed_redirect_uris="https://vault.home.local/ui/vault/auth/oidc/oidc/callback" \
allowed_redirect_uris="https://vault.home.local/oidc/callback" \
user_claim="sub" \
groups_claim="groups" \
token_policies="default"
```
---
## 5. Debugging auth issues
### Check Authelia logs (real-time)
```bash
kubectl -n authelia logs -l app.kubernetes.io/name=authelia -f --tail=100
```
Common log messages:
- `"ALLOW"` — request was allowed
- `"DENY"` — request was denied (check domain in `protectedDomains`)
- `"Redirecting"` — unauthenticated user redirected to login
- `"POST /api/firstfactor"` — login attempt
### Verify forward-auth endpoint is reachable
From within the cluster:
```bash
kubectl run curl-test --rm -it --image=curlimages/curl -- \
curl -v http://authelia.authelia.svc.cluster.local:9091/api/health
# Expected: {"status":"OK"}
```
### Test that a domain's annotations are applied
```bash
kubectl get ingress sonarr -n mediaserver -o yaml | grep auth-url
```
### Check current access control configuration
```bash
kubectl -n authelia exec deploy/authelia -- \
cat /config/configuration.yml | grep -A 30 "access_control:"
```
### Check active sessions (SQLite)
```bash
kubectl -n authelia exec deploy/authelia -- \
sqlite3 /data/db.sqlite3 "SELECT subject, ip, last_activity FROM user_opaque_identifier LIMIT 20;"
```
### Notification log (if SMTP disabled)
```bash
kubectl -n authelia exec deploy/authelia -- cat /data/notification.txt
```
---
## 6. Test the login flow
### Forward-auth flow (e.g., Sonarr)
1. Open `http://sonarr.home.local/` in a private browser window
2. Should redirect to `http://auth.home.local/?rd=http%3A%2F%2Fsonarr.home.local%2F`
3. Enter credentials → should redirect back to Sonarr
4. Access granted
Test from command line:
```bash
# Step 1: login request (expect redirect to Authelia)
curl -I http://sonarr.home.local/
# Expected: 302 to auth.home.local
# Step 2: verify endpoint directly
curl -I -H "X-Forwarded-Host: sonarr.home.local" \
-H "X-Forwarded-URI: /" \
-H "X-Forwarded-Proto: http" \
http://authelia.authelia.svc.cluster.local:9091/api/authz/forward-auth
# Expected: 401 (unauthenticated) or 200 (if session cookie provided)
```
### OIDC flow (e.g., Gitea)
1. Open `http://gitea.home.local/user/oauth2/Authelia`
(or click "Sign in with Authelia" on Gitea login page)
2. Should redirect to Authelia login form
3. Login with admin credentials
4. Authelia redirects back to Gitea with authorization code
5. Gitea exchanges code for token — user is logged in
### Check OIDC discovery endpoint
```bash
curl -s http://auth.home.local/.well-known/openid-configuration | jq .
# Should return JSON with issuer, authorization_endpoint, token_endpoint, etc.
```
---
## Variables reference
| Variable | Default | Description |
|----------|---------|-------------|
| `authelia_host` | `auth.home.local` | Portal hostname |
| `authelia_domain` | `home.local` | Session cookie domain |
| `authelia_theme` | `dark` | UI theme |
| `authelia_two_factor_enabled` | `false` | Require TOTP/WebAuthn |
| `authelia_storage_type` | `sqlite` | `sqlite` or `postgresql` |
| `authelia_redis_enabled` | `false` | Built-in Redis for sessions |
| `authelia_smtp_enabled` | `false` | SMTP for 2FA/password-reset emails |
| `authelia_oidc_enabled` | `true` | Enable OIDC provider |
| `authelia_oidc_gitea_enabled` | `true` | Gitea OIDC client |
| `authelia_oidc_grafana_enabled` | `true` | Grafana OIDC client |
| `authelia_oidc_argocd_enabled` | `false` | ArgoCD OIDC client |
| `authelia_oidc_minio_enabled` | `false` | MinIO OIDC client |
| `authelia_oidc_vault_enabled` | `false` | Vault OIDC client |
| `authelia_ingress_tls_enabled` | `false` | TLS on auth portal |
| `authelia_protected_domains` | `[sonarr, radarr…]` | Domains requiring login |
| `authelia_admin_domains` | `[argocd, vault…]` | Admin-only domains |
| `authelia_bypass_domains` | `[]` | Public bypass domains |
| `authelia_oidc_domains` | `[gitea, grafana, minio]` | OIDC bypass (forward-auth off) |
### Vault secrets required
| Variable | Notes |
|----------|-------|
| `authelia_jwt_secret` | min 64 chars — `openssl rand -base64 64` |
| `authelia_session_secret` | min 64 chars |
| `authelia_storage_encryption_key` | min 20 chars — `openssl rand -base64 32` |
| `authelia_oidc_hmac_secret` | min 32 chars — `openssl rand -base64 48` |
| `authelia_oidc_private_key` | RSA PEM — leave empty, auto-generated |
| `authelia_oidc_secret_gitea` | `openssl rand -hex 32` |
| `authelia_oidc_secret_grafana` | `openssl rand -hex 32` |
| `authelia_user_admin_password_hash` | argon2id hash from `authelia hash-password` |
---
## Saving the auto-generated OIDC private key
After the first deploy, save the key to vault for reproducibility:
```bash
kubectl -n authelia get secret authelia-secrets \
-o jsonpath='{.data.oidc_private_key}' | base64 -d
```
Paste the PEM output into `vault.yml` as `authelia_oidc_private_key: |` (multiline YAML).

View File

@@ -0,0 +1,7 @@
---
- name: Install Authelia SSO
hosts: k3s_master[0]
gather_facts: false
become: true
roles:
- role: "{{ playbook_dir }}/role"

View File

@@ -0,0 +1,21 @@
apiVersion: v2
name: authelia
description: |
Self-hosted authentication portal with forward-auth for ingress-nginx
and OIDC provider for Gitea, Grafana, ArgoCD, MinIO, Vault, Nextcloud.
Supports file-based users, SQLite/PostgreSQL storage, optional Redis sessions.
type: application
version: 1.0.0
appVersion: "4.38.17"
keywords:
- authentication
- sso
- oidc
- forward-auth
- 2fa
home: https://www.authelia.com
sources:
- https://github.com/authelia/authelia
- https://git.antropoff.ru/DevOpsTools/K3S
maintainers:
- name: k3s-ansible

View File

@@ -0,0 +1,203 @@
---
# Authelia configuration — rendered by Helm via tpl()
# Secrets (jwt, session, storage_encryption, oidc_hmac, oidc_private_key)
# are injected via AUTHELIA_*_FILE environment variables — not in this file.
server:
host: 0.0.0.0
port: 9091
path: ""
buffers:
read: 4096
write: 4096
timeouts:
read: 6s
write: 6s
idle: 30s
log:
level: info
format: text
theme: {{ .Values.theme | quote }}
totp:
disable: false
issuer: {{ .Values.totp.issuer | quote }}
algorithm: sha1
digits: 6
period: {{ .Values.totp.period }}
skew: {{ .Values.totp.skew }}
secret_size: 32
webauthn:
disable: false
display_name: {{ .Values.domain | quote }}
attestation_conveyance_preference: indirect
user_verification: preferred
timeout: 60s
authentication_backend:
password_reset:
disable: false
refresh_interval: 5m
file:
path: /config/users_database.yml
watch: false
password:
algorithm: argon2id
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
session:
name: {{ .Values.session.name | quote }}
domain: {{ .Values.session.domain | quote }}
same_site: {{ .Values.session.sameSite | quote }}
expiration: {{ .Values.session.expiration | quote }}
inactivity: {{ .Values.session.inactivity | quote }}
remember_me_duration: {{ .Values.session.rememberMeDuration | quote }}
{{- if .Values.redis.enabled }}
redis:
host: {{ printf "%s-redis" (include "authelia.name" .) | quote }}
port: 6379
{{- end }}
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
storage:
{{- if eq .Values.storage.type "postgresql" }}
postgres:
host: {{ .Values.storage.postgresql.host | quote }}
port: {{ .Values.storage.postgresql.port }}
database: {{ .Values.storage.postgresql.database | quote }}
schema: {{ .Values.storage.postgresql.schema | quote }}
username: {{ .Values.storage.postgresql.username | quote }}
tls:
skip_verify: true
{{- else }}
local:
path: {{ .Values.storage.sqlite.path | quote }}
{{- end }}
notifier:
disable_startup_check: true
{{- if .Values.notifier.smtp.enabled }}
smtp:
host: {{ .Values.notifier.smtp.host | quote }}
port: {{ .Values.notifier.smtp.port }}
username: {{ .Values.notifier.smtp.username | quote }}
sender: {{ .Values.notifier.smtp.sender | quote }}
tls:
skip_verify: {{ .Values.notifier.smtp.tls.skipVerify }}
{{- else }}
filesystem:
filename: /data/notification.txt
{{- end }}
access_control:
default_policy: {{ .Values.accessControl.defaultPolicy | quote }}
rules:
# Authelia portal — always bypass (prevents auth loop)
- domain: {{ .Values.authHost | quote }}
policy: bypass
# Health check endpoints — bypass for monitoring
- domain: "*.{{ .Values.domain }}"
resources:
- "^/healthz(.*)$"
- "^/api/healthz(.*)$"
- "^/health$"
policy: bypass
{{- if .Values.accessControl.bypassDomains }}
# Public services — no authentication required
- domain:
{{- range .Values.accessControl.bypassDomains }}
- {{ . | quote }}
{{- end }}
policy: bypass
{{- end }}
{{- if .Values.accessControl.oidcDomains }}
# OIDC-enabled services — bypass forward-auth (OIDC handles authentication)
- domain:
{{- range .Values.accessControl.oidcDomains }}
- {{ . | quote }}
{{- end }}
policy: bypass
{{- end }}
{{- if .Values.accessControl.adminDomains }}
# Admin-only services — require 'admins' group
- domain:
{{- range .Values.accessControl.adminDomains }}
- {{ . | quote }}
{{- end }}
subject:
- "group:admins"
policy: {{ if .Values.twoFactor.enabled }}two_factor{{ else }}one_factor{{ end }}
{{- end }}
{{- if .Values.accessControl.protectedDomains }}
# Protected services — login required
- domain:
{{- range .Values.accessControl.protectedDomains }}
- {{ . | quote }}
{{- end }}
policy: {{ if .Values.twoFactor.enabled }}two_factor{{ else }}one_factor{{ end }}
{{- end }}
{{- if .Values.oidc.enabled }}
identity_providers:
oidc:
access_token_lifespan: {{ .Values.oidc.accessTokenLifespan | quote }}
authorize_code_lifespan: {{ .Values.oidc.authorizeCodeLifespan | quote }}
id_token_lifespan: {{ .Values.oidc.idTokenLifespan | quote }}
refresh_token_lifespan: {{ .Values.oidc.refreshTokenLifespan | quote }}
enable_client_debug_messages: false
minimum_parameter_entropy: 8
cors:
endpoints:
- authorization
- token
- revocation
- introspection
allowed_origins_from_client_redirect_uris: true
clients:
{{- range $name, $client := .Values.oidc.clients }}
{{- if $client.enabled }}
- id: {{ $client.id | quote }}
description: {{ $client.description | default $name | quote }}
# $plaintext$ prefix — Authelia 4.38+ plain-text client secret marker
secret: {{ printf "$plaintext$%s" $client.secret | quote }}
public: false
authorization_policy: one_factor
scopes:
{{- range $client.scopes }}
- {{ . | quote }}
{{- end }}
redirect_uris:
{{- range $client.redirectUris }}
- {{ . | quote }}
{{- end }}
grant_types:
{{- range $client.grantTypes }}
- {{ . | quote }}
{{- end }}
response_types:
- code
response_modes:
- form_post
- query
- fragment
userinfo_signing_algorithm: none
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,56 @@
╔══════════════════════════════════════════════════════════════╗
║ Authelia SSO — Deployed ║
╚══════════════════════════════════════════════════════════════╝
Portal: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.authHost }}/
Namespace: {{ .Release.Namespace }}
OIDC: {{ if .Values.oidc.enabled }}enabled{{ else }}disabled{{ end }}
Redis: {{ if .Values.redis.enabled }}enabled{{ else }}disabled (memory sessions){{ end }}
Storage: {{ .Values.storage.type }}
─── Protect a new service ──────────────────────────────────────
Add to its Ingress:
nginx.ingress.kubernetes.io/auth-url: "http://{{ include "authelia.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:9091/api/authz/forward-auth"
nginx.ingress.kubernetes.io/auth-signin: "http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.authHost }}/?rd=$scheme://$host$escaped_request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "Remote-User,Remote-Name,Remote-Groups,Remote-Email"
nginx.ingress.kubernetes.io/auth-snippet: "proxy_set_header X-Forwarded-Method $request_method;"
Or get the full reference:
kubectl get cm {{ include "authelia.name" . }}-forward-auth -n {{ .Release.Namespace }} -o jsonpath='{.data.annotations\.yaml}'
─── OIDC Issuer ────────────────────────────────────────────────
http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.authHost }}
Discovery: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.authHost }}/.well-known/openid-configuration
─── Logs / Debug ───────────────────────────────────────────────
kubectl -n {{ .Release.Namespace }} logs -l app.kubernetes.io/name={{ include "authelia.name" . }} -f
─── First login ────────────────────────────────────────────────
Open: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.authHost }}/
User: admin (or as configured in authelia_users)
Pass: the plaintext password whose hash you set in vault.yml
─── Access control rules ────────────────────────────────────────
{{- if .Values.accessControl.protectedDomains }}
Protected (login required):
{{- range .Values.accessControl.protectedDomains }}
- {{ . }}
{{- end }}
{{- end }}
{{- if .Values.accessControl.adminDomains }}
Admin-only (group: admins):
{{- range .Values.accessControl.adminDomains }}
- {{ . }}
{{- end }}
{{- end }}
{{- if .Values.accessControl.bypassDomains }}
Bypass (public):
{{- range .Values.accessControl.bypassDomains }}
- {{ . }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,37 @@
{{- define "authelia.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "authelia.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "authelia.labels" -}}
helm.sh/chart: {{ include "authelia.chart" . }}
{{ include "authelia.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "authelia.selectorLabels" -}}
app.kubernetes.io/name: {{ include "authelia.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Forward-auth URL for use in ingress-nginx annotations.
Returns the full URL to Authelia's authz endpoint inside the cluster.
*/}}
{{- define "authelia.forwardAuthUrl" -}}
{{- printf "http://%s.%s.svc.cluster.local:9091/api/authz/forward-auth" (include "authelia.name" .) .Release.Namespace }}
{{- end }}
{{/*
Sign-in URL for redirecting unauthenticated users.
*/}}
{{- define "authelia.signinUrl" -}}
{{- if .Values.ingress.tls.enabled -}}
{{- printf "https://%s/?rd=$scheme://$host$escaped_request_uri" .Values.authHost }}
{{- else -}}
{{- printf "http://%s/?rd=$scheme://$host$escaped_request_uri" .Values.authHost }}
{{- end -}}
{{- end }}

View File

@@ -0,0 +1,43 @@
---
# Reference ConfigMap: forward-auth annotations for ingress-nginx.
# Copy-paste these annotations onto any Ingress you want to protect with Authelia.
# Usage: kubectl get cm authelia-forward-auth -n {{ .Release.Namespace }} -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "authelia.name" . }}-forward-auth
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
data:
# ── Paste these onto protected Ingress resources ─────────────────────────────
annotations.yaml: |
# Required on EVERY protected Ingress:
nginx.ingress.kubernetes.io/auth-url: "{{ include "authelia.forwardAuthUrl" . }}"
nginx.ingress.kubernetes.io/auth-signin: "{{ include "authelia.signinUrl" . }}"
nginx.ingress.kubernetes.io/auth-response-headers: "Remote-User,Remote-Name,Remote-Groups,Remote-Email"
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Method $request_method;
# ── OIDC Issuer URL (for configuring OIDC clients) ────────────────────────────
oidc-issuer: |
{{ if .Values.ingress.tls.enabled }}https{{ else }}http{{ end }}://{{ .Values.authHost }}
# ── OIDC discovery endpoint ───────────────────────────────────────────────────
oidc-discovery: |
{{ if .Values.ingress.tls.enabled }}https{{ else }}http{{ end }}://{{ .Values.authHost }}/.well-known/openid-configuration
# ── Quick reference: protect a new service ────────────────────────────────────
howto: |
To protect myservice.home.local:
1. Add these annotations to the Service's Ingress:
nginx.ingress.kubernetes.io/auth-url: "{{ include "authelia.forwardAuthUrl" . }}"
nginx.ingress.kubernetes.io/auth-signin: "{{ include "authelia.signinUrl" . }}"
nginx.ingress.kubernetes.io/auth-response-headers: "Remote-User,Remote-Name,Remote-Groups,Remote-Email"
2. Add the domain to accessControl.protectedDomains in addons.yml and re-run:
make addon-authelia
3. Add to Technitium DNS (or /etc/hosts):
<kube-vip-IP> myservice.home.local

View File

@@ -0,0 +1,115 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "authelia.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
spec:
replicas: 1
# Recreate required for ReadWriteOnce PVC (SQLite)
strategy:
type: Recreate
selector:
matchLabels:
{{- include "authelia.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "authelia.selectorLabels" . | nindent 8 }}
annotations:
# Force pod restart when config/users secrets change
checksum/config: {{ tpl (.Files.Get "files/configuration.yml.tpl") . | sha256sum }}
checksum/secrets: {{ .Values.secrets | toJson | sha256sum }}
checksum/users: {{ .Values.users | toJson | sha256sum }}
spec:
securityContext:
runAsNonRoot: true
runAsUser: 8000
runAsGroup: 8000
fsGroup: 8000
containers:
- name: authelia
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- authelia
- --config=/config/configuration.yml
ports:
- name: http
containerPort: 9091
protocol: TCP
env:
# Core secrets — read from mounted files
- name: AUTHELIA_JWT_SECRET_FILE
value: /secrets/jwt_secret
- name: AUTHELIA_SESSION_SECRET_FILE
value: /secrets/session_secret
- name: AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE
value: /secrets/storage_encryption_key
{{- if .Values.oidc.enabled }}
- name: AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE
value: /secrets/oidc_hmac_secret
- name: AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE
value: /secrets/oidc_private_key
{{- end }}
{{- if eq .Values.storage.type "postgresql" }}
- name: AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE
value: /secrets/db_password
{{- end }}
{{- if .Values.notifier.smtp.enabled }}
- name: AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
value: /secrets/smtp_password
{{- end }}
- name: TZ
value: Europe/Moscow
volumeMounts:
- name: config
mountPath: /config
readOnly: true
- name: users
mountPath: /config/users_database.yml
subPath: users_database.yml
readOnly: true
- name: secrets
mountPath: /secrets
readOnly: true
- name: data
mountPath: /data
readinessProbe:
httpGet:
path: /api/health
port: 9091
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: 9091
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 3
startupProbe:
httpGet:
path: /api/health
port: 9091
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 12
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: config
secret:
secretName: {{ include "authelia.name" . }}-config
- name: users
secret:
secretName: {{ include "authelia.name" . }}-users
- name: secrets
secret:
secretName: {{ include "authelia.name" . }}-secrets
defaultMode: 0400
- name: data
persistentVolumeClaim:
claimName: {{ include "authelia.name" . }}-data

View File

@@ -0,0 +1,36 @@
{{- if .Values.ingress.enabled }}
---
# Authelia portal ingress — accessible at authHost
# No forward-auth annotation here (would cause an auth loop)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "authelia.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
annotations:
kubernetes.io/ingress.class: {{ .Values.ingress.ingressClass | quote }}
nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"
{{- if .Values.ingress.tls.certManager.enabled }}
cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.certManager.issuer | quote }}
{{- end }}
spec:
{{- if .Values.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.authHost | quote }}
secretName: {{ .Values.ingress.tls.secretName | default (printf "%s-tls" (include "authelia.name" .)) | quote }}
{{- end }}
rules:
- host: {{ .Values.authHost | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "authelia.name" . }}
port:
number: 9091
{{- end }}

View File

@@ -0,0 +1,18 @@
---
# PVC for Authelia data directory: SQLite database, notification log
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "authelia.name" . }}-data
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteOnce
{{- if .Values.storage.storageClassName }}
storageClassName: {{ .Values.storage.storageClassName | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.storage.size }}

View File

@@ -0,0 +1,64 @@
{{- if .Values.redis.enabled }}
---
# Redis Deployment — session storage for Authelia (optional but recommended)
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "authelia.name" . }}-redis
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
{{- include "authelia.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: redis
template:
metadata:
labels:
{{- include "authelia.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: redis
spec:
containers:
- name: redis
image: {{ .Values.redis.image }}
imagePullPolicy: IfNotPresent
command:
- redis-server
- --save ""
- --appendonly no
ports:
- name: redis
containerPort: 6379
protocol: TCP
resources:
{{- toYaml .Values.redis.resources | nindent 12 }}
readinessProbe:
exec:
command: [redis-cli, ping]
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "authelia.name" . }}-redis
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
type: ClusterIP
selector:
{{- include "authelia.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: redis
ports:
- name: redis
port: 6379
targetPort: 6379
protocol: TCP
{{- end }}

View File

@@ -0,0 +1,14 @@
---
# Authelia configuration.yml stored as Secret because it contains OIDC client secrets.
# Rendered via Helm tpl() from files/configuration.yml.tpl
apiVersion: v1
kind: Secret
metadata:
name: {{ include "authelia.name" . }}-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
type: Opaque
stringData:
configuration.yml: |
{{ tpl (.Files.Get "files/configuration.yml.tpl") . | indent 4 }}

View File

@@ -0,0 +1,24 @@
---
# Authelia core secrets — mounted as files, read via AUTHELIA_*_FILE env vars.
# Contains: jwt_secret, session_secret, storage_encryption_key, oidc_hmac_secret,
# oidc_private_key (RSA PEM), db_password (optional), smtp_password (optional)
apiVersion: v1
kind: Secret
metadata:
name: {{ include "authelia.name" . }}-secrets
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
type: Opaque
stringData:
jwt_secret: {{ .Values.secrets.jwtSecret | quote }}
session_secret: {{ .Values.secrets.sessionSecret | quote }}
storage_encryption_key: {{ .Values.secrets.storageEncryptionKey | quote }}
oidc_hmac_secret: {{ .Values.secrets.oidcHmacSecret | quote }}
oidc_private_key: {{ .Values.secrets.oidcPrivateKey | quote }}
{{- if eq .Values.storage.type "postgresql" }}
db_password: {{ .Values.secrets.dbPassword | quote }}
{{- end }}
{{- if .Values.notifier.smtp.enabled }}
smtp_password: {{ .Values.secrets.smtpPassword | quote }}
{{- end }}

View File

@@ -0,0 +1,25 @@
---
# Authelia users_database.yml — file-based authentication backend.
# Passwords must be Argon2id hashes (see README for generation command).
apiVersion: v1
kind: Secret
metadata:
name: {{ include "authelia.name" . }}-users
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
type: Opaque
stringData:
users_database.yml: |
users:
{{- range $username, $user := .Values.users }}
{{ $username }}:
disabled: {{ $user.disabled | default false }}
displayname: {{ $user.displayname | quote }}
password: {{ $user.password | quote }}
email: {{ $user.email | quote }}
groups:
{{- range $user.groups }}
- {{ . | quote }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "authelia.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "authelia.labels" . | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "authelia.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: 9091
targetPort: 9091
protocol: TCP

View File

@@ -0,0 +1,229 @@
# Authelia — SSO + Forward Auth
# Configure via: group_vars/all/addons.yml → authelia_* variables
# Secrets in: group_vars/all/vault.yml → authelia_* secrets
image:
repository: authelia/authelia
tag: "4.38.17"
pullPolicy: IfNotPresent
# ── Domain ────────────────────────────────────────────────────────────────────
# Base domain and public hostname for the Authelia portal
domain: "home.local"
authHost: "auth.home.local"
# ── Theme / UX ────────────────────────────────────────────────────────────────
theme: dark # light | dark | grey | auto
# ── Secrets (all provided via Ansible vault, never hardcoded here) ────────────
# These are placed in a dedicated K8s Secret and mounted as files.
# Authelia reads them via AUTHELIA_*_FILE environment variables.
secrets:
jwtSecret: "" # min 64 chars — `openssl rand -base64 64`
sessionSecret: "" # min 64 chars — `openssl rand -base64 64`
storageEncryptionKey: "" # min 20 chars — `openssl rand -base64 32`
oidcHmacSecret: "" # min 32 chars — `openssl rand -base64 48`
oidcPrivateKey: "" # RSA-4096 PEM — auto-generated during deploy
dbPassword: "" # only if storage.type=postgresql
smtpPassword: "" # only if notifier.smtp.enabled
# ── Users (file-based auth backend) ──────────────────────────────────────────
# Passwords must be Argon2id hashes.
# Generate: docker run authelia/authelia:latest authelia hash-password 'yourpassword'
# Or: authelia hash-password 'yourpassword' (if installed locally)
users:
admin:
disabled: false
displayname: "Administrator"
password: "" # set in vault: authelia_user_admin_password_hash (argon2id)
email: "admin@home.local"
groups:
- admins
- users
# ── TOTP ──────────────────────────────────────────────────────────────────────
totp:
issuer: "home.local"
period: 30
skew: 1
# ── 2FA policy ────────────────────────────────────────────────────────────────
# false = one_factor (password only) for protected/admin domains
# true = two_factor (password + TOTP/WebAuthn) for protected/admin domains
twoFactor:
enabled: false
# ── Session ───────────────────────────────────────────────────────────────────
session:
name: authelia_session
domain: "home.local"
sameSite: lax
expiration: 1h
inactivity: 5m
rememberMeDuration: 1M
# ── Storage ───────────────────────────────────────────────────────────────────
storage:
type: sqlite # sqlite | postgresql
size: 1Gi
storageClassName: ""
sqlite:
path: /data/db.sqlite3
postgresql:
host: "postgresql.postgresql.svc.cluster.local"
port: 5432
database: authelia
username: authelia
schema: public
# ── Redis (built-in, optional) ────────────────────────────────────────────────
# When enabled, deploys a Redis sidecar for persistent session storage.
# Recommended for production; not required for homelab.
redis:
enabled: false
image: "redis:7-alpine"
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
# ── Notifier ──────────────────────────────────────────────────────────────────
notifier:
# SMTP — for password reset and 2FA enrollment emails
smtp:
enabled: false
host: ""
port: 587
username: ""
sender: "authelia@home.local"
tls:
skipVerify: false
# Filesystem — fallback: writes to /data/notification.txt (no email)
filesystem:
enabled: true
# ── Access Control ────────────────────────────────────────────────────────────
# Rules evaluated top-to-bottom. First match wins.
accessControl:
defaultPolicy: deny
# Public services — no authentication required
bypassDomains: []
# - plex.home.local
# OIDC-enabled services — bypass forward-auth (OIDC handles its own auth)
oidcDomains:
- gitea.home.local
- grafana.home.local
- minio.home.local
# Services requiring admin group membership
adminDomains:
- argocd.home.local
- vault.home.local
- harbor.home.local
- kubernetes-dashboard.home.local
# Services requiring login (one_factor or two_factor per twoFactor.enabled)
protectedDomains:
- sonarr.home.local
- radarr.home.local
- lidarr.home.local
- bazarr.home.local
- prowlarr.home.local
- pgadmin.home.local
- phpmyadmin.home.local
# ── OIDC Provider ─────────────────────────────────────────────────────────────
oidc:
enabled: true
accessTokenLifespan: 1h
authorizeCodeLifespan: 1m
idTokenLifespan: 1h
refreshTokenLifespan: 90m
clients:
gitea:
enabled: true
id: gitea
secret: "" # set in vault: authelia_oidc_secret_gitea
description: "Gitea"
redirectUris:
- https://gitea.home.local/user/oauth2/Authelia/callback
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
grafana:
enabled: true
id: grafana
secret: "" # set in vault: authelia_oidc_secret_grafana
description: "Grafana"
redirectUris:
- https://grafana.home.local/login/generic_oauth
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
argocd:
enabled: false
id: argocd
secret: "" # set in vault: authelia_oidc_secret_argocd
description: "ArgoCD"
redirectUris:
- https://argocd.home.local/auth/callback
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
minio:
enabled: false
id: minio
secret: "" # set in vault: authelia_oidc_secret_minio
description: "MinIO"
redirectUris:
- https://minio.home.local/oauth_callback
scopes: [openid, profile, email]
grantTypes: [authorization_code]
vault:
enabled: false
id: vault
secret: "" # set in vault: authelia_oidc_secret_vault
description: "Vault"
redirectUris:
- https://vault.home.local/ui/vault/auth/oidc/oidc/callback
- https://vault.home.local/oidc/callback
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
nextcloud:
enabled: false
id: nextcloud
secret: "" # set in vault: authelia_oidc_secret_nextcloud
description: "Nextcloud"
redirectUris:
- https://nextcloud.home.local/apps/user_oidc/code
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
# ── Ingress ───────────────────────────────────────────────────────────────────
ingress:
enabled: true
ingressClass: nginx
tls:
enabled: false
secretName: ""
certManager:
enabled: false
issuer: ""
issuerKind: ClusterIssuer
# ── Resources ─────────────────────────────────────────────────────────────────
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi

View File

@@ -0,0 +1,123 @@
---
# ── Namespace / release ───────────────────────────────────────────────────────
authelia_namespace: authelia
authelia_release_name: authelia
# ── Domain ────────────────────────────────────────────────────────────────────
authelia_domain: "home.local"
authelia_host: "auth.home.local"
# ── Theme ─────────────────────────────────────────────────────────────────────
authelia_theme: dark # light | dark | grey | auto
# ── 2FA enforcement ───────────────────────────────────────────────────────────
authelia_two_factor_enabled: false
# ── Session ───────────────────────────────────────────────────────────────────
authelia_session_expiration: "1h"
authelia_session_inactivity: "5m"
authelia_session_remember_me: "1M"
# ── Storage ───────────────────────────────────────────────────────────────────
authelia_storage_type: sqlite # sqlite | postgresql
authelia_storage_size: 1Gi
authelia_storage_class: ""
# PostgreSQL (only if authelia_storage_type=postgresql)
authelia_db_host: "postgresql.postgresql.svc.cluster.local"
authelia_db_port: 5432
authelia_db_name: authelia
authelia_db_user: authelia
# ── Redis session storage (optional) ─────────────────────────────────────────
authelia_redis_enabled: false
# ── SMTP notifier (optional) ──────────────────────────────────────────────────
authelia_smtp_enabled: false
authelia_smtp_host: ""
authelia_smtp_port: 587
authelia_smtp_username: ""
authelia_smtp_sender: "authelia@home.local"
authelia_smtp_tls_skip_verify: false
# ── Ingress ───────────────────────────────────────────────────────────────────
authelia_ingress_enabled: true
authelia_ingress_class: nginx
authelia_ingress_tls_enabled: false
authelia_ingress_tls_secret: ""
authelia_ingress_cert_manager_enabled: false
authelia_ingress_cert_manager_issuer: ""
# ── OIDC provider ─────────────────────────────────────────────────────────────
authelia_oidc_enabled: true
# Per-client toggles — secrets come from vault.yml
authelia_oidc_gitea_enabled: true
authelia_oidc_gitea_redirect: "https://gitea.home.local/user/oauth2/Authelia/callback"
authelia_oidc_grafana_enabled: true
authelia_oidc_grafana_redirect: "https://grafana.home.local/login/generic_oauth"
authelia_oidc_argocd_enabled: false
authelia_oidc_argocd_redirect: "https://argocd.home.local/auth/callback"
authelia_oidc_minio_enabled: false
authelia_oidc_minio_redirect: "https://minio.home.local/oauth_callback"
authelia_oidc_vault_enabled: false
authelia_oidc_vault_redirect_1: "https://vault.home.local/ui/vault/auth/oidc/oidc/callback"
authelia_oidc_vault_redirect_2: "https://vault.home.local/oidc/callback"
authelia_oidc_nextcloud_enabled: false
authelia_oidc_nextcloud_redirect: "https://nextcloud.home.local/apps/user_oidc/code"
# ── Access control ────────────────────────────────────────────────────────────
authelia_bypass_domains: []
# - plex.home.local
authelia_oidc_domains:
- gitea.home.local
- grafana.home.local
- minio.home.local
authelia_admin_domains:
- argocd.home.local
- vault.home.local
- harbor.home.local
- kubernetes-dashboard.home.local
authelia_protected_domains:
- sonarr.home.local
- radarr.home.local
- lidarr.home.local
- bazarr.home.local
- prowlarr.home.local
- pgadmin.home.local
- phpmyadmin.home.local
# ── Users ─────────────────────────────────────────────────────────────────────
# Passwords are Argon2id hashes — set in vault.yml: authelia_user_*_password_hash
# Generate: docker run authelia/authelia:latest authelia hash-password 'yourpassword'
authelia_users:
admin:
displayname: "Administrator"
email: "admin@home.local"
groups:
- admins
- users
# ── Secrets — ALL must be set in vault.yml ────────────────────────────────────
# authelia_jwt_secret: "" # openssl rand -base64 64
# authelia_session_secret: "" # openssl rand -base64 64
# authelia_storage_encryption_key: "" # openssl rand -base64 32
# authelia_oidc_hmac_secret: "" # openssl rand -base64 48
# authelia_oidc_private_key: "" # auto-generated during deploy if empty
# authelia_oidc_secret_gitea: "" # openssl rand -hex 32
# authelia_oidc_secret_grafana: "" # openssl rand -hex 32
# authelia_oidc_secret_argocd: ""
# authelia_oidc_secret_minio: ""
# authelia_oidc_secret_vault: ""
# authelia_oidc_secret_nextcloud: ""
# authelia_user_admin_password_hash: "" # argon2id hash
# authelia_db_password: "" # only if storage_type=postgresql
# authelia_smtp_password: "" # only if smtp_enabled=true

View File

@@ -0,0 +1,206 @@
---
# ── Validate required secrets ─────────────────────────────────────────────────
- name: Validate Authelia required secrets are set
ansible.builtin.assert:
that:
- authelia_jwt_secret is defined and authelia_jwt_secret | length >= 64
- authelia_session_secret is defined and authelia_session_secret | length >= 64
- authelia_storage_encryption_key is defined and authelia_storage_encryption_key | length >= 20
- authelia_oidc_hmac_secret is defined and authelia_oidc_hmac_secret | length >= 32
fail_msg: >
Required secrets are missing or too short in vault.yml.
authelia_jwt_secret (min 64 chars) : openssl rand -base64 64
authelia_session_secret (min 64 chars) : openssl rand -base64 64
authelia_storage_encryption_key (min 20 chars): openssl rand -base64 32
authelia_oidc_hmac_secret (min 32 chars) : openssl rand -base64 48
- name: Validate admin user password hash is set
ansible.builtin.assert:
that:
- authelia_user_admin_password_hash is defined
- authelia_user_admin_password_hash | length > 0
fail_msg: >
authelia_user_admin_password_hash is not set in vault.yml.
Generate with: docker run authelia/authelia:latest authelia hash-password 'yourpassword'
Then paste the $argon2id$... hash into vault.yml
- name: Validate OIDC client secrets when OIDC is enabled
ansible.builtin.assert:
that:
- authelia_oidc_secret_gitea is defined and authelia_oidc_secret_gitea | length >= 16
- authelia_oidc_secret_grafana is defined and authelia_oidc_secret_grafana | length >= 16
fail_msg: >
authelia_oidc_secret_gitea and authelia_oidc_secret_grafana must be set in vault.yml.
Generate with: openssl rand -hex 32
when: authelia_oidc_enabled | bool
# ── Generate OIDC RSA private key if not provided ────────────────────────────
- name: Check if OIDC private key is already set
ansible.builtin.set_fact:
_authelia_oidc_key_provided: >-
{{ authelia_oidc_private_key is defined and
authelia_oidc_private_key | length > 0 }}
- name: Generate RSA-4096 OIDC private key (if not in vault)
ansible.builtin.command: openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096
register: _generated_oidc_key
changed_when: true
no_log: true
when:
- authelia_oidc_enabled | bool
- not _authelia_oidc_key_provided | bool
- name: Use generated OIDC private key
ansible.builtin.set_fact:
_authelia_oidc_private_key_final: "{{ _generated_oidc_key.stdout }}"
no_log: true
when:
- authelia_oidc_enabled | bool
- not _authelia_oidc_key_provided | bool
- name: Use vault-provided OIDC private key
ansible.builtin.set_fact:
_authelia_oidc_private_key_final: "{{ authelia_oidc_private_key }}"
no_log: true
when: _authelia_oidc_key_provided | bool
- name: Set empty OIDC private key (OIDC disabled)
ansible.builtin.set_fact:
_authelia_oidc_private_key_final: ""
when: not (authelia_oidc_enabled | bool)
# ── Create namespace ──────────────────────────────────────────────────────────
- name: Create authelia namespace
ansible.builtin.command: >
k3s kubectl create namespace {{ authelia_namespace }}
--dry-run=client -o yaml | k3s kubectl apply -f -
become: true
changed_when: false
# ── Copy Helm chart ───────────────────────────────────────────────────────────
- name: Ensure chart temp directory is clean
ansible.builtin.file:
path: /tmp/authelia-chart
state: absent
become: true
- name: Create chart temp directory
ansible.builtin.file:
path: /tmp/authelia-chart
state: directory
mode: "0755"
become: true
- name: Copy Helm chart to master
ansible.builtin.copy:
src: "{{ role_path }}/chart/"
dest: /tmp/authelia-chart/
mode: preserve
become: true
# ── Template Helm values ──────────────────────────────────────────────────────
- name: Template Helm values
ansible.builtin.template:
src: values.yaml.j2
dest: /tmp/authelia-values.yaml
mode: "0600"
become: true
no_log: true
# ── Lint chart ────────────────────────────────────────────────────────────────
- name: Lint Helm chart
ansible.builtin.command: >
helm lint /tmp/authelia-chart
--values /tmp/authelia-values.yaml
become: true
changed_when: false
register: _helm_lint
failed_when: _helm_lint.rc != 0
# ── Deploy chart ──────────────────────────────────────────────────────────────
- name: Deploy Authelia via Helm
ansible.builtin.command: >
helm upgrade --install {{ authelia_release_name }}
/tmp/authelia-chart
--namespace {{ authelia_namespace }}
--values /tmp/authelia-values.yaml
--atomic
--wait
--timeout 180s
become: true
register: _helm_result
changed_when: true
# ── Cleanup ───────────────────────────────────────────────────────────────────
- name: Remove temp values file (contains secrets)
ansible.builtin.file:
path: /tmp/authelia-values.yaml
state: absent
become: true
# ── Wait for readiness ────────────────────────────────────────────────────────
- name: Wait for Authelia pod to be ready
ansible.builtin.command: >
k3s kubectl -n {{ authelia_namespace }} rollout status
deployment/authelia --timeout=120s
become: true
changed_when: false
# ── Get status ────────────────────────────────────────────────────────────────
- name: Get pod status
ansible.builtin.command: >
k3s kubectl -n {{ authelia_namespace }} get pods,svc,ingress -o wide
become: true
changed_when: false
register: _status
- name: Get forward-auth annotations reference
ansible.builtin.command: >
k3s kubectl -n {{ authelia_namespace }} get cm authelia-forward-auth
-o jsonpath='{.data.annotations\.yaml}'
become: true
changed_when: false
register: _annotations
# ── Summary ───────────────────────────────────────────────────────────────────
- name: "=== Authelia SSO Ready ==="
ansible.builtin.debug:
msg:
- "╔══════════════════════════════════════════════════════════════╗"
- "║ Authelia SSO — Deployed ║"
- "╚══════════════════════════════════════════════════════════════╝"
- ""
- " Portal : http{{ 's' if authelia_ingress_tls_enabled else '' }}://{{ authelia_host }}/"
- " Namespace : {{ authelia_namespace }}"
- " OIDC : {{ 'enabled' if authelia_oidc_enabled else 'disabled' }}"
- " Storage : {{ authelia_storage_type }}"
- " Redis : {{ 'enabled' if authelia_redis_enabled else 'disabled' }}"
- ""
- " ── Protect a new service (add to its Ingress) ──"
- "{{ _annotations.stdout_lines | to_yaml }}"
- ""
- " ── OIDC Issuer URL ──"
- " http{{ 's' if authelia_ingress_tls_enabled else '' }}://{{ authelia_host }}"
- ""
- " ── Pods / Services ──"
- "{{ _status.stdout_lines | to_yaml }}"
- ""
- " Login: open http{{ 's' if authelia_ingress_tls_enabled else '' }}://{{ authelia_host }}/"
- " user: admin | pass: <your vault plaintext password>"
- ""
- "{% if not _authelia_oidc_key_provided %}"
- " ⚠ OIDC private key was AUTO-GENERATED. Save it to vault.yml for reproducibility:"
- " kubectl -n {{ authelia_namespace }} get secret authelia-secrets \\"
- " -o jsonpath='{.data.oidc_private_key}' | base64 -d"
- "{% endif %}"

View File

@@ -0,0 +1,154 @@
# Generated by Ansible — do not edit manually.
# Configure via: group_vars/all/addons.yml → authelia_* variables
# Secrets from: group_vars/all/vault.yml → authelia_* secrets
domain: {{ authelia_domain | quote }}
authHost: {{ authelia_host | quote }}
theme: {{ authelia_theme | quote }}
secrets:
jwtSecret: {{ authelia_jwt_secret | quote }}
sessionSecret: {{ authelia_session_secret | quote }}
storageEncryptionKey: {{ authelia_storage_encryption_key | quote }}
oidcHmacSecret: {{ authelia_oidc_hmac_secret | quote }}
oidcPrivateKey: {{ _authelia_oidc_private_key_final | quote }}
dbPassword: {{ authelia_db_password | default('') | quote }}
smtpPassword: {{ authelia_smtp_password | default('') | quote }}
# Users — passwords are Argon2id hashes from vault.yml
users:
{% for username, user in authelia_users.items() %}
{{ username }}:
disabled: false
displayname: {{ user.displayname | quote }}
password: {{ vars['authelia_user_' + username + '_password_hash'] | default('') | quote }}
email: {{ user.email | quote }}
groups:
{{ user.groups | to_yaml | indent(6, True) }}
{% endfor %}
totp:
issuer: {{ authelia_domain | quote }}
period: 30
skew: 1
twoFactor:
enabled: {{ authelia_two_factor_enabled | string | lower }}
session:
name: authelia_session
domain: {{ authelia_domain | quote }}
sameSite: lax
expiration: {{ authelia_session_expiration | quote }}
inactivity: {{ authelia_session_inactivity | quote }}
rememberMeDuration: {{ authelia_session_remember_me | quote }}
storage:
type: {{ authelia_storage_type | quote }}
size: {{ authelia_storage_size | quote }}
storageClassName: {{ authelia_storage_class | quote }}
sqlite:
path: /data/db.sqlite3
postgresql:
host: {{ authelia_db_host | quote }}
port: {{ authelia_db_port }}
database: {{ authelia_db_name | quote }}
username: {{ authelia_db_user | quote }}
schema: public
redis:
enabled: {{ authelia_redis_enabled | string | lower }}
notifier:
smtp:
enabled: {{ authelia_smtp_enabled | string | lower }}
host: {{ authelia_smtp_host | quote }}
port: {{ authelia_smtp_port }}
username: {{ authelia_smtp_username | quote }}
sender: {{ authelia_smtp_sender | quote }}
tls:
skipVerify: {{ authelia_smtp_tls_skip_verify | string | lower }}
accessControl:
defaultPolicy: deny
bypassDomains:
{{ authelia_bypass_domains | to_yaml | indent(4, True) }}
oidcDomains:
{{ authelia_oidc_domains | to_yaml | indent(4, True) }}
adminDomains:
{{ authelia_admin_domains | to_yaml | indent(4, True) }}
protectedDomains:
{{ authelia_protected_domains | to_yaml | indent(4, True) }}
oidc:
enabled: {{ authelia_oidc_enabled | string | lower }}
accessTokenLifespan: 1h
authorizeCodeLifespan: 1m
idTokenLifespan: 1h
refreshTokenLifespan: 90m
clients:
gitea:
enabled: {{ authelia_oidc_gitea_enabled | string | lower }}
id: gitea
secret: {{ authelia_oidc_secret_gitea | default('') | quote }}
description: "Gitea"
redirectUris:
- {{ authelia_oidc_gitea_redirect | quote }}
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
grafana:
enabled: {{ authelia_oidc_grafana_enabled | string | lower }}
id: grafana
secret: {{ authelia_oidc_secret_grafana | default('') | quote }}
description: "Grafana"
redirectUris:
- {{ authelia_oidc_grafana_redirect | quote }}
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
argocd:
enabled: {{ authelia_oidc_argocd_enabled | string | lower }}
id: argocd
secret: {{ authelia_oidc_secret_argocd | default('') | quote }}
description: "ArgoCD"
redirectUris:
- {{ authelia_oidc_argocd_redirect | quote }}
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
minio:
enabled: {{ authelia_oidc_minio_enabled | string | lower }}
id: minio
secret: {{ authelia_oidc_secret_minio | default('') | quote }}
description: "MinIO"
redirectUris:
- {{ authelia_oidc_minio_redirect | quote }}
scopes: [openid, profile, email]
grantTypes: [authorization_code]
vault:
enabled: {{ authelia_oidc_vault_enabled | string | lower }}
id: vault
secret: {{ authelia_oidc_secret_vault | default('') | quote }}
description: "Vault"
redirectUris:
- {{ authelia_oidc_vault_redirect_1 | quote }}
- {{ authelia_oidc_vault_redirect_2 | quote }}
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
nextcloud:
enabled: {{ authelia_oidc_nextcloud_enabled | string | lower }}
id: nextcloud
secret: {{ authelia_oidc_secret_nextcloud | default('') | quote }}
description: "Nextcloud"
redirectUris:
- {{ authelia_oidc_nextcloud_redirect | quote }}
scopes: [openid, profile, email, groups]
grantTypes: [refresh_token, authorization_code]
ingress:
enabled: {{ authelia_ingress_enabled | string | lower }}
ingressClass: {{ authelia_ingress_class | quote }}
tls:
enabled: {{ authelia_ingress_tls_enabled | string | lower }}
secretName: {{ authelia_ingress_tls_secret | quote }}
certManager:
enabled: {{ authelia_ingress_cert_manager_enabled | string | lower }}
issuer: {{ authelia_ingress_cert_manager_issuer | quote }}