This commit is contained in:
Tiara Rodney 2026-03-14 05:38:45 +01:00
commit 883f31932e
No known key found for this signature in database
GPG key ID: 5CD8EC1D46106723
169 changed files with 5676 additions and 0 deletions

View file

@ -0,0 +1,9 @@
---
install_dir: /opt/authentik
bind_address: "0.0.0.0"
port: 9000
postgres_shared_buffers: 64MB
postgres_work_mem: 4MB
postgres_maintenance_work_mem: 32MB
postgres_effective_cache_size: 256MB
redis_maxmemory: 80mb

View file

@ -0,0 +1,4 @@
---
dependencies:
-
role: docker

View file

@ -0,0 +1,92 @@
---
-
name: Ensure install directory exists
file:
path: "{{ install_dir }}"
state: directory
mode: "0755"
-
name: Deploy environment file
template:
src: env.j2
dest: "{{ install_dir }}/.env"
-
name: Ensure blueprints directory exists
file:
path: "{{ install_dir }}/blueprints"
state: directory
mode: "0755"
-
name: Deploy OAuth2 blueprint
template:
src: blueprint-oauth2.yml.j2
dest: "{{ install_dir }}/blueprints/oauth2-applications.yaml"
when: oauth_applications is defined and oauth_applications | length > 0
-
name: Deploy enrollment blueprint
template:
src: blueprint-enrollment.yml.j2
dest: "{{ install_dir }}/blueprints/enrollment.yaml"
-
name: Deploy social login blueprint
template:
src: blueprint-social-logins.yml.j2
dest: "{{ install_dir }}/blueprints/social-logins.yaml"
when: social_login_sources is defined and social_login_sources | length > 0
-
name: Ensure media directory exists
file:
path: "{{ install_dir }}/media/public"
state: directory
mode: "0755"
-
name: Copy branding assets
copy:
src: branding/
dest: "{{ install_dir }}/media/public/"
mode: "0644"
when: branding_assets | default(false)
-
name: Ensure custom-templates email directory exists
file:
path: "{{ install_dir }}/custom-templates/email"
state: directory
mode: "0755"
-
name: Deploy custom email templates
template:
src: "email/{{ item }}.j2"
dest: "{{ install_dir }}/custom-templates/email/{{ item }}"
loop:
- account-confirmation.html
- password-reset.html
-
name: Deploy docker-compose file
template:
src: docker-compose.yml.j2
dest: "{{ install_dir }}/docker-compose.yml"
-
name: Start Authentik stack
include_role:
name: docker
tasks_from: start-compose
vars:
compose_project_dir: "{{ install_dir }}"
-
name: Deploy Authentik backup script
template:
src: backup.sh.j2
dest: /etc/restic/pre-backup.d/authentik.sh
mode: "0755"

View file

@ -0,0 +1,4 @@
---
-
name: Deploy Authentik
ansible.builtin.include_tasks: deploy-authentik.yml

View file

@ -0,0 +1,46 @@
---
-
name: Set backup staging directory
set_fact:
_authentik_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/authentik"
-
name: Stop Authentik stack
community.docker.docker_compose_v2:
project_src: "{{ install_dir }}"
state: absent
-
name: Restore config files
copy:
src: "{{ _authentik_backup_dir }}/{{ item }}"
dest: "{{ install_dir }}/{{ item }}"
remote_src: yes
mode: "0600"
loop:
- .env
- docker-compose.yml
-
name: Start Postgres only
command: >
docker compose -f {{ install_dir }}/docker-compose.yml
up -d postgres
-
name: Wait for Postgres to be ready
pause:
seconds: 10
-
name: Restore Postgres dump
shell: >
docker compose -f {{ install_dir }}/docker-compose.yml
exec -T postgres psql -U authentik authentik
< {{ _authentik_backup_dir }}/authentik.sql
-
name: Start full Authentik stack
community.docker.docker_compose_v2:
project_src: "{{ install_dir }}"
state: present

View file

@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
BACKUP_DIR="{{ backup_staging_dir | default('/var/backups') }}/authentik"
mkdir -p "$BACKUP_DIR"
docker compose -f {{ install_dir }}/docker-compose.yml exec -T postgres pg_dump -U authentik authentik > "$BACKUP_DIR/authentik.sql"
cp "{{ install_dir }}/.env" "$BACKUP_DIR/.env"
cp "{{ install_dir }}/docker-compose.yml" "$BACKUP_DIR/docker-compose.yml"

View file

@ -0,0 +1,326 @@
version: 1
metadata:
name: enrollment-flow
entries:
# --- Brand configuration ---
- model: authentik_brands.brand
identifiers:
domain: authentik-default
state: present
attrs:
branding_title: {{ domain }}
{% if branding_logo is defined %}
branding_logo: {{ branding_logo }}
{% endif %}
{% if branding_favicon is defined %}
branding_favicon: {{ branding_favicon }}
{% endif %}
# --- Enrollment flow ---
- model: authentik_flows.flow
id: enrollment-flow
identifiers:
slug: default-enrollment-flow
attrs:
name: Sign Up
title: Create an account
designation: enrollment
authentication: require_unauthenticated
# --- Recovery flow ---
- model: authentik_flows.flow
id: recovery-flow
identifiers:
slug: default-recovery-flow
attrs:
name: Password Recovery
title: Reset your password
designation: recovery
authentication: require_unauthenticated
# --- Prompt fields ---
- model: authentik_stages_prompt.prompt
id: field-email
identifiers:
name: enrollment-field-email
attrs:
field_key: email
label: Email
type: email
required: true
placeholder: Email
placeholder_expression: false
order: 0
- model: authentik_stages_prompt.prompt
id: field-username
identifiers:
name: enrollment-field-username
attrs:
field_key: username
label: Username
type: username
required: true
placeholder: Username
placeholder_expression: false
order: 1
- model: authentik_stages_prompt.prompt
id: field-password
identifiers:
name: enrollment-field-password
attrs:
field_key: password
label: Password
type: password
required: true
placeholder: Password
placeholder_expression: false
order: 2
- model: authentik_stages_prompt.prompt
id: field-password-repeat
identifiers:
name: enrollment-field-password-repeat
attrs:
field_key: password_repeat
label: Password (repeat)
type: password
required: true
placeholder: Password (repeat)
placeholder_expression: false
order: 3
# --- Password policy ---
- model: authentik_policies_password.passwordpolicy
id: password-policy
identifiers:
name: enrollment-password-policy
attrs:
name: enrollment-password-policy
length_min: 10
amount_uppercase: 1
amount_lowercase: 1
amount_digits: 1
amount_symbols: 1
check_static_rules: true
check_have_i_been_pwned: true
check_zxcvbn: true
zxcvbn_score_threshold: 3
error_message: "Password must be at least 10 characters with uppercase, lowercase, digit, and symbol."
# --- Enrollment stages ---
- model: authentik_stages_prompt.promptstage
id: enrollment-prompt-stage
identifiers:
name: enrollment-prompt
attrs:
fields:
- !KeyOf field-email
- !KeyOf field-username
- !KeyOf field-password
- !KeyOf field-password-repeat
validation_policies:
- !KeyOf password-policy
- model: authentik_stages_user_write.userwritestage
id: enrollment-user-write
identifiers:
name: enrollment-user-write
attrs:
user_creation_mode: always_create
create_users_as_inactive: true
- model: authentik_stages_email.emailstage
id: enrollment-email-verification
identifiers:
name: enrollment-email-verification
attrs:
use_global_settings: true
activate_user_on_success: true
subject: Verify your email address
template: email/account-confirmation.html
- model: authentik_stages_user_login.userloginstage
id: enrollment-user-login
identifiers:
name: enrollment-user-login
# --- Enrollment flow stage bindings ---
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-prompt-stage
order: 10
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-user-write
order: 20
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-email-verification
order: 30
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-user-login
order: 100
# --- Recovery stages ---
- model: authentik_stages_identification.identificationstage
id: recovery-identification
identifiers:
name: recovery-identification
attrs:
user_fields:
- email
- model: authentik_stages_email.emailstage
id: recovery-email
identifiers:
name: recovery-email
attrs:
use_global_settings: true
subject: Reset your password
template: email/password-reset.html
- model: authentik_stages_prompt.prompt
id: field-recovery-password
identifiers:
name: recovery-field-password
attrs:
field_key: password
label: New Password
type: password
required: true
placeholder: New Password
placeholder_expression: false
order: 0
- model: authentik_stages_prompt.prompt
id: field-recovery-password-repeat
identifiers:
name: recovery-field-password-repeat
attrs:
field_key: password_repeat
label: New Password (repeat)
type: password
required: true
placeholder: New Password (repeat)
placeholder_expression: false
order: 1
- model: authentik_stages_prompt.promptstage
id: recovery-password-stage
identifiers:
name: recovery-password-prompt
attrs:
fields:
- !KeyOf field-recovery-password
- !KeyOf field-recovery-password-repeat
validation_policies:
- !KeyOf password-policy
- model: authentik_stages_user_write.userwritestage
id: recovery-user-write
identifiers:
name: recovery-user-write
attrs:
user_creation_mode: never_create
- model: authentik_stages_user_login.userloginstage
id: recovery-user-login
identifiers:
name: recovery-user-login
# --- Recovery flow stage bindings ---
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf recovery-identification
order: 10
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf recovery-email
order: 20
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf recovery-password-stage
order: 30
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf recovery-user-write
order: 40
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf recovery-user-login
order: 100
# --- Unenrollment (account deletion) flow ---
- model: authentik_flows.flow
id: unenrollment-flow
identifiers:
slug: default-unenrollment-flow
attrs:
name: Delete Account
title: Delete your account
designation: unenrollment
- model: authentik_stages_consent.consentstage
id: unenrollment-consent
identifiers:
name: unenrollment-consent
attrs:
mode: always_require
- model: authentik_stages_user_delete.userdeletestage
id: unenrollment-user-delete
identifiers:
name: unenrollment-user-delete
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf unenrollment-flow
stage: !KeyOf unenrollment-consent
order: 10
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf unenrollment-flow
stage: !KeyOf unenrollment-user-delete
order: 20
# --- Set recovery and unenrollment flows on brand ---
- model: authentik_brands.brand
identifiers:
domain: authentik-default
state: present
attrs:
flow_recovery: !KeyOf recovery-flow
flow_unenrollment: !KeyOf unenrollment-flow
{% if social_login_sources is not defined or social_login_sources | length == 0 %}
# --- Bind enrollment flow to the default login identification stage ---
- model: authentik_stages_identification.identificationstage
identifiers:
name: default-authentication-identification
state: present
attrs:
user_fields:
- email
- username
enrollment_flow: !KeyOf enrollment-flow
{% endif %}

View file

@ -0,0 +1,67 @@
version: 1
metadata:
name: oauth2-applications
entries:
{% for app in oauth_applications %}
- model: authentik_providers_oauth2.oauth2provider
identifiers:
name: {{ app.name }}
state: present
attrs:
name: {{ app.name }}
client_type: {{ app.client_type | default('confidential') }}
client_id: {{ app.client_id }}
{% if app.client_secret is defined %}
client_secret: {{ app.client_secret }}
{% endif %}
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
redirect_uris:
{% for uri in app.redirect_uris %}
- url: "{{ uri }}"
matching_mode: strict
{% endfor %}
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
- model: authentik_core.application
identifiers:
slug: {{ app.slug }}
state: present
attrs:
name: {{ app.name }}
slug: {{ app.slug }}
provider: !Find [authentik_providers_oauth2.oauth2provider, [name, {{ app.name }}]]
policy_engine_mode: any
{% endfor %}
- model: authentik_policies_expression.expressionpolicy
identifiers:
name: enforce-unique-email
state: present
attrs:
name: enforce-unique-email
expression: |
from authentik.core.models import User
email = request.context.get("prompt_data", {}).get("email", "")
if not email:
return True
if User.objects.filter(email=email).exists():
ak_message("This email address is already in use.")
return False
return True
execution_logging: true
- model: authentik_stages_prompt.promptstage
identifiers:
name: default-source-enrollment-prompt
state: present
attrs:
fields:
- !Find [authentik_stages_prompt.prompt, [name, default-source-enrollment-field-username]]
validation_policies:
- !Find [authentik_policies_expression.expressionpolicy, [name, enforce-unique-email]]

View file

@ -0,0 +1,35 @@
version: 1
metadata:
name: social-login-sources
entries:
{% for source in social_login_sources %}
- model: authentik_sources_oauth.oauthsource
id: source-{{ source.slug }}
identifiers:
slug: {{ source.slug }}
state: present
attrs:
name: {{ source.name }}
slug: {{ source.slug }}
provider_type: {{ source.provider_type }}
consumer_key: {{ source.client_id }}
consumer_secret: {{ source.client_secret }}
authentication_flow: !Find [authentik_flows.flow, [slug, default-source-authentication]]
enrollment_flow: !Find [authentik_flows.flow, [slug, default-source-enrollment]]
{% endfor %}
# --- Add social login sources to the login identification stage ---
- model: authentik_stages_identification.identificationstage
identifiers:
name: default-authentication-identification
state: present
attrs:
user_fields:
- email
- username
enrollment_flow: !Find [authentik_flows.flow, [slug, default-enrollment-flow]]
recovery_flow: !Find [authentik_flows.flow, [slug, default-recovery-flow]]
sources:
{% for source in social_login_sources %}
- !KeyOf source-{{ source.slug }}
{% endfor %}

View file

