Add optional SMTP configuration for Authentik, including email host, port, and credentials. Update README and .env.sample to clarify usage for outbound email settings. Introduce blueprint support for enhanced deployment flexibility, with assertions in Ansible tasks to ensure required variables are set when enabled.

This commit is contained in:
Nikholas Pcenicni
2026-05-14 22:21:11 -04:00
parent e48b19b64c
commit fbcd2416e6
14 changed files with 478 additions and 6 deletions

View File

@@ -38,3 +38,13 @@ NOBLE_AUTHENTIK_MEDIA_S3_BUCKET=
# NOBLE_AUTHENTIK_S3_SECRET_KEY=
# NOBLE_AUTHENTIK_S3_REGION=
# NOBLE_AUTHENTIK_S3_ADDRESSING_STYLE=
#
# Optional outbound email (password recovery, invites, etc.) — maps to Authentik **AUTHENTIK_EMAIL__*** (see https://docs.goauthentik.io/install-config/configuration/#email-settings ). Omit **NOBLE_AUTHENTIK_SMTP_HOST** to leave email unset in Helm.
# NOBLE_AUTHENTIK_SMTP_HOST=
# NOBLE_AUTHENTIK_SMTP_FROM=
# NOBLE_AUTHENTIK_SMTP_PORT=587
# NOBLE_AUTHENTIK_SMTP_USERNAME=
# NOBLE_AUTHENTIK_SMTP_PASSWORD=
# NOBLE_AUTHENTIK_SMTP_USE_TLS=true
# NOBLE_AUTHENTIK_SMTP_USE_SSL=false
# NOBLE_AUTHENTIK_SMTP_TIMEOUT=30

View File

@@ -184,7 +184,7 @@ Shared services used across multiple applications.
- **[Versity S3 Gateway](https://github.com/versity/versitygw)** — S3 API on port **10000** by default; optional **WebUI** on **8080** (not the same listener—enable `VERSITYGW_WEBUI_PORT` / `VGW_WEBUI_GATEWAYS` per `.env.sample`). Behind **Pangolin**, expose the API and WebUI separately (or you will see **404** browsing the API URL).
**Configuration:** Set either `ROOT_ACCESS_KEY` / `ROOT_SECRET_KEY` or `ROOT_ACCESS_KEY_ID` / `ROOT_SECRET_ACCESS_KEY`. Optional `VERSITYGW_PORT`. Compose uses `${VAR}` interpolation so credentials work with Komodos `docker compose --env-file <run_directory>/.env` (avoid `env_file:` in the service when `run_directory` is not the same folder as `compose.yaml`, or the written `.env` will not be found).
**Configuration:** Set either `ROOT_ACCESS_KEY` / `ROOT_SECRET_KEY` or `ROOT_ACCESS_KEY_ID` / `ROOT_SECRET_ACCESS_KEY`. Optional `VERSITYGW_PORT`. For a browser UI on another host (for example `https://s3-ui.pcenicni.dev` calling the API through Pangolin), set **`VGW_CORS_ALLOW_ORIGIN`** to that UI origin; VersityGW reflects requested headers on preflight when bucket CORS is unset (see `.env.sample`). If that is already set and CORS still fails, **Pangolin** may be handling **OPTIONS** before Versity (for example auth on the public resource blocking preflight; see `komodo/s3/versitygw/.env.sample` § Pangolin). Compose uses `${VAR}` interpolation so credentials work with Komodos `docker compose --env-file <run_directory>/.env` (avoid `env_file:` in the service when `run_directory` is not the same folder as `compose.yaml`, or the written `.env` will not be found).
---

View File

@@ -30,3 +30,4 @@ noble_authentik_install: true
# Optional: public (or extra) Authentik hostnames on the same IdP — list of FQDNs. Pangolin: CNAME + resource → Newt → Traefik (see noble_authentik README).
noble_authentik_ingress_extra_hosts:
- auth.nikflix.ca
noble_authentik_blueprints_enabled: true

View File

@@ -4,7 +4,7 @@ Installs **Authentik** (Helm `goauthentik/authentik`) as the cluster IdP, **oaut
## Enable
1. Copy repository **`.env.sample`** to **`.env`** and set every **`NOBLE_AUTHENTIK_*`** variable (see comments there).
1. Copy repository **`.env.sample`** to **`.env`** and set all **required** **`NOBLE_AUTHENTIK_*`** values (see comments there; SMTP keys are optional).
2. Set **`noble_authentik_install: true`** in **`ansible/inventory/group_vars/all.yml`** (or pass **`-e noble_authentik_install=true`**).
3. Run **`ansible-playbook playbooks/noble.yml --tags authentik`** (or a full **`noble.yml`**) from **`ansible/`** with a working **`KUBECONFIG`**.
@@ -18,13 +18,43 @@ See **`defaults/main.yml`**. Hostnames default to **`auth.apps.noble.lab.pcenicn
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.
### Outbound email (SMTP)
Optional. Set **`NOBLE_AUTHENTIK_SMTP_HOST`** and **`NOBLE_AUTHENTIK_SMTP_FROM`** in repository **`.env`**; Ansible adds **`AUTHENTIK_EMAIL__HOST`**, **`AUTHENTIK_EMAIL__FROM`**, and related variables to Helm **`global.env`** (see [Authentik configuration — email](https://docs.goauthentik.io/install-config/configuration/#email-settings)). Omit **`NOBLE_AUTHENTIK_SMTP_HOST`** to skip SMTP env vars entirely. Optional overrides: **`NOBLE_AUTHENTIK_SMTP_PORT`** (default **587** in **`defaults/main.yml`**), **`NOBLE_AUTHENTIK_SMTP_USERNAME`**, **`NOBLE_AUTHENTIK_SMTP_PASSWORD`**, **`NOBLE_AUTHENTIK_SMTP_USE_TLS`** / **`USE_SSL`** / **`TIMEOUT`**. Re-run **`ansible-playbook playbooks/noble.yml --tags authentik`** after changes.
### 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).
Then in **Pangolin**: link the domain, create an **HTTP** resource for that hostname, and set the **target** to your **Newt** site with **`ip:port`** pointing at the cluster **Traefik** HTTPS entry (same pattern as **`clusters/noble/bootstrap/newt/README.md`** — typically the MetalLB / LAN VIP and **443**). One Newt tunnel can front many hostnames.
In **Authentik**, add a **Brand** (or equivalent) for the new hostname if you want different titles/favicon; OAuth **redirect URIs** for each app must include issuer URLs that match what browsers use (often you keep **internal** issuer URLs in cluster apps and use the public URL only for human login, or align all apps to the public issuer — pick one strategy to avoid mixed **`iss`** / callback mismatches).
### Split routing, two Brands, and optional blueprints
This role supports a **single Authentik deployment** with **two hostnames** (lab + public) and **different Brands** per **`Host`**, without Authentiks separate-database **Tenancy** feature (see [Tenancy](https://docs.goauthentik.io/sys-mgmt/tenancy) — alpha / licensing). [Brands](https://docs.goauthentik.io/brands/) choose default **authentication** (and related) **flows** and branding for each FQDN.
**Split routing (recommended):**
- **Lab / operator URL** — **`noble_authentik_host`** (default **`auth.apps.noble.lab.pcenicni.dev`**): keep DNS **internal-only** (split horizon, VPN, or LAN DNS). Do **not** publish this hostname as a Pangolin HTTP resource toward the internet unless you intentionally want it reachable off-LAN.
- **Public URL** — entries in **`noble_authentik_ingress_extra_hosts`**: use Pangolin (or another edge) only for these names so casual users never need the lab FQDN.
Network isolation is enforced at **DNS and the tunnel**, not inside Authentik. Optionally add firewall / Traefik entrypoint rules for defense in depth.
**Two-Brand model:**
- **Lab Brand** — domain equals **`noble_authentik_host`**: use a **restricted authentication flow** so only operator groups (defaults: **`noble-admins`**, **`authentik Admins`**) can complete sign-in on that hostname. Your bootstrap / break-glass account must remain in one of those groups (see **`noble_authentik_ensure_admin_ui_access`** and **`configure_authentik.py`** group membership).
- **Public Brand(s)** — one Brand per FQDN in **`noble_authentik_ingress_extra_hosts`**: use the stock **`default-authentication-flow`** (or replace with your own flow slug via a forked blueprint later). Assign general users to **`noble_authentik_blueprint_public_groups`** (defaults **`noble-public-users`**, **`noble-public-admins`**) for app policies and OAuth claims; **`noble-admins`** / **`noble-editors`** remain for cluster / Argo / Grafana as today.
**OAuth note:** Redirect URIs and **`iss`** must stay consistent with the hostname clients use (internal issuer for in-cluster apps vs public issuer is a deliberate choice — avoid mixing both for the same app).
**Mounted blueprints (optional):** set **`noble_authentik_blueprints_enabled: true`** in **`group_vars`** (or **`-e`**). On each **`--tags authentik`** run, Ansible renders Jinja templates under **`templates/blueprints/`** into a ConfigMap **`noble_authentik_blueprints_configmap_name`** (default **`authentik-noble-blueprints`**) and sets Helm **`blueprints.configMaps`** so **authentik-worker** loads them from **`/blueprints/mounted/cm-authentik-noble-blueprints/`** (see [Blueprints](https://docs.goauthentik.io/customize/blueprints/)). Files (apply in lexical order):
| Key | Purpose |
| --- | --- |
| **`10-noble-public-groups.yaml.j2`** | Ensures **`noble_authentik_blueprint_public_groups`** exist. |
| **`20-noble-lab-operator-authentication-flow.yaml.j2`** | Flow **`noble_authentik_blueprint_lab_flow_slug`** + expression policy **`noble_authentik_blueprint_operator_policy_name`** (allowed groups **`noble_authentik_blueprint_lab_operator_groups`**). |
| **`30-noble-brands-domain-split.yaml.j2`** | Brand for **`noble_authentik_host`** → lab flow; one Brand per **`noble_authentik_ingress_extra_hosts`** → default authentication. |
Tune titles via **`noble_authentik_blueprint_lab_brand_title`** and **`noble_authentik_blueprint_public_brand_title_prefix`**. After the worker applies blueprints, confirm **System → Brands** and **Flows** in the admin UI; fix any **`!Find`** failures if upstream default stage **names** change between Authentik versions.
### “Secondary tenant” (separate PostgreSQL schema — alpha)
@@ -41,6 +71,7 @@ When **`noble_authentik_configure_idp`** is true, Ansible creates/updates OAuth2
## Troubleshooting
- **Blueprints from Ansible fail to apply** (worker logs / **System → Blueprints**): confirm the ConfigMap exists (**`kubectl -n authentik get cm authentik-noble-blueprints`** unless you changed **`noble_authentik_blueprints_configmap_name`**), that Helm mounts it (**`blueprints.configMaps`** in the rendered extra values), and that every **`!Find`** in **`templates/blueprints/20-*.j2`** still matches your Authentik versions default stage **names**. Re-run **`--tags authentik`** after editing templates.
- **oauth2-proxy shows 500** on **`oauth2.apps…/oauth2/callback`** (logs: `email in id_token (...) isn't verified`): Authentiks id_token often lacks **`email_verified: true`** for bootstrap users. **`clusters/noble/bootstrap/oauth2-proxy/values.yaml`** sets **`insecure-oidc-allow-unverified-email`** for the lab; otherwise verify the users email in Authentik, then **`helm upgrade oauth2-proxy`** (or **`--tags authentik`**).
- Re-run **`configure_authentik.py`** only by executing **`noble.yml`** with **`--tags authentik`** after fixing `.env`.
- If Authentik API calls fail, check flows exist (slug **`default-provider-authorization-implicit-consent`**) and TLS reaches **`AUTHENTIK_API_BASE`**.

View File

@@ -26,6 +26,24 @@ noble_authentik_api_base: "{{ noble_authentik_public_url }}/api/v3"
# Ansible merges these into **server.ingress.hosts** / **tls** (one cert Secret with multiple SANs).
noble_authentik_ingress_extra_hosts: []
# Mounted **blueprints** (ConfigMap → worker `/blueprints/mounted/cm-*`). See README § split routing / two-Brand.
noble_authentik_blueprints_enabled: false
noble_authentik_blueprints_configmap_name: authentik-noble-blueprints
# Directory groups for the public Brand(s); adjust names to match your apps policies / OAuth claims.
noble_authentik_blueprint_public_groups:
- noble-public-users
- noble-public-admins
# Lab-only authentication flow slug (Brand for **`noble_authentik_host`** points here).
noble_authentik_blueprint_lab_flow_slug: noble-lab-operator-authentication-flow
noble_authentik_blueprint_operator_policy_name: noble-lab-operators-only
# Who may sign in on the **lab** hostname (`noble_authentik_host`). Bootstrap user should be in **noble-admins**
# and/or **authentik Admins** (see **`noble_authentik_ensure_admin_ui_access`**).
noble_authentik_blueprint_lab_operator_groups:
- noble-admins
- authentik Admins
noble_authentik_blueprint_lab_brand_title: Noble lab (operators)
noble_authentik_blueprint_public_brand_title_prefix: Noble public
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).
@@ -37,6 +55,17 @@ noble_authentik_s3_secret_key: ""
noble_authentik_s3_region: "us-east-1"
noble_authentik_s3_addressing_style: "path"
# Optional outbound SMTP (maps to **AUTHENTIK_EMAIL__*** in Helm **global.env**). Leave **noble_authentik_smtp_host**
# empty to omit email env vars; set **NOBLE_AUTHENTIK_SMTP_HOST** (and **NOBLE_AUTHENTIK_SMTP_FROM**) in **.env** to enable.
noble_authentik_smtp_host: ""
noble_authentik_smtp_port: "587"
noble_authentik_smtp_username: ""
noble_authentik_smtp_password: ""
noble_authentik_smtp_use_tls: "true"
noble_authentik_smtp_use_ssl: "false"
noble_authentik_smtp_timeout: "30"
noble_authentik_smtp_from: ""
# OIDC client ids (must match Authentik providers created by configure script)
noble_authentik_client_id_argocd: argocd
noble_authentik_client_id_grafana: grafana

