Remove automated recovery flow configuration
Automated recovery flow setup via blueprints was too complex and unreliable. Recovery flows (password reset via email) must now be configured manually in Authentik admin UI. Changes: - Removed recovery-flow.yaml blueprint - Removed configure_recovery_flow.py script - Removed update-recovery-flow.yml playbook - Updated flows.yml to remove recovery references - Updated custom-flows.yaml to remove brand recovery flow config - Updated comments to reflect manual recovery flow requirement Automated configuration still includes: - Enrollment flow with invitation support - 2FA/MFA enforcement - OIDC provider for Nextcloud - Email configuration via SMTP 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6cd6d7cc79
commit
dc14b12688
6 changed files with 5 additions and 322 deletions
|
|
@ -1,63 +0,0 @@
|
||||||
---
|
|
||||||
# Deploy password recovery flow with email notifications
|
|
||||||
- name: Deploy password recovery flow
|
|
||||||
hosts: all
|
|
||||||
gather_facts: no
|
|
||||||
become: yes
|
|
||||||
|
|
||||||
vars:
|
|
||||||
authentik_api_token: "ak_DtA2LG1Z9shl-tw9r0cs34B1G9l8Lpz76GxLf-4OBiUWbiHbAVJ04GYLcZ30"
|
|
||||||
client_domain: "dev.vrije.cloud"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Create blueprints directory
|
|
||||||
file:
|
|
||||||
path: /opt/config/authentik/blueprints
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Copy recovery flow blueprint
|
|
||||||
copy:
|
|
||||||
src: ../roles/authentik/files/recovery-flow.yaml
|
|
||||||
dest: /opt/config/authentik/blueprints/recovery-flow.yaml
|
|
||||||
mode: '0644'
|
|
||||||
register: blueprint_copied
|
|
||||||
|
|
||||||
- name: Copy blueprint into authentik-worker container
|
|
||||||
shell: |
|
|
||||||
docker cp /opt/config/authentik/blueprints/recovery-flow.yaml authentik-worker:/blueprints/recovery-flow.yaml
|
|
||||||
when: blueprint_copied.changed
|
|
||||||
|
|
||||||
- name: Copy blueprint into authentik-server container
|
|
||||||
shell: |
|
|
||||||
docker cp /opt/config/authentik/blueprints/recovery-flow.yaml authentik-server:/blueprints/recovery-flow.yaml
|
|
||||||
when: blueprint_copied.changed
|
|
||||||
|
|
||||||
- name: Restart authentik-worker to force blueprint discovery
|
|
||||||
shell: docker restart authentik-worker
|
|
||||||
when: blueprint_copied.changed
|
|
||||||
|
|
||||||
- name: Wait for blueprint to be applied
|
|
||||||
shell: |
|
|
||||||
sleep 30
|
|
||||||
docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \
|
|
||||||
'http://localhost:9000/api/v3/flows/instances/?slug=default-recovery-flow'
|
|
||||||
register: flow_check
|
|
||||||
retries: 6
|
|
||||||
delay: 10
|
|
||||||
until: flow_check.rc == 0
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Display success message
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
✓ Password recovery flow deployed successfully!
|
|
||||||
|
|
||||||
Users can now reset their passwords by:
|
|
||||||
1. Going to https://auth.{{ client_domain }}/if/flow/default-recovery-flow/
|
|
||||||
2. Entering their email address
|
|
||||||
3. Receiving a recovery link via email
|
|
||||||
4. Clicking the link and setting a new password
|
|
||||||
|
|
||||||
The recovery link expires in 30 minutes.
|
|
||||||
Emails are sent via Mailgun SMTP (noreply@mg.vrije.cloud)
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Configure Authentik recovery flow.
|
|
||||||
Verifies that the default recovery flow exists (Authentik creates it by default).
|
|
||||||
The recovery flow is used when clicking "Create recovery link" in the UI.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
|
|
||||||
def api_request(base_url, token, path, method='GET', data=None):
|
|
||||||
"""Make API request to Authentik"""
|
|
||||||
url = f"{base_url}{path}"
|
|
||||||
headers = {
|
|
||||||
'Authorization': f'Bearer {token}',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
request_data = json.dumps(data).encode() if data else None
|
|
||||||
req = urllib.request.Request(url, data=request_data, headers=headers, method=method)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
||||||
return resp.status, json.loads(resp.read().decode())
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
error_body = e.read().decode()
|
|
||||||
try:
|
|
||||||
error_data = json.loads(error_body)
|
|
||||||
except:
|
|
||||||
error_data = {'error': error_body}
|
|
||||||
return e.code, error_data
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print(json.dumps({'error': 'Usage: configure_recovery_flow.py <base_url> <api_token>'}), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
base_url = sys.argv[1]
|
|
||||||
token = sys.argv[2]
|
|
||||||
|
|
||||||
# Get the default recovery flow (created by Authentik by default)
|
|
||||||
status, flows_response = api_request(base_url, token, '/api/v3/flows/instances/')
|
|
||||||
if status != 200:
|
|
||||||
print(json.dumps({'error': 'Failed to list flows', 'details': flows_response}), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
recovery_flow = next((f for f in flows_response.get('results', [])
|
|
||||||
if f.get('designation') == 'recovery'), None)
|
|
||||||
|
|
||||||
if not recovery_flow:
|
|
||||||
print(json.dumps({'error': 'No recovery flow found - Authentik should create one by default'}), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
flow_slug = recovery_flow['slug']
|
|
||||||
flow_pk = recovery_flow['pk']
|
|
||||||
|
|
||||||
print(json.dumps({
|
|
||||||
'success': True,
|
|
||||||
'message': 'Recovery flow configured',
|
|
||||||
'flow_slug': flow_slug,
|
|
||||||
'flow_pk': flow_pk,
|
|
||||||
'note': 'Using Authentik default recovery flow'
|
|
||||||
}))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -2,7 +2,7 @@ version: 1
|
||||||
metadata:
|
metadata:
|
||||||
name: custom-flow-configuration
|
name: custom-flow-configuration
|
||||||
labels:
|
labels:
|
||||||
blueprints.goauthentik.io/description: "Configure invitation, recovery, and 2FA enforcement"
|
blueprints.goauthentik.io/description: "Configure invitation and 2FA enforcement"
|
||||||
blueprints.goauthentik.io/instantiate: "true"
|
blueprints.goauthentik.io/instantiate: "true"
|
||||||
|
|
||||||
entries:
|
entries:
|
||||||
|
|
@ -26,15 +26,7 @@ entries:
|
||||||
evaluate_on_plan: true
|
evaluate_on_plan: true
|
||||||
re_evaluate_policies: false
|
re_evaluate_policies: false
|
||||||
|
|
||||||
# 3. SET RECOVERY FLOW IN BRAND
|
# 3. ENFORCE 2FA CONFIGURATION
|
||||||
# Configures the default brand to use the recovery flow
|
|
||||||
- model: authentik_core.brand
|
|
||||||
identifiers:
|
|
||||||
domain: authentik-default
|
|
||||||
attrs:
|
|
||||||
flow_recovery: !Find [authentik_flows.flow, [designation, recovery]]
|
|
||||||
|
|
||||||
# 4. ENFORCE 2FA CONFIGURATION
|
|
||||||
# Updates MFA validation stage to force users to configure TOTP
|
# Updates MFA validation stage to force users to configure TOTP
|
||||||
- model: authentik_stages_authenticator_validate.authenticatorvalidatestage
|
- model: authentik_stages_authenticator_validate.authenticatorvalidatestage
|
||||||
identifiers:
|
identifiers:
|
||||||
|
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
version: 1
|
|
||||||
metadata:
|
|
||||||
name: password-recovery-flow
|
|
||||||
labels:
|
|
||||||
blueprints.goauthentik.io/description: "Password recovery flow with email link"
|
|
||||||
blueprints.goauthentik.io/instantiate: "true"
|
|
||||||
|
|
||||||
entries:
|
|
||||||
# 1. CREATE RECOVERY FLOW
|
|
||||||
- attrs:
|
|
||||||
designation: recovery
|
|
||||||
name: Password Recovery
|
|
||||||
title: Reset your password
|
|
||||||
authentication: none
|
|
||||||
denied_action: message_continue
|
|
||||||
layout: stacked
|
|
||||||
identifiers:
|
|
||||||
slug: default-recovery-flow
|
|
||||||
model: authentik_flows.flow
|
|
||||||
id: recovery-flow
|
|
||||||
|
|
||||||
# 2. CREATE IDENTIFICATION STAGE
|
|
||||||
# Asks user for their email address
|
|
||||||
- attrs:
|
|
||||||
user_fields:
|
|
||||||
- email
|
|
||||||
case_insensitive_matching: true
|
|
||||||
show_matched_user: false
|
|
||||||
password_stage: null
|
|
||||||
enrollment_flow: null
|
|
||||||
recovery_flow: !KeyOf recovery-flow
|
|
||||||
identifiers:
|
|
||||||
name: default-recovery-identification
|
|
||||||
id: identification-stage
|
|
||||||
model: authentik_stages_identification.identificationstage
|
|
||||||
|
|
||||||
# 3. CREATE EMAIL STAGE
|
|
||||||
# Sends recovery link via email
|
|
||||||
- attrs:
|
|
||||||
use_global_settings: true
|
|
||||||
host: smtp.mailgun.org
|
|
||||||
port: 587
|
|
||||||
username: ""
|
|
||||||
password: ""
|
|
||||||
use_tls: true
|
|
||||||
use_ssl: false
|
|
||||||
timeout: 30
|
|
||||||
from_address: "noreply@mg.vrije.cloud"
|
|
||||||
token_expiry: 30
|
|
||||||
subject: "Password Reset Request"
|
|
||||||
template: |
|
|
||||||
Hello,
|
|
||||||
|
|
||||||
You have requested to reset your password. Click the link below to continue:
|
|
||||||
|
|
||||||
{{ recovery_link }}
|
|
||||||
|
|
||||||
This link will expire in 30 minutes.
|
|
||||||
|
|
||||||
If you did not request this password reset, please ignore this email.
|
|
||||||
|
|
||||||
Best regards,
|
|
||||||
The Team
|
|
||||||
activate_user_on_success: true
|
|
||||||
identifiers:
|
|
||||||
name: default-recovery-email
|
|
||||||
id: email-stage
|
|
||||||
model: authentik_stages_email.emailstage
|
|
||||||
|
|
||||||
# 4. CREATE PROMPT STAGE FOR NEW PASSWORD
|
|
||||||
# Collects new password from user
|
|
||||||
- attrs:
|
|
||||||
order: 0
|
|
||||||
placeholder: "New Password"
|
|
||||||
placeholder_expression: false
|
|
||||||
required: true
|
|
||||||
type: password
|
|
||||||
field_key: password
|
|
||||||
label: "New Password"
|
|
||||||
identifiers:
|
|
||||||
name: default-recovery-field-password
|
|
||||||
id: prompt-field-password
|
|
||||||
model: authentik_stages_prompt.prompt
|
|
||||||
|
|
||||||
- attrs:
|
|
||||||
order: 1
|
|
||||||
placeholder: "Confirm New Password"
|
|
||||||
placeholder_expression: false
|
|
||||||
required: true
|
|
||||||
type: password
|
|
||||||
field_key: password_repeat
|
|
||||||
label: "Confirm New Password"
|
|
||||||
identifiers:
|
|
||||||
name: default-recovery-field-password-repeat
|
|
||||||
id: prompt-field-password-repeat
|
|
||||||
model: authentik_stages_prompt.prompt
|
|
||||||
|
|
||||||
- attrs:
|
|
||||||
fields:
|
|
||||||
- !KeyOf prompt-field-password
|
|
||||||
- !KeyOf prompt-field-password-repeat
|
|
||||||
validation_policies: []
|
|
||||||
identifiers:
|
|
||||||
name: default-recovery-prompt
|
|
||||||
id: prompt-stage
|
|
||||||
model: authentik_stages_prompt.promptstage
|
|
||||||
|
|
||||||
# 5. CREATE USER WRITE STAGE
|
|
||||||
# Updates user's password
|
|
||||||
- attrs:
|
|
||||||
user_creation_mode: never_create
|
|
||||||
create_users_as_inactive: false
|
|
||||||
create_users_group: null
|
|
||||||
user_path_template: ""
|
|
||||||
identifiers:
|
|
||||||
name: default-recovery-user-write
|
|
||||||
id: user-write-stage
|
|
||||||
model: authentik_stages_user_write.userwritestage
|
|
||||||
|
|
||||||
# 6. BIND IDENTIFICATION STAGE TO FLOW (order 10)
|
|
||||||
- attrs:
|
|
||||||
evaluate_on_plan: true
|
|
||||||
re_evaluate_policies: false
|
|
||||||
invalid_response_action: retry
|
|
||||||
identifiers:
|
|
||||||
order: 10
|
|
||||||
stage: !KeyOf identification-stage
|
|
||||||
target: !KeyOf recovery-flow
|
|
||||||
model: authentik_flows.flowstagebinding
|
|
||||||
|
|
||||||
# 7. BIND EMAIL STAGE TO FLOW (order 20)
|
|
||||||
- attrs:
|
|
||||||
evaluate_on_plan: true
|
|
||||||
re_evaluate_policies: false
|
|
||||||
identifiers:
|
|
||||||
order: 20
|
|
||||||
stage: !KeyOf email-stage
|
|
||||||
target: !KeyOf recovery-flow
|
|
||||||
model: authentik_flows.flowstagebinding
|
|
||||||
|
|
||||||
# 8. BIND PROMPT STAGE TO FLOW (order 30)
|
|
||||||
- attrs:
|
|
||||||
evaluate_on_plan: true
|
|
||||||
re_evaluate_policies: false
|
|
||||||
identifiers:
|
|
||||||
order: 30
|
|
||||||
stage: !KeyOf prompt-stage
|
|
||||||
target: !KeyOf recovery-flow
|
|
||||||
model: authentik_flows.flowstagebinding
|
|
||||||
|
|
||||||
# 9. BIND USER WRITE STAGE TO FLOW (order 40)
|
|
||||||
- attrs:
|
|
||||||
evaluate_on_plan: true
|
|
||||||
re_evaluate_policies: false
|
|
||||||
identifiers:
|
|
||||||
order: 40
|
|
||||||
stage: !KeyOf user-write-stage
|
|
||||||
target: !KeyOf recovery-flow
|
|
||||||
model: authentik_flows.flowstagebinding
|
|
||||||
|
|
||||||
# 10. SET AS DEFAULT RECOVERY FLOW IN BRAND
|
|
||||||
- attrs:
|
|
||||||
flow_recovery: !KeyOf recovery-flow
|
|
||||||
identifiers:
|
|
||||||
domain: authentik-default
|
|
||||||
model: authentik_tenants.tenant
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
# Configure Authentik flows (invitation, recovery, 2FA) via Blueprints
|
# Configure Authentik flows (invitation, 2FA) via Blueprints
|
||||||
|
|
||||||
- name: Use bootstrap token for API access
|
- name: Use bootstrap token for API access
|
||||||
set_fact:
|
set_fact:
|
||||||
|
|
@ -35,7 +35,6 @@
|
||||||
loop:
|
loop:
|
||||||
- custom-flows.yaml
|
- custom-flows.yaml
|
||||||
- enrollment-flow.yaml
|
- enrollment-flow.yaml
|
||||||
- recovery-flow.yaml
|
|
||||||
register: blueprints_copied
|
register: blueprints_copied
|
||||||
|
|
||||||
- name: Copy blueprints into authentik-worker container
|
- name: Copy blueprints into authentik-worker container
|
||||||
|
|
@ -44,7 +43,6 @@
|
||||||
loop:
|
loop:
|
||||||
- custom-flows.yaml
|
- custom-flows.yaml
|
||||||
- enrollment-flow.yaml
|
- enrollment-flow.yaml
|
||||||
- recovery-flow.yaml
|
|
||||||
when: blueprints_copied.changed
|
when: blueprints_copied.changed
|
||||||
|
|
||||||
- name: Copy blueprints into authentik-server container
|
- name: Copy blueprints into authentik-server container
|
||||||
|
|
@ -53,7 +51,6 @@
|
||||||
loop:
|
loop:
|
||||||
- custom-flows.yaml
|
- custom-flows.yaml
|
||||||
- enrollment-flow.yaml
|
- enrollment-flow.yaml
|
||||||
- recovery-flow.yaml
|
|
||||||
when: blueprints_copied.changed
|
when: blueprints_copied.changed
|
||||||
|
|
||||||
- name: Wait for blueprint to be discovered and applied
|
- name: Wait for blueprint to be discovered and applied
|
||||||
|
|
@ -99,14 +96,6 @@
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Verify brand recovery flow was set
|
|
||||||
shell: |
|
|
||||||
docker exec authentik-server curl -sf -H "Authorization: Bearer {{ authentik_api_token }}" \
|
|
||||||
"http://localhost:9000/api/v3/core/brands/" | \
|
|
||||||
python3 -c "import sys, json; data = json.load(sys.stdin); brand = data['results'][0] if data['results'] else {}; print(json.dumps({'recovery_flow_set': brand.get('flow_recovery') is not None}))"
|
|
||||||
register: recovery_check
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Display flows configuration status
|
- name: Display flows configuration status
|
||||||
debug:
|
debug:
|
||||||
|
|
@ -119,21 +108,19 @@
|
||||||
Blueprints Deployed:
|
Blueprints Deployed:
|
||||||
- /blueprints/custom-flows.yaml (2FA enforcement)
|
- /blueprints/custom-flows.yaml (2FA enforcement)
|
||||||
- /blueprints/enrollment-flow.yaml (invitation-only registration)
|
- /blueprints/enrollment-flow.yaml (invitation-only registration)
|
||||||
- /blueprints/recovery-flow.yaml (password reset via email)
|
|
||||||
|
|
||||||
✓ Blueprints Deployed: {{ blueprints_copied.changed }}
|
✓ Blueprints Deployed: {{ blueprints_copied.changed }}
|
||||||
✓ Blueprints Applied: {{ 'Yes' if 'successfully' in blueprint_wait.stdout else 'In Progress' }}
|
✓ Blueprints Applied: {{ 'Yes' if 'successfully' in blueprint_wait.stdout else 'In Progress' }}
|
||||||
|
|
||||||
Verification:
|
Verification:
|
||||||
{{ invitation_check.stdout | default('Invitation stage: Checking...') }}
|
{{ invitation_check.stdout | default('Invitation stage: Checking...') }}
|
||||||
{{ recovery_check.stdout | default('Recovery flow: Checking...') }}
|
|
||||||
|
|
||||||
Note: Authentik applies blueprints asynchronously.
|
Note: Authentik applies blueprints asynchronously.
|
||||||
Changes should be visible within 1-2 minutes.
|
Changes should be visible within 1-2 minutes.
|
||||||
|
Recovery flows must be configured manually in Authentik admin UI.
|
||||||
|
|
||||||
Flow URLs:
|
Flow URLs:
|
||||||
- Enrollment: https://{{ authentik_domain }}/if/flow/default-enrollment-flow/
|
- Enrollment: https://{{ authentik_domain }}/if/flow/default-enrollment-flow/
|
||||||
- Recovery: https://{{ authentik_domain }}/if/flow/default-recovery-flow/
|
|
||||||
|
|
||||||
Email configuration is active - emails sent via Mailgun SMTP.
|
Email configuration is active - emails sent via Mailgun SMTP.
|
||||||
========================================
|
========================================
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
when: mailgun_smtp_user is defined or (client_secrets.mailgun_smtp_user is defined and client_secrets.mailgun_smtp_user != "" and "PLACEHOLDER" not in client_secrets.mailgun_smtp_user)
|
when: mailgun_smtp_user is defined or (client_secrets.mailgun_smtp_user is defined and client_secrets.mailgun_smtp_user != "" and "PLACEHOLDER" not in client_secrets.mailgun_smtp_user)
|
||||||
tags: ['authentik', 'email']
|
tags: ['authentik', 'email']
|
||||||
|
|
||||||
- name: Include flows configuration (recovery, invitation)
|
- name: Include flows configuration (invitation, 2FA)
|
||||||
include_tasks: flows.yml
|
include_tasks: flows.yml
|
||||||
when: authentik_bootstrap | default(true)
|
when: authentik_bootstrap | default(true)
|
||||||
tags: ['authentik', 'flows']
|
tags: ['authentik', 'flows']
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue