From e48b19b64c73510306d00327f39e4a4ce4bd4666 Mon Sep 17 00:00:00 2001 From: Nikholas Pcenicni <82239765+nikpcenicni@users.noreply.github.com> Date: Thu, 14 May 2026 20:07:52 -0400 Subject: [PATCH] Implement S3 media storage for Authentik by adding configuration options for dedicated S3 bucket and credentials. Update README and default values to clarify usage and requirements for S3 integration, ensuring compatibility with Velero settings. Enhance Ansible tasks to load S3 configurations from the environment. --- .env.sample | 8 ++ ansible/roles/noble_authentik/README.md | 4 + .../roles/noble_authentik/defaults/main.yml | 9 ++ .../roles/noble_authentik/tasks/from_env.yml | 134 ++++++++++++++++++ ansible/roles/noble_authentik/tasks/main.yml | 13 ++ .../templates/authentik-extra-values.yaml.j2 | 14 ++ .../noble/bootstrap/authentik/values.yaml | 26 +--- clusters/noble/bootstrap/velero/README.md | 2 + 8 files changed, 186 insertions(+), 24 deletions(-) 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: