From 9a9a47a6a417018cb42771b8052b75a7883b33ce Mon Sep 17 00:00:00 2001 From: "Data (Clawdbot)" Date: Mon, 2 Feb 2026 16:05:04 +0000 Subject: [PATCH] feat(security): add comprehensive security policies and RBAC - Add security-policies.tf: - Strong password policy (12 chars, HIBP check, zxcvbn scoring) - Password reuse prevention (last 5 passwords) - Brute force protection (reputation policy, 5 attempt threshold) - MFA stages: TOTP, WebAuthn/Passkeys, recovery codes - MFA validation stage with configurable enforcement - Admin-only and MFA-required expression policies - Add rbac-groups.tf: - Media group (Sonarr, Radarr, etc.) - Infrastructure group (Grafana, ArgoCD, etc.) - Home Automation group (Home Assistant) - Group-based access policies - Fix main.tf: Remove SOPS, use variables for token - Fix versions.tf: Remove unused SOPS provider - Update README with security documentation --- README.md | 54 +++++++++++++ main.tf | 12 +-- rbac-groups.tf | 89 ++++++++++++++++++++++ security-policies.tf | 178 +++++++++++++++++++++++++++++++++++++++++++ versions.tf | 4 - 5 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 rbac-groups.tf create mode 100644 security-policies.tf diff --git a/README.md b/README.md index 5e36977..03dcea9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Infrastructure as Code for Authentik identity provider - manage applications, pr - **Proxy Authentication**: Home Assistant, Immich, Uptime Kuma, *arr stack - **LDAP Outpost**: For legacy application support - **Google OAuth Source**: Social login integration +- **Security Policies**: Strong passwords, MFA, brute-force protection +- **RBAC Groups**: Role-based access control for applications ## Quick Start @@ -76,6 +78,8 @@ terraform apply ├── .github/workflows/deploy.yml # CI/CD pipeline ├── main.tf # Authentik provider & brand config ├── variables.tf # All configurable variables +├── security-policies.tf # Password, MFA, brute-force policies +├── rbac-groups.tf # RBAC groups and access policies ├── app-*.tf # Application configurations ├── ldap-outpost.tf # LDAP outpost config ├── source-google.tf # Google OAuth source @@ -143,12 +147,62 @@ terraform { } ``` +## Security Policies (security-policies.tf) + +This configuration includes enterprise-grade security controls: + +### Password Policy +- Minimum 12 characters +- Requires uppercase, lowercase, digits, and symbols +- **Have I Been Pwned** integration - rejects breached passwords +- **zxcvbn** password strength scoring (requires "strong" level 3/4) +- Password reuse prevention (last 5 passwords) + +### Multi-Factor Authentication +- TOTP authenticator apps (Google Authenticator, Authy, etc.) +- WebAuthn/Passkeys (YubiKey, Touch ID, Windows Hello) +- Static recovery codes (10 codes, 12 characters each) +- Configurable enforcement: skip, deny, or force configuration + +### Brute Force Protection +- Reputation-based blocking after 5 failed attempts +- Blocks by IP address and username +- Execution logging for audit trail + +### To Enable MFA Enforcement: +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 + +## RBAC Groups (rbac-groups.tf) + +Role-based access control with three predefined groups: + +| Group | Purpose | Example Apps | +|-------|---------|--------------| +| Media | Media server access | Sonarr, Radarr, Prowlarr, Plex | +| Infrastructure | DevOps/monitoring | Grafana, ArgoCD, Portainer | +| Home Automation | Smart home | Home Assistant | + +Admins automatically have access to all groups. Bind policies to applications: + +```hcl +resource "authentik_policy_binding" "grafana_infra_access" { + target = authentik_application.grafana.uuid + policy = authentik_policy_expression.infrastructure_access.id + order = 0 +} +``` + ## Security Notes - Never commit `terraform.tfvars` or any file with secrets - Use GitHub Actions secrets for CI/CD - API tokens should have minimal required permissions - Rotate tokens periodically +- Enable execution logging for security audit trails +- Review login events in Authentik's Events log regularly ## Requirements diff --git a/main.tf b/main.tf index 96112cf..57b6831 100644 --- a/main.tf +++ b/main.tf @@ -3,14 +3,14 @@ # Update the domain below to match your Authentik instance # ============================================================================= -# Decrypt secrets with SOPS -data "sops_file" "secrets" { - source_file = "secrets.enc.yaml" -} - +# Authentik Provider Configuration +# Token provided via: +# - GitHub Actions secrets (CI/CD) +# - terraform.tfvars (local dev - never commit!) +# - TF_VAR_authentik_token environment variable provider "authentik" { url = var.authentik_url - token = data.sops_file.secrets.data["authentik_token"] + token = var.authentik_token } # ============================================================================= diff --git a/rbac-groups.tf b/rbac-groups.tf new file mode 100644 index 0000000..770a315 --- /dev/null +++ b/rbac-groups.tf @@ -0,0 +1,89 @@ +# ============================================================================= +# RBAC Groups and Application Permissions +# Defines user groups and their application access +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Core Groups (extend from main.tf) +# ----------------------------------------------------------------------------- + +# Media group - access to Sonarr, Radarr, Prowlarr, etc. +resource "authentik_group" "media" { + name = "Media" + parent = authentik_group.users.id +} + +# Infrastructure group - access to monitoring, CI/CD tools +resource "authentik_group" "infrastructure" { + name = "Infrastructure" + parent = authentik_group.users.id +} + +# Home Automation group - Home Assistant access +resource "authentik_group" "home_automation" { + name = "Home Automation" + parent = authentik_group.users.id +} + +# ----------------------------------------------------------------------------- +# Group-based Access Policies +# Bind these to applications to restrict access +# ----------------------------------------------------------------------------- + +resource "authentik_policy_expression" "media_access" { + name = "media-group-access" + expression = <<-EOT + return ak_is_group_member(request.user, name="Media") or ak_is_group_member(request.user, name="Admins") + EOT + execution_logging = true +} + +resource "authentik_policy_expression" "infrastructure_access" { + name = "infrastructure-group-access" + expression = <<-EOT + return ak_is_group_member(request.user, name="Infrastructure") or ak_is_group_member(request.user, name="Admins") + EOT + execution_logging = true +} + +resource "authentik_policy_expression" "home_automation_access" { + name = "home-automation-group-access" + expression = <<-EOT + return ak_is_group_member(request.user, name="Home Automation") or ak_is_group_member(request.user, name="Admins") + EOT + execution_logging = true +} + +# ----------------------------------------------------------------------------- +# Example: Bind policy to an application +# Uncomment and modify for your applications +# ----------------------------------------------------------------------------- +# resource "authentik_policy_binding" "sonarr_media_access" { +# target = authentik_application.sonarr.uuid +# policy = authentik_policy_expression.media_access.id +# order = 0 +# } +# +# resource "authentik_policy_binding" "grafana_infra_access" { +# target = authentik_application.grafana.uuid +# policy = authentik_policy_expression.infrastructure_access.id +# order = 0 +# } + +# ----------------------------------------------------------------------------- +# Outputs +# ----------------------------------------------------------------------------- +output "media_group_id" { + description = "ID of the Media group" + value = authentik_group.media.id +} + +output "infrastructure_group_id" { + description = "ID of the Infrastructure group" + value = authentik_group.infrastructure.id +} + +output "home_automation_group_id" { + description = "ID of the Home Automation group" + value = authentik_group.home_automation.id +} diff --git a/security-policies.tf b/security-policies.tf new file mode 100644 index 0000000..a146e63 --- /dev/null +++ b/security-policies.tf @@ -0,0 +1,178 @@ +# ============================================================================= +# Security Policies for Authentik +# Provides password requirements, MFA, and brute-force protection +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Password Policy - Strong requirements with breach checking +# ----------------------------------------------------------------------------- +resource "authentik_policy_password" "strong_password" { + name = "strong-password-policy" + error_message = "Password does not meet security requirements" + + # Minimum length + length_min = 12 + + # Character requirements + amount_digits = 1 + amount_lowercase = 1 + amount_uppercase = 1 + amount_symbols = 1 + + # Enable Have I Been Pwned checking + check_have_i_been_pwned = true + hibp_allowed_count = 0 # Reject any password found in breaches + + # Enable zxcvbn password strength checking + check_zxcvbn = true + zxcvbn_score_threshold = 3 # Require "strong" passwords (0-4 scale) + + check_static_rules = true + execution_logging = true +} + +# ----------------------------------------------------------------------------- +# Unique Password Policy - Prevent password reuse +# ----------------------------------------------------------------------------- +resource "authentik_policy_unique_password" "no_reuse" { + name = "no-password-reuse" + num_historical_passwords = 5 # Remember last 5 passwords + execution_logging = true +} + +# ----------------------------------------------------------------------------- +# Password Expiry Policy - Force periodic password changes (optional) +# Uncomment to enable password expiration +# ----------------------------------------------------------------------------- +# resource "authentik_policy_expiry" "password_expiry" { +# name = "password-expiry-90-days" +# days = 90 +# deny_only = false +# execution_logging = true +# } + +# ----------------------------------------------------------------------------- +# Reputation Policy - Brute force protection +# Blocks IPs/usernames after repeated failed attempts +# ----------------------------------------------------------------------------- +resource "authentik_policy_reputation" "brute_force_protection" { + name = "brute-force-protection" + check_ip = true + check_username = true + threshold = 5 # Block after 5 failed attempts + execution_logging = true +} + +# ----------------------------------------------------------------------------- +# TOTP Setup Stage - Allow users to configure MFA +# ----------------------------------------------------------------------------- +resource "authentik_stage_authenticator_totp" "totp_setup" { + name = "totp-setup" + digits = "6" + friendly_name = "Authenticator App (TOTP)" +} + +# WebAuthn/Passkeys Setup Stage +resource "authentik_stage_authenticator_webauthn" "webauthn_setup" { + name = "webauthn-setup" + friendly_name = "Security Key / Passkey" + user_verification = "preferred" + resident_key_requirement = "preferred" # Support passkeys +} + +# Static Recovery Codes Setup +resource "authentik_stage_authenticator_static" "static_setup" { + name = "static-recovery-codes" + friendly_name = "Recovery Codes" + token_count = 10 + token_length = 12 +} + +# ----------------------------------------------------------------------------- +# MFA Validation Stage - Require MFA during authentication +# Configure with "deny" to require MFA, or "configure" to prompt setup +# ----------------------------------------------------------------------------- +resource "authentik_stage_authenticator_validate" "mfa_validation" { + name = "mfa-validation" + + # Options: skip, deny, configure + # - 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" + + # Supported authenticator types + device_classes = [ + "totp", # Authenticator apps + "webauthn", # Security keys / Passkeys + "static", # Recovery codes + ] + + # Link to setup stages for "configure" action + configuration_stages = [ + authentik_stage_authenticator_totp.totp_setup.id, + authentik_stage_authenticator_webauthn.webauthn_setup.id, + authentik_stage_authenticator_static.static_setup.id, + ] + + # Re-authenticate after 12 hours even if "remember this device" is used + last_auth_threshold = "hours=12" + + # WebAuthn settings + webauthn_user_verification = "preferred" +} + +# ----------------------------------------------------------------------------- +# Expression Policy - Admin-only access for sensitive apps +# Example: Bind this to admin-only applications +# ----------------------------------------------------------------------------- +resource "authentik_policy_expression" "admin_only" { + name = "admin-only-access" + expression = <<-EOT + return ak_is_group_member(request.user, name="Admins") + EOT + execution_logging = true +} + +# Expression Policy - Require MFA for app access +resource "authentik_policy_expression" "require_mfa_configured" { + name = "require-mfa-configured" + expression = <<-EOT + from authentik.stages.authenticator.models import Device + + # Check if user has any MFA device configured + devices = Device.objects.filter(user=request.user, confirmed=True) + return devices.exists() + EOT + execution_logging = true +} + +# ----------------------------------------------------------------------------- +# GeoIP Policy - Block logins from suspicious locations (optional) +# Requires GeoIP database configured in Authentik +# Uncomment to enable +# ----------------------------------------------------------------------------- +# resource "authentik_policy_geoip" "block_high_risk_countries" { +# name = "block-high-risk-countries" +# asns = [] +# countries = [] # Add country codes to block, e.g., ["RU", "CN", "KP"] +# execution_logging = true +# } + +# ----------------------------------------------------------------------------- +# Outputs +# ----------------------------------------------------------------------------- +output "password_policy_id" { + description = "ID of the strong password policy" + value = authentik_policy_password.strong_password.id +} + +output "mfa_validation_stage_id" { + description = "ID of the MFA validation stage - bind to authentication flow" + value = authentik_stage_authenticator_validate.mfa_validation.id +} + +output "admin_policy_id" { + description = "ID of the admin-only policy - bind to sensitive applications" + value = authentik_policy_expression.admin_only.id +} diff --git a/versions.tf b/versions.tf index 0a31553..119065b 100644 --- a/versions.tf +++ b/versions.tf @@ -6,9 +6,5 @@ terraform { source = "goauthentik/authentik" version = "~> 2025.2" } - sops = { - source = "carlpett/sops" - version = "~> 1.0" - } } }