@ -0,0 +1,51 @@
services:
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: "${AUTHENTIK_POSTGRESQL__PASSWORD}"
POSTGRES_USER: "authentik"
POSTGRES_DB: "authentik"
volumes:
- postgres_data:/var/lib/postgresql/data
command: >
postgres -c shared_buffers={{ postgres_shared_buffers }}
-c work_mem={{ postgres_work_mem }}
-c maintenance_work_mem={{ postgres_maintenance_work_mem }}
-c effective_cache_size={{ postgres_effective_cache_size }}
restart: unless-stopped
redis:
image: redis:7
command: ["redis-server", "--maxmemory", "{{ redis_maxmemory }}", "--maxmemory-policy", "allkeys-lru"]
restart: unless-stopped
server:
image: ghcr.io/goauthentik/server:{{ version }}
command: server
env_file: .env
ports:
- "{{ bind_address }}:{{ port }}:9000"
volumes:
- ./blueprints:/blueprints/custom:ro
- ./media:/media:ro
- ./custom-templates:/templates:ro
depends_on:
- postgres
- redis
restart: unless-stopped
worker:
image: ghcr.io/goauthentik/server:{{ version }}
command: worker
env_file: .env
volumes:
- ./blueprints:/blueprints/custom:ro
- ./media:/media:ro
- ./custom-templates:/templates:ro
depends_on:
- postgres
- redis
restart: unless-stopped
volumes:
postgres_data:

View file

@ -0,0 +1,35 @@
{% raw %}{% load i18n %}
{% load humanize %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% blocktrans with domain=branding_title %}Verify your email - {{ domain }}{% endblocktrans %}</title>
<style>
body { margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.card { background-color: #ffffff; border-radius: 8px; padding: 40px; margin-top: 20px; }
.btn { display: inline-block; background-color: #4050b5; color: #ffffff; text-decoration: none; padding: 12px 32px; border-radius: 6px; font-weight: 600; }
.footer { text-align: center; color: #888; font-size: 13px; margin-top: 24px; }
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>{% blocktrans with domain=branding_title %}Welcome to {{ domain }}{% endblocktrans %}</h1>
<p>{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}</p>
<p>{% blocktrans %}Please click the button below to verify your email address and activate your account.{% endblocktrans %}</p>
<p style="text-align: center; margin: 32px 0;">
<a href="{{ url }}" class="btn">{% trans "Verify Email" %}</a>
</p>
<p>{% blocktrans with expiry=expires|naturaltime %}This link will expire {{ expiry }}.{% endblocktrans %}</p>
<p style="color: #888; font-size: 13px;">{% blocktrans %}If you did not create an account, you can safely ignore this email.{% endblocktrans %}</p>
</div>
<div class="footer">
<p>{{ branding_title }}</p>
</div>
</div>
</body>
</html>{% endraw %}

View file

@ -0,0 +1,35 @@
{% raw %}{% load i18n %}
{% load humanize %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% blocktrans with domain=branding_title %}Reset your password - {{ domain }}{% endblocktrans %}</title>
<style>
body { margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.card { background-color: #ffffff; border-radius: 8px; padding: 40px; margin-top: 20px; }
.btn { display: inline-block; background-color: #4050b5; color: #ffffff; text-decoration: none; padding: 12px 32px; border-radius: 6px; font-weight: 600; }
.footer { text-align: center; color: #888; font-size: 13px; margin-top: 24px; }
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>{% trans "Reset your password" %}</h1>
<p>{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}</p>
<p>{% blocktrans with domain=branding_title %}A password reset was requested for your account on {{ domain }}.{% endblocktrans %}</p>
<p style="text-align: center; margin: 32px 0;">
<a href="{{ url }}" class="btn">{% trans "Reset Password" %}</a>
</p>
<p>{% blocktrans with expiry=expires|naturaltime %}This link will expire {{ expiry }}.{% endblocktrans %}</p>
<p style="color: #888; font-size: 13px;">{% blocktrans %}If you did not request a password reset, you can safely ignore this email.{% endblocktrans %}</p>
</div>
<div class="footer">
<p>{{ branding_title }}</p>
</div>
</div>
</body>
</html>{% endraw %}

View file

@ -0,0 +1,19 @@
AUTHENTIK_SECRET_KEY={{ secret_key }}
AUTHENTIK_POSTGRESQL__PASSWORD={{ pg_password }}
AUTHENTIK_POSTGRESQL__HOST=postgres
AUTHENTIK_POSTGRESQL__USER=authentik
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_REDIS__HOST=redis
AUTHENTIK_HOST={{ domain }}
AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS=10.0.0.0/24,172.16.0.0/12
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
AUTHENTIK_DISABLE_UPDATE_CHECK=true
AUTHENTIK_ERROR_REPORTING__ENABLED=false
{% if smtp_host is defined %}
AUTHENTIK_EMAIL__HOST={{ smtp_host }}
AUTHENTIK_EMAIL__PORT={{ smtp_port | default(587) }}
AUTHENTIK_EMAIL__USERNAME={{ smtp_username }}
AUTHENTIK_EMAIL__PASSWORD={{ smtp_password }}
AUTHENTIK_EMAIL__USE_TLS=true
AUTHENTIK_EMAIL__FROM={{ smtp_from | default(smtp_username) }}
{% endif %}