diff --git a/README.md b/README.md index 03dcea9..3aef9ab 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ terraform apply ├── .github/workflows/deploy.yml # CI/CD pipeline ├── main.tf # Authentik provider & brand config ├── variables.tf # All configurable variables +├── authentication-flow.tf # Custom MFA authentication flow ├── security-policies.tf # Password, MFA, brute-force policies ├── rbac-groups.tf # RBAC groups and access policies ├── app-*.tf # Application configurations @@ -170,11 +171,34 @@ This configuration includes enterprise-grade security controls: - Execution logging for audit trail ### To Enable MFA Enforcement: + +**Option 1: Use the Custom MFA Authentication Flow (Recommended)** + +Set in `terraform.tfvars`: +```hcl +enable_mfa_flow = true +mfa_enforcement = "configure" # or "deny" for strict enforcement +``` + +This creates a complete authentication flow with: +- User identification → Password → MFA validation → Session creation +- Brute-force protection policy binding +- Configurable MFA enforcement level + +**Option 2: Manual Configuration** 1. Deploy these policies with `terraform apply` 2. In Authentik UI: Edit your authentication flow 3. Add the `mfa-validation` stage after the password stage 4. Set `not_configured_action` to `deny` for strict enforcement +### MFA Enforcement Levels + +| Level | Behavior | +|-------|----------| +| `skip` | MFA optional, no prompt if not configured | +| `configure` | Prompts users to set up MFA on login (recommended for rollout) | +| `deny` | Blocks login if MFA not configured (use after users have set up MFA) | + ## RBAC Groups (rbac-groups.tf) Role-based access control with three predefined groups: diff --git a/authentication-flow.tf b/authentication-flow.tf new file mode 100644 index 0000000..e7314c5 --- /dev/null +++ b/authentication-flow.tf @@ -0,0 +1,109 @@ +# ============================================================================= +# Custom Authentication Flow with MFA +# Creates a secure authentication flow that requires MFA +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Custom Authentication Flow - With MFA Enforcement +# ----------------------------------------------------------------------------- +resource "authentik_flow" "mfa_authentication" { + name = "mfa-authentication-flow" + title = "Welcome! Please sign in." + slug = "mfa-authentication-flow" + designation = "authentication" + + # Background and styling + background = "/static/dist/assets/images/flow_background.jpg" + + # Policy behavior + policy_engine_mode = "all" + + # Compatibility mode for older clients + compatibility_mode = true +} + +# ----------------------------------------------------------------------------- +# Stage: User Identification +# First stage - user enters username/email +# ----------------------------------------------------------------------------- +data "authentik_stage" "identification" { + name = "default-authentication-identification" +} + +resource "authentik_flow_stage_binding" "identification" { + target = authentik_flow.mfa_authentication.uuid + stage = data.authentik_stage.identification.id + order = 10 +} + +# ----------------------------------------------------------------------------- +# Stage: Password Authentication +# Second stage - user enters password +# ----------------------------------------------------------------------------- +data "authentik_stage" "password" { + name = "default-authentication-password" +} + +resource "authentik_flow_stage_binding" "password" { + target = authentik_flow.mfa_authentication.uuid + stage = data.authentik_stage.password.id + order = 20 +} + +# ----------------------------------------------------------------------------- +# Stage: MFA Validation +# Third stage - require MFA (TOTP, WebAuthn, or recovery code) +# Uses the mfa_validation stage from security-policies.tf +# ----------------------------------------------------------------------------- +resource "authentik_flow_stage_binding" "mfa" { + target = authentik_flow.mfa_authentication.uuid + stage = authentik_stage_authenticator_validate.mfa_validation.id + order = 30 + + # Optional: Evaluate on plan to check user context + evaluate_on_plan = true + + # Re-evaluate policies each time + re_evaluate_policies = true +} + +# ----------------------------------------------------------------------------- +# Stage: User Login (Session Creation) +# Final stage - create user session after successful auth +# ----------------------------------------------------------------------------- +data "authentik_stage" "user_login" { + name = "default-authentication-login" +} + +resource "authentik_flow_stage_binding" "login" { + target = authentik_flow.mfa_authentication.uuid + stage = data.authentik_stage.user_login.id + order = 40 +} + +# ----------------------------------------------------------------------------- +# Policy Binding: Reputation Check (Anti-Brute Force) +# Bound to the flow, checks reputation before allowing auth +# ----------------------------------------------------------------------------- +resource "authentik_policy_binding" "reputation_check" { + target = authentik_flow.mfa_authentication.uuid + policy = authentik_policy_reputation.brute_force_protection.id + order = 0 + + # Fail closed - if policy errors, deny access + negate = false + timeout = 30 +} + +# ----------------------------------------------------------------------------- +# Outputs +# ----------------------------------------------------------------------------- +output "mfa_authentication_flow_id" { + description = "ID of the MFA authentication flow" + value = authentik_flow.mfa_authentication.uuid +} + +output "mfa_authentication_flow_slug" { + description = "Slug of the MFA authentication flow - use in brand configuration" + value = authentik_flow.mfa_authentication.slug +} diff --git a/main.tf b/main.tf index 57b6831..8a3a80c 100644 --- a/main.tf +++ b/main.tf @@ -58,7 +58,8 @@ resource "authentik_brand" "main" { branding_logo = "/static/dist/assets/icons/icon_left_brand.svg" branding_favicon = "/static/dist/assets/icons/icon.png" - flow_authentication = data.authentik_flow.default_authentication.id + # Use MFA auth flow if enabled, otherwise default + flow_authentication = var.enable_mfa_flow ? authentik_flow.mfa_authentication.uuid : data.authentik_flow.default_authentication.id flow_invalidation = data.authentik_flow.default_invalidation.id } diff --git a/rbac-groups.tf b/rbac-groups.tf index 0fb03ec..b9106a7 100644 --- a/rbac-groups.tf +++ b/rbac-groups.tf @@ -9,20 +9,20 @@ # Media group - access to Sonarr, Radarr, Prowlarr, etc. resource "authentik_group" "media" { - name = "Media" - parent = authentik_group.users.id + name = "Media" + parents = [authentik_group.users.id] } # Infrastructure group - access to monitoring, CI/CD tools resource "authentik_group" "infrastructure" { - name = "Infrastructure" - parent = authentik_group.users.id + name = "Infrastructure" + parents = [authentik_group.users.id] } # Home Automation group - Home Assistant access resource "authentik_group" "home_automation" { - name = "Home Automation" - parent = authentik_group.users.id + name = "Home Automation" + parents = [authentik_group.users.id] } # ----------------------------------------------------------------------------- diff --git a/security-policies.tf b/security-policies.tf index a146e63..2b0da30 100644 --- a/security-policies.tf +++ b/security-policies.tf @@ -99,7 +99,7 @@ resource "authentik_stage_authenticator_validate" "mfa_validation" { # - skip: Don't require MFA if not configured # - deny: Block login if MFA not configured (after enabling, users need MFA) # - configure: Force users to set up MFA if not configured - not_configured_action = "configure" + not_configured_action = var.mfa_enforcement # Supported authenticator types device_classes = [ diff --git a/source-google.tf b/source-google.tf index d4be0f9..ef66ba7 100644 --- a/source-google.tf +++ b/source-google.tf @@ -3,16 +3,18 @@ # Allow users to sign in with their Google Workspace accounts # ============================================================================= -# Google OAuth Source +# Google OAuth Source - Only created if credentials are provided resource "authentik_source_oauth" "google" { + count = var.google_client_id != "" ? 1 : 0 + name = "Google Workspace" slug = "google" authentication_flow = data.authentik_flow.default_authentication.id enrollment_flow = data.authentik_flow.default_enrollment.id provider_type = "google" - consumer_key = data.sops_file.secrets.data["google_client_id"] - consumer_secret = data.sops_file.secrets.data["google_client_secret"] + consumer_key = var.google_client_id + consumer_secret = var.google_client_secret # PKCE method - S256 is recommended pkce = "S256" diff --git a/terraform.tfvars.example b/terraform.tfvars.example index efba978..864abb4 100644 --- a/terraform.tfvars.example +++ b/terraform.tfvars.example @@ -22,3 +22,10 @@ portainer_url = "https://portainer.example.com" # LDAP Configuration ldap_base_dn = "dc=ldap,dc=example,dc=com" + +# Security Configuration +enable_mfa_flow = false # Set to true to enable MFA authentication flow +mfa_enforcement = "configure" # Options: skip, configure, deny +# - skip: MFA optional, no prompt if not configured +# - configure: Prompt users to set up MFA on login +# - deny: Block login if MFA not configured (use after users have set up MFA) diff --git a/variables.tf b/variables.tf index 783fd2a..8f23cbd 100644 --- a/variables.tf +++ b/variables.tf @@ -94,3 +94,20 @@ variable "ldap_base_dn" { default = "dc=ldap,dc=example,dc=com" description = "LDAP base DN" } + +# Security Configuration +variable "enable_mfa_flow" { + type = bool + default = false + description = "Use custom MFA authentication flow instead of default" +} + +variable "mfa_enforcement" { + type = string + default = "configure" + description = "MFA enforcement mode: 'skip' (optional), 'configure' (prompt to set up), 'deny' (required)" + validation { + condition = contains(["skip", "configure", "deny"], var.mfa_enforcement) + error_message = "MFA enforcement must be one of: skip, configure, deny" + } +}