View File

@@ -349,3 +349,168 @@
- noble_authentik_s3_addr_from_env is defined
- (noble_authentik_s3_addr_from_env.stdout | default('') | trim | length) > 0
no_log: true
# --- Optional SMTP (AUTHENTIK_EMAIL__* via Helm global.env) ---
- name: Load NOBLE_AUTHENTIK_SMTP_HOST from .env when unset
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_HOST:-}"
register: noble_authentik_smtp_host_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
- noble_authentik_smtp_host | default('') | length == 0
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_HOST from .env
ansible.builtin.set_fact:
noble_authentik_smtp_host: "{{ noble_authentik_smtp_host_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_host_from_env is defined
- (noble_authentik_smtp_host_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_FROM from .env when unset
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_FROM:-}"
register: noble_authentik_smtp_from_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
- noble_authentik_smtp_from | default('') | length == 0
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_FROM from .env
ansible.builtin.set_fact:
noble_authentik_smtp_from: "{{ noble_authentik_smtp_from_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_from_from_env is defined
- (noble_authentik_smtp_from_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_USERNAME from .env when unset
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_USERNAME:-}"
register: noble_authentik_smtp_username_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
- noble_authentik_smtp_username | default('') | length == 0
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_USERNAME from .env
ansible.builtin.set_fact:
noble_authentik_smtp_username: "{{ noble_authentik_smtp_username_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_username_from_env is defined
- (noble_authentik_smtp_username_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_PASSWORD from .env when unset
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_PASSWORD:-}"
register: noble_authentik_smtp_password_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
- noble_authentik_smtp_password | default('') | length == 0
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_PASSWORD from .env
ansible.builtin.set_fact:
noble_authentik_smtp_password: "{{ noble_authentik_smtp_password_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_password_from_env is defined
- (noble_authentik_smtp_password_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_PORT from .env
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_PORT:-}"
register: noble_authentik_smtp_port_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_PORT from .env
ansible.builtin.set_fact:
noble_authentik_smtp_port: "{{ noble_authentik_smtp_port_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_port_from_env is defined
- (noble_authentik_smtp_port_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_USE_TLS from .env
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_USE_TLS:-}"
register: noble_authentik_smtp_use_tls_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_USE_TLS from .env
ansible.builtin.set_fact:
noble_authentik_smtp_use_tls: "{{ noble_authentik_smtp_use_tls_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_use_tls_from_env is defined
- (noble_authentik_smtp_use_tls_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_USE_SSL from .env
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_USE_SSL:-}"
register: noble_authentik_smtp_use_ssl_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_USE_SSL from .env
ansible.builtin.set_fact:
noble_authentik_smtp_use_ssl: "{{ noble_authentik_smtp_use_ssl_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_use_ssl_from_env is defined
- (noble_authentik_smtp_use_ssl_from_env.stdout | default('') | trim | length) > 0
no_log: true
- name: Load NOBLE_AUTHENTIK_SMTP_TIMEOUT from .env
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
printf '%s' "${NOBLE_AUTHENTIK_SMTP_TIMEOUT:-}"
register: noble_authentik_smtp_timeout_from_env
when:
- noble_authentik_dotenv_stat.stat.exists | default(false)
changed_when: false
no_log: true
- name: Apply NOBLE_AUTHENTIK_SMTP_TIMEOUT from .env
ansible.builtin.set_fact:
noble_authentik_smtp_timeout: "{{ noble_authentik_smtp_timeout_from_env.stdout | trim }}"
when:
- noble_authentik_smtp_timeout_from_env is defined
- (noble_authentik_smtp_timeout_from_env.stdout | default('') | trim | length) > 0
no_log: true

View File

@@ -39,6 +39,15 @@
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: Require Authentik SMTP From when SMTP host is set
ansible.builtin.assert:
that:
- noble_authentik_smtp_from | default('') | trim | length > 0
fail_msg: >-
When NOBLE_AUTHENTIK_SMTP_HOST is set, set NOBLE_AUTHENTIK_SMTP_FROM (sender address).
See repository .env.sample and https://docs.goauthentik.io/install-config/configuration/#email-settings
when: noble_authentik_smtp_host | default('') | trim | length > 0
- name: Ensure Ansible temp dir for rendered Helm values
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/.ansible-tmp"
@@ -65,6 +74,47 @@
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Ensure dir for rendered Authentik blueprints
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/.ansible-tmp/authentik-blueprints"
state: directory
mode: "0700"
when: noble_authentik_blueprints_enabled | default(false) | bool
- name: Assert noble Authentik blueprint variables (when blueprints enabled)
ansible.builtin.assert:
that:
- noble_authentik_blueprint_public_groups | default([]) | length > 0
- noble_authentik_blueprint_lab_operator_groups | default([]) | length > 0
- noble_authentik_blueprint_lab_flow_slug | default('') | trim | length > 0
fail_msg: >-
When noble_authentik_blueprints_enabled is true, set noble_authentik_blueprint_public_groups (non-empty),
noble_authentik_blueprint_lab_operator_groups (non-empty), and noble_authentik_blueprint_lab_flow_slug.
See ansible/roles/noble_authentik/defaults/main.yml and README.
when: noble_authentik_blueprints_enabled | default(false) | bool
- name: Render Authentik noble blueprint YAML files
ansible.builtin.template:
src: "blueprints/{{ item }}.j2"
dest: "{{ noble_repo_root }}/ansible/.ansible-tmp/authentik-blueprints/{{ item }}"
mode: "0600"
loop:
- 10-noble-public-groups.yaml
- 20-noble-lab-operator-authentication-flow.yaml
- 30-noble-brands-domain-split.yaml
when: noble_authentik_blueprints_enabled | default(false) | bool
- name: Apply Authentik noble blueprints ConfigMap (worker mounts under /blueprints/mounted/cm-*)
ansible.builtin.shell: |
set -euo pipefail
kubectl -n "{{ noble_authentik_namespace }}" create configmap "{{ noble_authentik_blueprints_configmap_name }}" \
--from-file="{{ noble_repo_root }}/ansible/.ansible-tmp/authentik-blueprints" \
--dry-run=client -o yaml | kubectl apply -f -
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_authentik_blueprints_enabled | default(false) | bool
changed_when: true
- name: Install Authentik (Helm)
ansible.builtin.command:
argv:

View File

@@ -25,6 +25,24 @@ global:
value: "{{ noble_authentik_s3_region }}"
- name: AUTHENTIK_STORAGE__S3__ADDRESSING_STYLE
value: "{{ noble_authentik_s3_addressing_style }}"
{% if noble_authentik_smtp_host | default('') | trim | length > 0 %}
- name: AUTHENTIK_EMAIL__HOST
value: {{ noble_authentik_smtp_host | trim | to_json }}
- name: AUTHENTIK_EMAIL__PORT
value: {{ (noble_authentik_smtp_port | default('587') | string) | to_json }}
- name: AUTHENTIK_EMAIL__USERNAME
value: {{ noble_authentik_smtp_username | default('') | to_json }}
- name: AUTHENTIK_EMAIL__PASSWORD
value: {{ noble_authentik_smtp_password | default('') | to_json }}
- name: AUTHENTIK_EMAIL__USE_TLS
value: {{ (noble_authentik_smtp_use_tls | default('true') | string) | to_json }}
- name: AUTHENTIK_EMAIL__USE_SSL
value: {{ (noble_authentik_smtp_use_ssl | default('false') | string) | to_json }}
- name: AUTHENTIK_EMAIL__TIMEOUT
value: {{ (noble_authentik_smtp_timeout | default('30') | string) | to_json }}
- name: AUTHENTIK_EMAIL__FROM
value: {{ noble_authentik_smtp_from | trim | to_json }}
{% endif %}
postgresql:
auth:
password: "{{ noble_authentik_postgresql_password }}"
@@ -46,3 +64,8 @@ server:
- {{ h }}
{% endfor %}
{% endif %}
{% if noble_authentik_blueprints_enabled | default(false) | bool %}
blueprints:
configMaps:
- {{ noble_authentik_blueprints_configmap_name }}
{% endif %}

View File

@@ -0,0 +1,13 @@
# Noble — directory groups for the **public** hostname Brand (see role README).
# Groups are global to the instance; use policies and OAuth scope mappings to scope claims per app.
version: 1
metadata:
name: noble-public-groups
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
{% for group in noble_authentik_blueprint_public_groups | default([]) %}
- model: authentik_core.group
identifiers:
name: "{{ group | trim }}"
{% endfor %}

View File

@@ -0,0 +1,101 @@
# Noble — authentication flow for the **lab** hostname Brand: only members of operator groups may continue.
# Reuses default identification / password / MFA / login stages; adds a policy on the password stage binding.
version: 1
metadata:
name: noble-lab-operator-authentication
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
- model: authentik_blueprints.metaapplyblueprint
attrs:
identifiers:
name: Default - Password change flow
required: false
- model: authentik_flows.flow
id: flow
identifiers:
slug: {{ noble_authentik_blueprint_lab_flow_slug | trim | to_json }}
attrs:
name: Noble lab (operators)
title: Noble lab — operators only
designation: authentication
authentication: none
- id: noble-lab-identification-binding
model: authentik_flows.flowstagebinding
identifiers:
order: 10
stage: !Find [authentik_stages_identification.identificationstage, [name, default-authentication-identification]]
target: !KeyOf flow
- id: noble-lab-password-binding
model: authentik_flows.flowstagebinding
identifiers:
order: 20
stage: !Find [authentik_stages_password.passwordstage, [name, default-authentication-password]]
target: !KeyOf flow
attrs:
re_evaluate_policies: true
- id: noble-lab-authenticator-binding
model: authentik_flows.flowstagebinding
identifiers:
order: 30
stage: !Find [authentik_stages_authenticator_validate.authenticatorvalidatestage, [name, default-authentication-mfa-validation]]
target: !KeyOf flow
- model: authentik_flows.flowstagebinding
identifiers:
order: 100
stage: !Find [authentik_stages_user_login.userloginstage, [name, default-authentication-login]]
target: !KeyOf flow
- model: authentik_policies_expression.expressionpolicy
id: noble-lab-password-optional
identifiers:
name: noble-lab-password-optional
attrs:
expression: |
flow_plan = request.context.get("flow_plan")
if not flow_plan:
return True
return not hasattr(flow_plan.context.get("pending_user"), "backend")
- model: authentik_policies_expression.expressionpolicy
id: noble-lab-authenticator-validate-optional
identifiers:
name: noble-lab-authenticator-validate-optional
attrs:
expression: |
flow_plan = request.context.get("flow_plan")
if not flow_plan:
return True
return not (flow_plan.context.get("auth_method") == "auth_webauthn_pwl")
- model: authentik_policies_expression.expressionpolicy
id: noble-lab-operators-only
identifiers:
name: {{ noble_authentik_blueprint_operator_policy_name | trim | to_json }}
attrs:
expression: |
u = context.get("pending_user")
if u is None:
return False
{% for g in noble_authentik_blueprint_lab_operator_groups | default([]) %}
if ak_is_group_member(u, name={{ g | trim | to_json }}):
return True
{% endfor %}
ak_message("This login URL is for administrators only. Use the public Authentik hostname instead.")
return False
- model: authentik_policies.policybinding
identifiers:
order: 5
target: !KeyOf noble-lab-password-binding
policy: !KeyOf noble-lab-operators-only
- model: authentik_policies.policybinding
identifiers:
order: 10
target: !KeyOf noble-lab-password-binding
policy: !KeyOf noble-lab-password-optional
attrs:
failure_result: true
- model: authentik_policies.policybinding
identifiers:
order: 10
target: !KeyOf noble-lab-authenticator-binding
policy: !KeyOf noble-lab-authenticator-validate-optional
attrs:
failure_result: true

