diff --git a/.env.sample b/.env.sample index 6080580..59f676f 100644 --- a/.env.sample +++ b/.env.sample @@ -30,3 +30,11 @@ NOBLE_AUTHENTIK_CLIENT_SECRET_HEADLAMP= NOBLE_AUTHENTIK_CLIENT_SECRET_OAUTH2_PROXY= # Random secret for oauth2-proxy session cookie (see oauth2-proxy Helm chart docs; e.g. openssl rand -base64 32 | head -c 32 | base64) NOBLE_AUTHENTIK_OAUTH2_PROXY_COOKIE_SECRET= +# S3 media — **separate** bucket from Velero backups (**NOBLE_VELERO_S3_BUCKET**). Endpoint and keys default to the Velero vars above unless you set the Authentik-specific overrides. +NOBLE_AUTHENTIK_MEDIA_S3_BUCKET= +# Optional overrides (otherwise **NOBLE_VELERO_S3_URL** and Velero AWS keys are used): +# NOBLE_AUTHENTIK_S3_URL= +# NOBLE_AUTHENTIK_S3_ACCESS_KEY= +# NOBLE_AUTHENTIK_S3_SECRET_KEY= +# NOBLE_AUTHENTIK_S3_REGION= +# NOBLE_AUTHENTIK_S3_ADDRESSING_STYLE= diff --git a/ansible/roles/noble_authentik/README.md b/ansible/roles/noble_authentik/README.md index ebda5be..99d4330 100644 --- a/ansible/roles/noble_authentik/README.md +++ b/ansible/roles/noble_authentik/README.md @@ -14,6 +14,10 @@ Installs **Authentik** (Helm `goauthentik/authentik`) as the cluster IdP, **oaut See **`defaults/main.yml`**. Hostnames default to **`auth.apps.noble.lab.pcenicni.dev`** and **`oauth2.apps.noble.lab.pcenicni.dev`**. **`noble_authentik_ensure_admin_ui_access`** (default **true**) re-applies **authentik Admins** superuser membership via the worker on each **`--tags authentik`** run so the admin UI keeps working under **2026+** RBAC. +### S3 media (avatars, flows, uploads) + +Authentik stores file-backed data in **S3** (not a shared PVC on **`authentik-worker`**). Set **`NOBLE_AUTHENTIK_MEDIA_S3_BUCKET`** in **`.env`** to a **dedicated** bucket name (do **not** reuse the Velero backup bucket). **`NOBLE_VELERO_S3_URL`**, **`NOBLE_VELERO_AWS_ACCESS_KEY_ID`**, and **`NOBLE_VELERO_AWS_SECRET_ACCESS_KEY`** are reused automatically when the Authentik-specific S3 variables are unset; override with **`NOBLE_AUTHENTIK_S3_URL`** / **`NOBLE_AUTHENTIK_S3_ACCESS_KEY`** / **`NOBLE_AUTHENTIK_S3_SECRET_KEY`** if needed. Optional: **`NOBLE_AUTHENTIK_S3_REGION`** (defaults to **`us-east-1`** in Ansible), **`NOBLE_AUTHENTIK_S3_ADDRESSING_STYLE`** (**`path`** vs **`virtual`** for some gateways). Create the bucket and grant the same credentials **read/write** to that bucket only. For browser uploads and public assets, follow [Authentik — S3 storage](https://docs.goauthentik.io/sys-mgmt/ops/storage-s3/) (CORS and policies). If you previously used a PVC for **`/data`**, sync into the new bucket (for example **`aws s3 sync`** from a volume snapshot or old mount) before relying on S3-only. + ### Extra public hostname (Pangolin + Newt, same Authentik) To expose the **same** Authentik instance on an **internet-facing** FQDN (while keeping the lab name on Traefik), set **`noble_authentik_ingress_extra_hosts`** in **`ansible/inventory/group_vars/all.yml`** (or **`-e`**) to a list of extra FQDNs, for example **`auth.example.com`**. Re-run **`ansible-playbook playbooks/noble.yml --tags authentik`**. Ansible extends **`server.ingress.hosts`** and **`tls[0].hosts`** so **cert-manager** issues one certificate with SANs for the primary **`noble_authentik_host`** plus those names (DNS must resolve for your issuer — often **Cloudflare** for public names, split horizon for lab). diff --git a/ansible/roles/noble_authentik/defaults/main.yml b/ansible/roles/noble_authentik/defaults/main.yml index e012969..ee1b07d 100644 --- a/ansible/roles/noble_authentik/defaults/main.yml +++ b/ansible/roles/noble_authentik/defaults/main.yml @@ -28,6 +28,15 @@ noble_authentik_ingress_extra_hosts: [] noble_authentik_oauth2_proxy_host: oauth2.apps.noble.lab.pcenicni.dev +# Media: **S3** via Ansible **`global.env`** (same S3 **URL** + **access keys** as **Velero** when you omit Authentik-specific overrides). +# Set **`NOBLE_AUTHENTIK_MEDIA_S3_BUCKET`** to a **dedicated** bucket (do not use the Velero backup bucket). +noble_authentik_media_s3_bucket: "" +noble_authentik_s3_endpoint: "" +noble_authentik_s3_access_key: "" +noble_authentik_s3_secret_key: "" +noble_authentik_s3_region: "us-east-1" +noble_authentik_s3_addressing_style: "path" + # OIDC client ids (must match Authentik providers created by configure script) noble_authentik_client_id_argocd: argocd noble_authentik_client_id_grafana: grafana diff --git a/ansible/roles/noble_authentik/tasks/from_env.yml b/ansible/roles/noble_authentik/tasks/from_env.yml index d20398e..9e00a3a 100644 --- a/ansible/roles/noble_authentik/tasks/from_env.yml +++ b/ansible/roles/noble_authentik/tasks/from_env.yml @@ -215,3 +215,137 @@ - noble_authentik_cs_cookie_from_env is defined - (noble_authentik_cs_cookie_from_env.stdout | default('') | trim | length) > 0 no_log: true + +# --- S3 media (reuse Velero endpoint + AWS keys from .env unless Authentik-specific vars are set) --- +- name: Load NOBLE_AUTHENTIK_MEDIA_S3_BUCKET from .env when unset + ansible.builtin.shell: | + set -a + . "{{ noble_repo_root }}/.env" + set +a + printf '%s' "${NOBLE_AUTHENTIK_MEDIA_S3_BUCKET:-}" + register: noble_authentik_media_s3_bucket_from_env + when: + - noble_authentik_dotenv_stat.stat.exists | default(false) + - noble_authentik_media_s3_bucket | default('') | length == 0 + changed_when: false + no_log: true + +- name: Apply NOBLE_AUTHENTIK_MEDIA_S3_BUCKET from .env + ansible.builtin.set_fact: + noble_authentik_media_s3_bucket: "{{ noble_authentik_media_s3_bucket_from_env.stdout | trim }}" + when: + - noble_authentik_media_s3_bucket_from_env is defined + - (noble_authentik_media_s3_bucket_from_env.stdout | default('') | trim | length) > 0 + no_log: true + +- name: Resolve Authentik S3 endpoint from .env (Authentik-specific URL or Velero S3 URL) + ansible.builtin.shell: | + set -a + . "{{ noble_repo_root }}/.env" + set +a + if [ -n "${NOBLE_AUTHENTIK_S3_URL:-}" ]; then printf '%s' "${NOBLE_AUTHENTIK_S3_URL}" + elif [ -n "${NOBLE_VELERO_S3_URL:-}" ]; then printf '%s' "${NOBLE_VELERO_S3_URL}" + else printf '' + fi + register: noble_authentik_s3_endpoint_from_env + when: + - noble_authentik_dotenv_stat.stat.exists | default(false) + - noble_authentik_s3_endpoint | default('') | length == 0 + changed_when: false + no_log: true + +- name: Apply resolved Authentik S3 endpoint from .env + ansible.builtin.set_fact: + noble_authentik_s3_endpoint: "{{ noble_authentik_s3_endpoint_from_env.stdout | trim }}" + when: + - noble_authentik_s3_endpoint_from_env is defined + - (noble_authentik_s3_endpoint_from_env.stdout | default('') | trim | length) > 0 + no_log: true + +- name: Resolve Authentik S3 access key from .env (override or Velero AWS key) + ansible.builtin.shell: | + set -a + . "{{ noble_repo_root }}/.env" + set +a + if [ -n "${NOBLE_AUTHENTIK_S3_ACCESS_KEY:-}" ]; then printf '%s' "${NOBLE_AUTHENTIK_S3_ACCESS_KEY}" + elif [ -n "${NOBLE_VELERO_AWS_ACCESS_KEY_ID:-}" ]; then printf '%s' "${NOBLE_VELERO_AWS_ACCESS_KEY_ID}" + else printf '' + fi + register: noble_authentik_s3_access_from_env + when: + - noble_authentik_dotenv_stat.stat.exists | default(false) + - noble_authentik_s3_access_key | default('') | length == 0 + changed_when: false + no_log: true + +- name: Apply resolved Authentik S3 access key from .env + ansible.builtin.set_fact: + noble_authentik_s3_access_key: "{{ noble_authentik_s3_access_from_env.stdout | trim }}" + when: + - noble_authentik_s3_access_from_env is defined + - (noble_authentik_s3_access_from_env.stdout | default('') | trim | length) > 0 + no_log: true + +- name: Resolve Authentik S3 secret key from .env (override or Velero AWS secret) + ansible.builtin.shell: | + set -a + . "{{ noble_repo_root }}/.env" + set +a + if [ -n "${NOBLE_AUTHENTIK_S3_SECRET_KEY:-}" ]; then printf '%s' "${NOBLE_AUTHENTIK_S3_SECRET_KEY}" + elif [ -n "${NOBLE_VELERO_AWS_SECRET_ACCESS_KEY:-}" ]; then printf '%s' "${NOBLE_VELERO_AWS_SECRET_ACCESS_KEY}" + else printf '' + fi + register: noble_authentik_s3_secret_from_env + when: + - noble_authentik_dotenv_stat.stat.exists | default(false) + - noble_authentik_s3_secret_key | default('') | length == 0 + changed_when: false + no_log: true + +- name: Apply resolved Authentik S3 secret key from .env + ansible.builtin.set_fact: + noble_authentik_s3_secret_key: "{{ noble_authentik_s3_secret_from_env.stdout | trim }}" + when: + - noble_authentik_s3_secret_from_env is defined + - (noble_authentik_s3_secret_from_env.stdout | default('') | trim | length) > 0 + no_log: true + +- name: Load NOBLE_AUTHENTIK_S3_REGION from .env when set + ansible.builtin.shell: | + set -a + . "{{ noble_repo_root }}/.env" + set +a + printf '%s' "${NOBLE_AUTHENTIK_S3_REGION:-}" + register: noble_authentik_s3_region_from_env + when: + - noble_authentik_dotenv_stat.stat.exists | default(false) + changed_when: false + no_log: true + +- name: Apply NOBLE_AUTHENTIK_S3_REGION from .env + ansible.builtin.set_fact: + noble_authentik_s3_region: "{{ noble_authentik_s3_region_from_env.stdout | trim }}" + when: + - noble_authentik_s3_region_from_env is defined + - (noble_authentik_s3_region_from_env.stdout | default('') | trim | length) > 0 + no_log: true + +- name: Load NOBLE_AUTHENTIK_S3_ADDRESSING_STYLE from .env when set + ansible.builtin.shell: | + set -a + . "{{ noble_repo_root }}/.env" + set +a + printf '%s' "${NOBLE_AUTHENTIK_S3_ADDRESSING_STYLE:-}" + register: noble_authentik_s3_addr_from_env + when: + - noble_authentik_dotenv_stat.stat.exists | default(false) + changed_when: false + no_log: true + +- name: Apply NOBLE_AUTHENTIK_S3_ADDRESSING_STYLE from .env + ansible.builtin.set_fact: + noble_authentik_s3_addressing_style: "{{ noble_authentik_s3_addr_from_env.stdout | trim }}" + when: + - noble_authentik_s3_addr_from_env is defined + - (noble_authentik_s3_addr_from_env.stdout | default('') | trim | length) > 0 + no_log: true diff --git a/ansible/roles/noble_authentik/tasks/main.yml b/ansible/roles/noble_authentik/tasks/main.yml index 3db453f..d5d1ad3 100644 --- a/ansible/roles/noble_authentik/tasks/main.yml +++ b/ansible/roles/noble_authentik/tasks/main.yml @@ -26,6 +26,19 @@ fail_msg: >- Authentik requires secrets in .env (see ansible/roles/noble_authentik/README.md) or matching -e extra-vars. + - name: Require Authentik S3 media settings (same endpoint/keys as Velero; dedicated bucket) + ansible.builtin.assert: + that: + - noble_authentik_media_s3_bucket | default('') | length > 0 + - noble_authentik_s3_endpoint | default('') | length > 0 + - noble_authentik_s3_access_key | default('') | length > 0 + - noble_authentik_s3_secret_key | default('') | length > 0 + fail_msg: >- + Set NOBLE_AUTHENTIK_MEDIA_S3_BUCKET (dedicated bucket for media, not the Velero backup bucket). + For S3 URL and keys, set NOBLE_AUTHENTIK_S3_URL / NOBLE_AUTHENTIK_S3_ACCESS_KEY / NOBLE_AUTHENTIK_S3_SECRET_KEY, + or reuse Velero's NOBLE_VELERO_S3_URL and NOBLE_VELERO_AWS_ACCESS_KEY_ID / NOBLE_VELERO_AWS_SECRET_ACCESS_KEY + in .env (see .env.sample and clusters/noble/bootstrap/velero/README.md). + - name: Ensure Ansible temp dir for rendered Helm values ansible.builtin.file: path: "{{ noble_repo_root }}/ansible/.ansible-tmp" diff --git a/ansible/roles/noble_authentik/templates/authentik-extra-values.yaml.j2 b/ansible/roles/noble_authentik/templates/authentik-extra-values.yaml.j2 index c6c12f1..556ff91 100644 --- a/ansible/roles/noble_authentik/templates/authentik-extra-values.yaml.j2 +++ b/ansible/roles/noble_authentik/templates/authentik-extra-values.yaml.j2 @@ -11,6 +11,20 @@ global: value: "{{ noble_authentik_bootstrap_email }}" - name: AUTHENTIK_BOOTSTRAP_PASSWORD value: "{{ noble_authentik_bootstrap_password }}" + - name: AUTHENTIK_STORAGE__BACKEND + value: "s3" + - name: AUTHENTIK_STORAGE__S3__BUCKET_NAME + value: "{{ noble_authentik_media_s3_bucket }}" + - name: AUTHENTIK_STORAGE__S3__ENDPOINT + value: "{{ noble_authentik_s3_endpoint }}" + - name: AUTHENTIK_STORAGE__S3__ACCESS_KEY + value: "{{ noble_authentik_s3_access_key }}" + - name: AUTHENTIK_STORAGE__S3__SECRET_KEY + value: "{{ noble_authentik_s3_secret_key }}" + - name: AUTHENTIK_STORAGE__S3__REGION + value: "{{ noble_authentik_s3_region }}" + - name: AUTHENTIK_STORAGE__S3__ADDRESSING_STYLE + value: "{{ noble_authentik_s3_addressing_style }}" postgresql: auth: password: "{{ noble_authentik_postgresql_password }}" diff --git a/clusters/noble/bootstrap/authentik/values.yaml b/clusters/noble/bootstrap/authentik/values.yaml index 63ac02f..ec17385 100644 --- a/clusters/noble/bootstrap/authentik/values.yaml +++ b/clusters/noble/bootstrap/authentik/values.yaml @@ -11,23 +11,8 @@ # helm upgrade --install authentik goauthentik/authentik -n authentik --create-namespace \ # --version 2026.2.3 -f clusters/noble/bootstrap/authentik/values.yaml -f /path/to/extra.yaml --wait # -# **Media / uploads:** PVC **`authentik-data`** at **`/data`** is mounted on **server only**. Longhorn **RWO** allows -# a single attachment — the same PVC on **server** and **worker** causes **Multi-Attach** errors. For shared media from -# workers, use **S3** or an **RWX** StorageClass (e.g. **`longhorn-rwx`** when installed) and **ReadWriteMany** on the PVC. - -additionalObjects: - - apiVersion: v1 - kind: PersistentVolumeClaim - metadata: - name: authentik-data - namespace: "{{ .Release.Namespace }}" - spec: - accessModes: - - ReadWriteOnce - storageClassName: longhorn - resources: - requests: - storage: 10Gi +# **Media / uploads:** **S3** (same endpoint/credentials pattern as **Velero** — see **ansible/roles/noble_authentik** and **.env.sample**). +# Ansible sets **`AUTHENTIK_STORAGE__BACKEND=s3`** in **`authentik-extra-values.yaml.j2`**; use a **dedicated** media bucket, not the Velero backup bucket. postgresql: enabled: true @@ -51,13 +36,6 @@ authentik: server: replicas: 1 - volumes: - - name: authentik-data - persistentVolumeClaim: - claimName: authentik-data - volumeMounts: - - name: authentik-data - mountPath: /data ingress: enabled: true ingressClassName: traefik diff --git a/clusters/noble/bootstrap/velero/README.md b/clusters/noble/bootstrap/velero/README.md index 834c8f2..6f37cce 100644 --- a/clusters/noble/bootstrap/velero/README.md +++ b/clusters/noble/bootstrap/velero/README.md @@ -19,6 +19,8 @@ Ansible-managed core stack — **not** reconciled by Argo CD (`clusters/noble/ap 4. **S3-compatible** endpoint (MinIO, VersityGW, AWS, etc.) and a **bucket**. +5. **Authentik** (when **`noble_authentik_install=true`**) can reuse the **same** S3 endpoint and access-key credentials for a **separate** media bucket (**`NOBLE_AUTHENTIK_MEDIA_S3_BUCKET`**); see **`ansible/roles/noble_authentik/README.md`**. + ## Credentials Secret Velero expects **`velero/velero-cloud-credentials`**, key **`cloud`**, in **INI** form for the AWS plugin: