From 7fed8820ce687d90fd54c016daad05f72f9b4bda Mon Sep 17 00:00:00 2001 From: Nikholas Pcenicni <82239765+nikpcenicni@users.noreply.github.com> Date: Thu, 14 May 2026 22:59:40 -0400 Subject: [PATCH] Enhance Authentik configuration by introducing dedicated authentication flows for public and lab brands, including stricter password policies and MFA requirements. Update README to clarify flow distinctions and invitation enrollment processes. Improve validation in Ansible tasks to ensure all necessary blueprint variables are set, enhancing deployment robustness. --- ansible/roles/noble_authentik/README.md | 23 +- .../roles/noble_authentik/defaults/main.yml | 31 +++ ansible/roles/noble_authentik/tasks/main.yml | 16 +- ...e-lab-operator-authentication-flow.yaml.j2 | 75 ++++-- ...1-noble-public-authentication-flow.yaml.j2 | 80 ++++++ ...-noble-invitation-enrollment-flows.yaml.j2 | 227 ++++++++++++++++++ .../30-noble-brands-domain-split.yaml.j2 | 4 +- 7 files changed, 435 insertions(+), 21 deletions(-) create mode 100644 ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 create mode 100644 ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 diff --git a/ansible/roles/noble_authentik/README.md b/ansible/roles/noble_authentik/README.md index 16a72ab..9aee536 100644 --- a/ansible/roles/noble_authentik/README.md +++ b/ansible/roles/noble_authentik/README.md @@ -41,8 +41,8 @@ Network isolation is enforced at **DNS and the tunnel**, not inside Authentik. O **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 people to **`noble_authentik_blueprint_public_groups`** and/or **`noble_authentik_blueprint_nikflix_groups`** (defaults include **`nikflix-users`** / **`nikflix-admins`** for the Nikflix hostname); **`noble-admins`** / **`noble-editors`** remain for cluster / Argo / Grafana as today. +- **Lab Brand** — domain equals **`noble_authentik_host`**: restricted authentication flow (**`noble_authentik_blueprint_lab_flow_slug`**) so only operator groups (defaults: **`noble-admins`**, **`authentik Admins`**) can continue past identification; dedicated password stage with **`noble_authentik_blueprint_lab_password_failed_attempts`**, expression-based password strength (**length / character classes / optional zxcvbn**), and MFA stage **`noble-lab-authenticator-validate-strict`** with **`noble_authentik_blueprint_lab_mfa_not_configured_action`** (**`configure`** injects default TOTP setup when no device; **`deny`** blocks; **`skip`** matches stock). WebAuthn passwordless does **not** skip MFA on this flow. 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`**: authentication flow **`noble_authentik_blueprint_public_auth_flow_slug`** (default **`noble-public-authentication-flow`**) mirrors stock **`default-authentication-flow`** (optional password / MFA skip for WebAuthn passwordless) so you can tune lab vs public independently. Assign people to **`noble_authentik_blueprint_public_groups`** and/or **`noble_authentik_blueprint_nikflix_groups`** (defaults include **`nikflix-users`** / **`nikflix-admins`** for the Nikflix hostname); **`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). @@ -51,11 +51,26 @@ Network isolation is enforced at **DNS and the tunnel**, not inside Authentik. O | Key | Purpose | | --- | --- | | **`10-noble-public-groups.yaml.j2`** | **`noble_authentik_blueprint_public_groups`** ∪ **`noble_authentik_blueprint_extra_directory_groups`** ∪ **`noble_authentik_blueprint_nikflix_groups`** → **Group** objects (see **Blueprint: directory groups**). | -| **`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. | +| **`20-noble-lab-operator-authentication-flow.yaml.j2`** | Flow **`noble_authentik_blueprint_lab_flow_slug`**: operator policy **`noble_authentik_blueprint_operator_policy_name`**, lab password/MFA tunables (see **`defaults/main.yml`**). | +| **`21-noble-public-authentication-flow.yaml.j2`** | Flow **`noble_authentik_blueprint_public_auth_flow_slug`** — public sign-in (same optional policies as stock default authentication). | +| **`22-noble-invitation-enrollment-flows.yaml.j2`** | Two **enrollment** flows + **Invitation** stages: public (**`noble_authentik_blueprint_public_invitation_enrollment_flow_slug`**) vs lab (**`noble_authentik_blueprint_lab_invitation_enrollment_flow_slug`**); see **Invitations** below. | +| **`30-noble-brands-domain-split.yaml.j2`** | Brand for **`noble_authentik_host`** → lab flow; one Brand per **`noble_authentik_ingress_extra_hosts`** → public flow slug above. | 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. +#### Invitations (public vs lab) + +[Brands](https://docs.goauthentik.io/brands/) do **not** expose a default “enrollment” or “invitation” flow: onboarding is driven by **Directory → Invitations**, where each invitation row selects an **enrollment** flow. The link Authentik shows is: + +`https:///if/flow//?itoken=` + +Use **``** that matches the experience you want: + +- **Public / Nikflix** — an FQDN from **`noble_authentik_ingress_extra_hosts`**: use flow slug **`noble_authentik_blueprint_public_invitation_enrollment_flow_slug`** (default **`noble-public-invitation-enrollment`**). New users are added to **`noble_authentik_blueprint_public_invitation_user_group`** (default **`noble-public-users`**; override to **`nikflix-users`** if that is your only audience group). Tune **`noble_authentik_blueprint_public_invitation_user_type`** (**`external`** / **`internal`**) and **`noble_authentik_blueprint_public_invitation_user_path`** as needed. +- **Lab** — **`noble_authentik_host`** only when you intend to onboard someone who will later get **`noble_authentik_blueprint_lab_operator_groups`** access: use **`noble_authentik_blueprint_lab_invitation_enrollment_flow_slug`** (default **`noble-lab-invitation-enrollment`**). The blueprint creates **`noble_authentik_blueprint_lab_invitee_group_name`** (default **`noble-lab-invited`**) and assigns new enrollments there; **promote** people to **`noble-admins`** / **`authentik Admins`** (or your configured operator groups) in the admin UI when they should sign in on the lab URL. + +Blueprint **22** does **not** create sample **Invitation** rows (no placeholder emails). Create invitations in the UI after blueprints apply. For richer patterns (prefilled attributes, extra policies), see [Invitations](https://docs.goauthentik.io/users-sources/user/invitations/) and the upstream example blueprint **`flows-invitation-enrollment.yaml`** ([download](https://goauthentik.io/blueprints/example/flows-invitation-enrollment.yaml)). Password strength for enrollment prompts is **not** duplicated from the lab **authentication** flow here; add **Prompt** validation policies or a dedicated policy if you need parity. + #### Blueprint: directory groups Three inventory lists are concatenated **in this order** into **`10-noble-public-groups.yaml.j2`**: diff --git a/ansible/roles/noble_authentik/defaults/main.yml b/ansible/roles/noble_authentik/defaults/main.yml index 440086c..313d07f 100644 --- a/ansible/roles/noble_authentik/defaults/main.yml +++ b/ansible/roles/noble_authentik/defaults/main.yml @@ -68,6 +68,37 @@ noble_authentik_blueprint_lab_operator_groups: - authentik Admins noble_authentik_blueprint_lab_brand_title: Noble lab (operators) noble_authentik_blueprint_public_brand_title_prefix: Noble public +# Public hostname Brand(s) → dedicated authentication flow (**21-noble-public-…** blueprint). +noble_authentik_blueprint_public_auth_flow_slug: noble-public-authentication-flow +# Lab flow: password stage (**failed_attempts_before_cancel**) and strength checks (expression policy; skips when **password** not yet in request context). +noble_authentik_blueprint_lab_password_failed_attempts: 3 +noble_authentik_blueprint_lab_password_policy_length_min: 16 +noble_authentik_blueprint_lab_password_policy_amount_uppercase: 1 +noble_authentik_blueprint_lab_password_policy_amount_lowercase: 1 +noble_authentik_blueprint_lab_password_policy_amount_digits: 1 +noble_authentik_blueprint_lab_password_policy_amount_symbols: 1 +noble_authentik_blueprint_lab_password_policy_check_zxcvbn: true +noble_authentik_blueprint_lab_password_policy_zxcvbn_score_threshold: 3 +noble_authentik_blueprint_lab_password_policy_error_message: >- + Lab password policy: at least 16 characters with upper, lower, digit, symbol, and sufficient strength. +# Lab MFA when user has no compatible device: **skip** (like stock), **deny** (block), **configure** (TOTP setup via default stage). +noble_authentik_blueprint_lab_mfa_not_configured_action: configure +# Invitation-based **enrollment** flows (blueprint **22**). Brands do not select enrollment; each **Invitation** picks a flow. +# Link shape: **`https:///if/flow//?itoken=`** — use the **public** hostname for **`noble_authentik_blueprint_public_invitation_enrollment_flow_slug`** invites. +noble_authentik_blueprint_public_invitation_enrollment_flow_slug: noble-public-invitation-enrollment +noble_authentik_blueprint_lab_invitation_enrollment_flow_slug: noble-lab-invitation-enrollment +noble_authentik_blueprint_public_invitation_flow_name: Noble public invitation enrollment +noble_authentik_blueprint_public_invitation_flow_title: Complete your signup +noble_authentik_blueprint_lab_invitation_flow_name: Noble lab invitation enrollment +noble_authentik_blueprint_lab_invitation_flow_title: Lab access — complete enrollment +# **User write** for public invites: must match an existing **Group** name from **`10-noble-public-groups`** (default **`noble-public-users`**; use **`nikflix-users`** if you only maintain Nikflix groups). +noble_authentik_blueprint_public_invitation_user_group: noble-public-users +noble_authentik_blueprint_public_invitation_user_type: external +noble_authentik_blueprint_public_invitation_user_path: users/noble/public +# Lab invites: blueprint creates **`noble_authentik_blueprint_lab_invitee_group_name`**; add members to **`noble_authentik_blueprint_lab_operator_groups`** manually when they should use the lab URL. +noble_authentik_blueprint_lab_invitee_group_name: noble-lab-invited +noble_authentik_blueprint_lab_invitation_user_type: internal +noble_authentik_blueprint_lab_invitation_user_path: users/noble/lab noble_authentik_oauth2_proxy_host: oauth2.apps.noble.lab.pcenicni.dev diff --git a/ansible/roles/noble_authentik/tasks/main.yml b/ansible/roles/noble_authentik/tasks/main.yml index 1211dbf..c1e0758 100644 --- a/ansible/roles/noble_authentik/tasks/main.yml +++ b/ansible/roles/noble_authentik/tasks/main.yml @@ -90,11 +90,23 @@ + (noble_authentik_blueprint_nikflix_groups | default([])) | length) > 0 - noble_authentik_blueprint_lab_operator_groups | default([]) | length > 0 - noble_authentik_blueprint_lab_flow_slug | default('') | trim | length > 0 + - noble_authentik_blueprint_public_auth_flow_slug | default('') | trim | length > 0 + - (noble_authentik_blueprint_lab_mfa_not_configured_action | default('configure') | trim | lower) + in ['skip', 'deny', 'configure'] + - noble_authentik_blueprint_public_invitation_enrollment_flow_slug | default('') | trim | length > 0 + - noble_authentik_blueprint_lab_invitation_enrollment_flow_slug | default('') | trim | length > 0 + - noble_authentik_blueprint_public_invitation_user_group | default('') | trim | length > 0 + - noble_authentik_blueprint_lab_invitee_group_name | default('') | trim | length > 0 + - (noble_authentik_blueprint_public_invitation_user_type | default('external') | trim | lower) in ['external', 'internal'] + - (noble_authentik_blueprint_lab_invitation_user_type | default('internal') | trim | lower) in ['external', 'internal'] fail_msg: >- When noble_authentik_blueprints_enabled is true, set at least one entry across noble_authentik_blueprint_public_groups, noble_authentik_blueprint_extra_directory_groups, and/or noble_authentik_blueprint_nikflix_groups, - plus noble_authentik_blueprint_lab_operator_groups (non-empty) and noble_authentik_blueprint_lab_flow_slug. + plus noble_authentik_blueprint_lab_operator_groups (non-empty), noble_authentik_blueprint_lab_flow_slug, + noble_authentik_blueprint_public_auth_flow_slug, noble_authentik_blueprint_lab_mfa_not_configured_action + (skip, deny, or configure), invitation enrollment flow slugs, noble_authentik_blueprint_public_invitation_user_group, + noble_authentik_blueprint_lab_invitee_group_name, and invitation user_type values (external or internal). See ansible/roles/noble_authentik/defaults/main.yml and README. when: noble_authentik_blueprints_enabled | default(false) | bool @@ -118,6 +130,8 @@ loop: - 10-noble-public-groups.yaml - 20-noble-lab-operator-authentication-flow.yaml + - 21-noble-public-authentication-flow.yaml + - 22-noble-invitation-enrollment-flows.yaml - 30-noble-brands-domain-split.yaml when: noble_authentik_blueprints_enabled | default(false) | bool diff --git a/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 index 7d96c8b..3646db7 100644 --- a/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 +++ b/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 @@ -1,5 +1,4 @@ -# 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. +# Noble — **lab** hostname authentication: operator-only, stricter password checks, MFA required (no WebAuthn-PWL skip). version: 1 metadata: name: noble-lab-operator-authentication @@ -11,6 +10,28 @@ entries: identifiers: name: Default - Password change flow required: false + - model: authentik_stages_password.passwordstage + id: noble-lab-password-stage + identifiers: + name: noble-lab-authentication-password + attrs: + backends: + - authentik.core.auth.InbuiltBackend + - authentik.sources.kerberos.auth.KerberosBackend + - authentik.sources.ldap.auth.LDAPBackend + - authentik.core.auth.TokenBackend + configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]] + failed_attempts_before_cancel: {{ noble_authentik_blueprint_lab_password_failed_attempts | int }} + - model: authentik_stages_authenticator_validate.authenticatorvalidatestage + id: noble-lab-authenticator-validate-strict + identifiers: + name: noble-lab-authenticator-validate-strict + attrs: + not_configured_action: {{ noble_authentik_blueprint_lab_mfa_not_configured_action | trim | lower | to_json }} +{% if noble_authentik_blueprint_lab_mfa_not_configured_action | trim | lower == 'configure' %} + configuration_stages: + - !Find [authentik_stages_authenticator_totp.authenticatortotpstage, [name, default-authenticator-totp-setup]] +{% endif %} - model: authentik_flows.flow id: flow identifiers: @@ -30,7 +51,7 @@ entries: model: authentik_flows.flowstagebinding identifiers: order: 20 - stage: !Find [authentik_stages_password.passwordstage, [name, default-authentication-password]] + stage: !KeyOf noble-lab-password-stage target: !KeyOf flow attrs: re_evaluate_policies: true @@ -38,7 +59,7 @@ entries: model: authentik_flows.flowstagebinding identifiers: order: 30 - stage: !Find [authentik_stages_authenticator_validate.authenticatorvalidatestage, [name, default-authentication-mfa-validation]] + stage: !KeyOf noble-lab-authenticator-validate-strict target: !KeyOf flow - model: authentik_flows.flowstagebinding identifiers: @@ -56,15 +77,43 @@ entries: return True return not hasattr(flow_plan.context.get("pending_user"), "backend") - model: authentik_policies_expression.expressionpolicy - id: noble-lab-authenticator-validate-optional + id: noble-lab-password-strength identifiers: - name: noble-lab-authenticator-validate-optional + name: noble-lab-password-strength attrs: expression: | - flow_plan = request.context.get("flow_plan") - if not flow_plan: + import re + _zxc = {{ noble_authentik_blueprint_lab_password_policy_check_zxcvbn | bool }} + _zxc_thr = {{ noble_authentik_blueprint_lab_password_policy_zxcvbn_score_threshold | int }} + pwd = request.context.get("password") + if not pwd: return True - return not (flow_plan.context.get("auth_method") == "auth_webauthn_pwl") + msg = {{ noble_authentik_blueprint_lab_password_policy_error_message | trim | to_json }} + if len(pwd) < {{ noble_authentik_blueprint_lab_password_policy_length_min | int }}: + ak_message(msg) + return False + if len(re.findall(r"[A-Z]", pwd)) < {{ noble_authentik_blueprint_lab_password_policy_amount_uppercase | int }}: + ak_message(msg) + return False + if len(re.findall(r"[a-z]", pwd)) < {{ noble_authentik_blueprint_lab_password_policy_amount_lowercase | int }}: + ak_message(msg) + return False + if len(re.findall(r"[0-9]", pwd)) < {{ noble_authentik_blueprint_lab_password_policy_amount_digits | int }}: + ak_message(msg) + return False + sym = sum(1 for c in pwd if (not c.isalnum()) and (not c.isspace())) + if sym < {{ noble_authentik_blueprint_lab_password_policy_amount_symbols | int }}: + ak_message(msg) + return False + if _zxc: + try: + from zxcvbn import zxcvbn + if zxcvbn(pwd[:72])["score"] <= _zxc_thr: + ak_message("Password is too weak for the lab policy.") + return False + except Exception: + pass + return True - model: authentik_policies_expression.expressionpolicy id: noble-lab-operators-only identifiers: @@ -94,8 +143,6 @@ entries: 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 + order: 15 + target: !KeyOf noble-lab-password-binding + policy: !KeyOf noble-lab-password-strength diff --git a/ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 new file mode 100644 index 0000000..9360f61 --- /dev/null +++ b/ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 @@ -0,0 +1,80 @@ +# Noble — **public** hostname(s): same behaviour as stock **default-authentication-flow** (optional password / MFA skip for WebAuthn PWL), isolated slug for Brand binding. +version: 1 +metadata: + name: noble-public-authentication-flow + 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_public_auth_flow_slug | trim | to_json }} + attrs: + name: Noble public sign-in + title: Sign in + designation: authentication + authentication: none + - id: noble-public-identification-binding + model: authentik_flows.flowstagebinding + identifiers: + order: 10 + stage: !Find [authentik_stages_identification.identificationstage, [name, default-authentication-identification]] + target: !KeyOf flow + - id: noble-public-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-public-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-public-password-optional + identifiers: + name: noble-public-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-public-authenticator-validate-optional + identifiers: + name: noble-public-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.policybinding + identifiers: + order: 10 + target: !KeyOf noble-public-password-binding + policy: !KeyOf noble-public-password-optional + attrs: + failure_result: true + - model: authentik_policies.policybinding + identifiers: + order: 10 + target: !KeyOf noble-public-authenticator-binding + policy: !KeyOf noble-public-authenticator-validate-optional + attrs: + failure_result: true diff --git a/ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 new file mode 100644 index 0000000..5f77ee4 --- /dev/null +++ b/ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 @@ -0,0 +1,227 @@ +# Noble — two **enrollment** flows (public vs lab) with separate **Invitation** stages (invitation token required). +# Create rows under **Directory → Invitations** in the admin UI and pick the matching flow; share links with the +# correct **Host** so the right Brand applies. Does **not** ship example **Invitation** objects (no prefilled emails). +version: 1 +metadata: + name: noble-invitation-enrollment-flows + labels: + blueprints.goauthentik.io/instantiate: "true" +entries: + - model: authentik_core.group + id: noble-lab-invited-group + identifiers: + name: {{ noble_authentik_blueprint_lab_invitee_group_name | trim | to_json }} + attrs: + is_superuser: false + attributes: + "noble.ak/audience": lab + "noble.ak/role": lab-invited + + - model: authentik_flows.flow + id: noble-inv-flow-public + identifiers: + slug: {{ noble_authentik_blueprint_public_invitation_enrollment_flow_slug | trim | to_json }} + attrs: + name: {{ noble_authentik_blueprint_public_invitation_flow_name | trim | to_json }} + title: {{ noble_authentik_blueprint_public_invitation_flow_title | trim | to_json }} + designation: enrollment + authentication: require_unauthenticated + + - model: authentik_flows.flow + id: noble-inv-flow-lab + identifiers: + slug: {{ noble_authentik_blueprint_lab_invitation_enrollment_flow_slug | trim | to_json }} + attrs: + name: {{ noble_authentik_blueprint_lab_invitation_flow_name | trim | to_json }} + title: {{ noble_authentik_blueprint_lab_invitation_flow_title | trim | to_json }} + designation: enrollment + authentication: require_unauthenticated + + - model: authentik_stages_invitation.invitationstage + id: noble-inv-stage-public + identifiers: + name: noble-invitation-enrollment-invitation-public + attrs: + continue_flow_without_invitation: false + + - model: authentik_stages_invitation.invitationstage + id: noble-inv-stage-lab + identifiers: + name: noble-invitation-enrollment-invitation-lab + attrs: + continue_flow_without_invitation: false + + - id: noble-inv-prompt-field-username + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-username + attrs: + field_key: username + label: Username + type: username + required: true + placeholder: Username + placeholder_expression: false + order: 0 + + - id: noble-inv-prompt-field-password + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-password + attrs: + field_key: password + label: Password + type: password + required: true + placeholder: Password + placeholder_expression: false + order: 1 + + - id: noble-inv-prompt-field-password-repeat + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-password-repeat + attrs: + field_key: password_repeat + label: Password (repeat) + type: password + required: true + placeholder: Password (repeat) + placeholder_expression: false + order: 2 + + - id: noble-inv-prompt-field-name + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-name + attrs: + field_key: name + label: Name + type: text + required: true + placeholder: Name + placeholder_expression: false + order: 0 + + - id: noble-inv-prompt-field-email + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-email + attrs: + field_key: email + label: Email + type: email + required: true + placeholder: Email + placeholder_expression: false + order: 1 + + - id: noble-inv-prompt-stage-credentials + model: authentik_stages_prompt.promptstage + identifiers: + name: noble-inv-enroll-prompt-credentials + attrs: + fields: + - !KeyOf noble-inv-prompt-field-username + - !KeyOf noble-inv-prompt-field-password + - !KeyOf noble-inv-prompt-field-password-repeat + + - id: noble-inv-prompt-stage-details + model: authentik_stages_prompt.promptstage + identifiers: + name: noble-inv-enroll-prompt-details + attrs: + fields: + - !KeyOf noble-inv-prompt-field-name + - !KeyOf noble-inv-prompt-field-email + + - id: noble-inv-user-write-public + model: authentik_stages_user_write.userwritestage + identifiers: + name: noble-inv-enroll-user-write-public + attrs: + user_creation_mode: always_create + user_type: {{ noble_authentik_blueprint_public_invitation_user_type | trim | lower | to_json }} + user_path_template: {{ noble_authentik_blueprint_public_invitation_user_path | trim | to_json }} + create_users_group: !Find [authentik_core.group, [name, {{ noble_authentik_blueprint_public_invitation_user_group | trim | to_json }}]] + + - id: noble-inv-user-write-lab + model: authentik_stages_user_write.userwritestage + identifiers: + name: noble-inv-enroll-user-write-lab + attrs: + user_creation_mode: always_create + user_type: {{ noble_authentik_blueprint_lab_invitation_user_type | trim | lower | to_json }} + user_path_template: {{ noble_authentik_blueprint_lab_invitation_user_path | trim | to_json }} + create_users_group: !KeyOf noble-lab-invited-group + + - id: noble-inv-user-login + model: authentik_stages_user_login.userloginstage + identifiers: + name: noble-inv-enroll-user-login + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-stage-public + order: 5 + attrs: + evaluate_on_plan: true + re_evaluate_policies: true + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-prompt-stage-credentials + order: 10 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-prompt-stage-details + order: 15 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-user-write-public + order: 20 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-user-login + order: 100 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-stage-lab + order: 5 + attrs: + evaluate_on_plan: true + re_evaluate_policies: true + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-prompt-stage-credentials + order: 10 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-prompt-stage-details + order: 15 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-user-write-lab + order: 20 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-user-login + order: 100 diff --git a/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 index 8c995f7..c308dbe 100644 --- a/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 +++ b/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 @@ -1,4 +1,4 @@ -# Noble — Brands so **Host** selects authentication flow: lab hostname → operator-only flow; extra hosts → default login. +# Noble — Brands so **Host** selects authentication flow: lab hostname → operator-only hardened flow; extra hosts → public flow (**21**). version: 1 metadata: name: noble-brands-domain-split @@ -21,7 +21,7 @@ entries: 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_authentication: !Find [authentik_flows.flow, [slug, {{ noble_authentik_blueprint_public_auth_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]] {% endfor %}