View File

@@ -0,0 +1,27 @@
# Noble — Brands so **Host** selects authentication flow: lab hostname → operator-only flow; extra hosts → default login.
version: 1
metadata:
name: noble-brands-domain-split
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
- model: authentik_brands.brand
identifiers:
domain: {{ noble_authentik_host | trim | to_json }}
attrs:
default: false
title: {{ noble_authentik_blueprint_lab_brand_title | trim | to_json }}
flow_authentication: !Find [authentik_flows.flow, [slug, {{ noble_authentik_blueprint_lab_flow_slug | trim | to_json }}]]
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
{% for host in noble_authentik_ingress_extra_hosts | default([]) %}
- model: authentik_brands.brand
identifiers:
domain: {{ host | trim | to_json }}
attrs:
default: false
title: {{ ((noble_authentik_blueprint_public_brand_title_prefix | default('Noble public')) ~ ' (' ~ (host | trim) ~ ')') | to_json }}
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
{% endfor %}

View File

@@ -13,6 +13,8 @@
#
# **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.
# **SMTP:** optional — set **`NOBLE_AUTHENTIK_SMTP_HOST`** + **`NOBLE_AUTHENTIK_SMTP_FROM`** in repo **`.env`** so Ansible injects **`AUTHENTIK_EMAIL__*`** into Helm **`global.env`** (see role README).
# **Blueprints:** optional **`blueprints.configMaps`** is merged by Ansible when **`noble_authentik_blueprints_enabled`** is true — see **`ansible/roles/noble_authentik/README.md`**.
postgresql:
enabled: true

View File

@@ -26,11 +26,30 @@ VERSITYGW_WEBUI_PORT=8080
# VGW_WEBUI_GATEWAYS=https://s3.example.com
VGW_WEBUI_GATEWAYS=
# Public origin of the **WebUI** page (Pangolin → :8080), e.g. https://s3-ui.example.com
# Required when UI and API are on different hosts so the browser can call the API (CORS).
# Public origin of the **browser app** that calls the S3 API (no path, no trailing slash).
# Use this when the UI is on a different hostname than the API — e.g. third-party S3 consoles,
# or the built-in WebUI behind Pangolin on another host than :10000.
# Example: VGW_CORS_ALLOW_ORIGIN=https://s3-ui.pcenicni.dev
#
# VersityGW maps the browsers preflight Access-Control-Request-Headers into
# Access-Control-Allow-Headers (Authorization, X-Amz-Date, X-Amz-Content-Sha256, Content-Type, …)
# when there is **no** per-bucket CORS configuration. You do not set those headers separately here.
#
# If you used PutBucketCors on a bucket, that config replaces this fallback for that bucket: add
# the same Origin and AllowedHeader entries (or *) there, or delete bucket CORS to rely on this.
# VGW_CORS_ALLOW_ORIGIN=https://s3-ui.example.com
VGW_CORS_ALLOW_ORIGIN=
# --- Pangolin (edge) vs Versity CORS ---
# If VGW_CORS_ALLOW_ORIGIN is correct but the browser still says CORS failed, the edge often
# never returns Versitys Access-Control-* headers: Pangolin can answer OPTIONS / block preflight
# before Newt reaches :10000. S3 clients send OPTIONS without SigV4 auth; Pangolin SSO or
# “authorization” on the HTTP resource can reject that (see https://github.com/fosrl/pangolin/issues/2369 ).
# Mitigations: make the **S3 API** hostname resource public (no Pangolin auth on that resource),
# or add a rule that allows OPTIONS to pass through when Pangolin supports method-based rules;
# confirm with: curl -sv -X OPTIONS -H "Origin: https://your-s3-ui" -H "Access-Control-Request-Method: PUT" \
# -H "Access-Control-Request-Headers: authorization,content-type" "https://your-s3-api-host/" 2>&1 | head -40
# NFS: object metadata defaults to xattrs; most NFS mounts need sidecar mode
# (compose.yaml uses --sidecar /data/sidecar). Create the host path, e.g.
# mkdir -p /mnt/nfs/versity/sidecar

View File

@@ -26,7 +26,8 @@ services:
# Public base URL of the *S3 API* only (Pangolin → :10000). Not the WebUI hostname.
# No trailing slash. If this points at the UI URL, bucket ops return 404/wrong host.
VGW_WEBUI_GATEWAYS: ${VGW_WEBUI_GATEWAYS}
# Browser Origin when WebUI and API use different HTTPS hostnames (see wiki / WebGUI CORS).
# Browser Origin for cross-host S3 from the UI (maps to --cors-allow-origin). See .env.sample
# for third-party consoles vs bucket PutBucketCors overrides.
VGW_CORS_ALLOW_ORIGIN: ${VGW_CORS_ALLOW_ORIGIN}
ports:
- "${VERSITYGW_PORT:-10000}:10000"