diff --git a/terraform/authentik/.claude/settings.local.json b/terraform/authentik/.claude/settings.local.json new file mode 100644 index 0000000..9f30b05 --- /dev/null +++ b/terraform/authentik/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "WebSearch", + "WebFetch(domain:registry.terraform.io)", + "Bash(C:UsersabAppDataLocalMicrosoftWinGetPackagesHashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbweterraform.exe apply -auto-approve)", + "Bash(\"C:\\Users\\ab\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Hashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbwe\\terraform.exe\" apply -auto-approve)", + "Bash(\"C:\\Users\\ab\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Hashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbwe\\terraform.exe\" apply -auto-approve -lock=false)", + "Bash(\"C:\\Users\\ab\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Hashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbwe\\terraform.exe\" plan -lock=false)", + "Bash(\"C:\\Users\\ab\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Hashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbwe\\terraform.exe\" apply -replace=\"authentik_outpost.outposts[\"\"kubernetes-outpost\"\"]\" -auto-approve -lock=false)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/terraform/authentik/.terraform.lock.hcl b/terraform/authentik/.terraform.lock.hcl new file mode 100644 index 0000000..a41270f --- /dev/null +++ b/terraform/authentik/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/goauthentik/authentik" { + version = "2025.8.1" + constraints = ">= 2023.10.0" + hashes = [ + "h1:L3Fh0LyQ066laexCAeqLd+AVuSPDemwCmYgq1Bges6c=", + "zh:0c3f1083fd48f20ed06959401ff1459fbb5d454d81c8175b5b6d321b308c0be3", + "zh:21c6d93f8d26e688da38a660d121b5624e3597c426c671289f31a17a9771abbf", + "zh:301b5763ffc4c5fe47aa7e851ce0b19f71bab4fae5c81003ad81b38775e85f78", + "zh:4f7ee6473f6a687340538ddac0ec4a0453664186b15fdb0bb2fb5fcd8fb3ad30", + "zh:7927f4f634c9e072d4aa6620d09e97dc83eeb1dbd0667102086779cd5fc495c1", + "zh:84e7c2a3f3de721a54abe4c971d9a163127f5e4af91d023260fea305ac74bcf4", + "zh:92af52aaac518c426164eb731d282f51a5825e64e6a02b0695952177a7af7d9c", + "zh:a6920a54d5df69342f4ea2d903676145b00e7375d2f2eecc0840858d83b3b4a8", + "zh:ac8a60801fc55fd05b3471778f908ed43072e046997c0082644c9602b84dafec", + "zh:b1cc29e2878aa94a3827fd5e1dd8cffb98397aa4093d6a4852c6e53157e9b35f", + "zh:c2d78f308c4d70a16ef4f6d1f4822a64f8f160d0a207f2121904cdd6f4942db4", + "zh:ca970e5776f408059a84b4e17f6ac257ec92afae956be74f3807c548e4567eaa", + "zh:eb2e3650ee0eec033207b6d72fcb938dc5846c6feb8a61ae30d61981ea411269", + "zh:fcb93e51c84ba592bc2b075d7342e475126e5029620959666999b5b1bd11cb98", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = ">= 3.5.0" + hashes = [ + "h1:0hcNr59VEJbhZYwuDE/ysmyTS0evkfcLarlni+zATPM=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/terraform/authentik/README.md b/terraform/authentik/README.md new file mode 100644 index 0000000..0328d8f --- /dev/null +++ b/terraform/authentik/README.md @@ -0,0 +1,55 @@ +# Authentik Terraform Module + +Terraform module for managing Authentik applications with OAuth2/OpenID and Proxy providers, including automatic Outpost assignment. + +## Usage + +```hcl +module "authentik" { + source = "./authentik" + + authentik_url = "https://auth.example.com" + authentik_token = var.authentik_token + + oauth_applications = { + "gitlab" = { + name = "GitLab OAuth" + slug = "gitlab" + redirect_uris = ["https://gitlab.example.com/users/auth/openid_connect/callback"] + } + } + + proxy_applications = { + "portainer" = { + name = "Portainer" + slug = "portainer" + external_host = "https://portainer.example.com" + internal_host = "http://portainer:9000" + outpost = "k8s-outpost" + } + } + + outposts = { + "k8s-outpost" = { + name = "Kubernetes Outpost" + type = "proxy" + service_connection = "k8s-local" + } + } +} +``` + +## Structure + +- `main.tf` - Main configuration +- `variables.tf` - Input variables +- `outputs.tf` - Output values +- `modules/oauth-provider/` - OAuth2/OIDC provider module +- `modules/proxy-provider/` - Proxy provider module +- `terraform.tfvars.example` - Configuration example + +## Requirements + +- Terraform >= 1.0 +- Authentik provider >= 2023.10.0 +- Authentik API token with admin permissions \ No newline at end of file diff --git a/terraform/authentik/main.tf b/terraform/authentik/main.tf new file mode 100644 index 0000000..234d11f --- /dev/null +++ b/terraform/authentik/main.tf @@ -0,0 +1,181 @@ + +data "authentik_flow" "default_authorization_flow" { + slug = var.default_authorization_flow +} + +data "authentik_flow" "default_authentication_flow" { + slug = var.default_authentication_flow +} + +data "authentik_flow" "default_invalidation_flow" { + slug = var.default_invalidation_flow +} + +resource "authentik_group" "groups" { + for_each = var.groups + + name = each.value.name + is_superuser = each.value.is_superuser + parent = each.value.parent + attributes = jsonencode(each.value.attributes) +} + +resource "authentik_certificate_key_pair" "certificates" { + for_each = var.certificates + + name = each.value.name + certificate_data = each.value.certificate_data + key_data = each.value.key_data +} + + +data "authentik_service_connection_kubernetes" "local_k8s" { + name = "Local Kubernetes Cluster" +} + +resource "authentik_flow" "flows" { + for_each = var.flows + + name = each.value.name + title = each.value.title + slug = each.value.slug + designation = each.value.designation + policy_engine_mode = each.value.policy_engine_mode + compatibility_mode = each.value.compatibility_mode + layout = each.value.layout + denied_action = each.value.denied_action +} + +resource "authentik_property_mapping_provider_scope" "oidc_mappings" { + for_each = { + for k, v in var.property_mappings : k => v + if v.oidc_scope != null + } + + name = each.value.name + scope_name = each.value.oidc_scope + expression = each.value.expression +} + +resource "authentik_property_mapping_provider_saml" "saml_mappings" { + for_each = { + for k, v in var.property_mappings : k => v + if v.saml_name != null + } + + name = each.value.name + saml_name = each.value.saml_name + expression = each.value.expression +} + +module "oauth_applications" { + source = "./modules/oauth-provider" + + for_each = var.oauth_applications + + name = each.value.name + app_name = each.value.name + app_slug = each.value.slug + app_group = each.value.group + client_id = each.value.client_id + authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id) + invalidation_flow = data.authentik_flow.default_invalidation_flow.id + redirect_uris = each.value.redirect_uris + client_type = each.value.client_type + include_claims_in_id_token = each.value.include_claims_in_id_token + access_code_validity = each.value.access_code_validity + access_token_validity = each.value.access_token_validity + refresh_token_validity = each.value.refresh_token_validity + property_mappings = each.value.property_mappings + signing_key = each.value.signing_key + policy_engine_mode = each.value.policy_engine_mode + meta_description = each.value.meta_description + meta_launch_url = each.value.meta_launch_url + meta_icon = each.value.meta_icon +} + +module "proxy_applications" { + source = "./modules/proxy-provider" + + for_each = var.proxy_applications + + name = each.value.name + app_name = each.value.name + app_slug = each.value.slug + app_group = each.value.group + external_host = each.value.external_host + internal_host = each.value.internal_host + internal_host_ssl_validation = each.value.internal_host_ssl_validation + authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id) + invalidation_flow = data.authentik_flow.default_invalidation_flow.id + mode = each.value.mode + intercept_header_auth = each.value.intercept_header_auth + basic_auth_enabled = each.value.basic_auth_enabled + basic_auth_user_attribute = each.value.basic_auth_username_attribute + basic_auth_password_attribute = each.value.basic_auth_password_attribute + cookie_domain = each.value.cookie_domain + skip_path_regex = each.value.skip_path_regex + policy_engine_mode = each.value.policy_engine_mode + meta_description = each.value.meta_description + meta_launch_url = each.value.meta_launch_url + meta_icon = each.value.meta_icon +} + +locals { + oauth_outpost_assignments = { + for app_key, app in var.oauth_applications : app_key => app.outpost + if app.outpost != null + } + + proxy_outpost_assignments = { + for app_key, app in var.proxy_applications : app_key => app.outpost + if app.outpost != null + } + + outpost_providers = { + for outpost_key, outpost in var.outposts : outpost_key => concat( + [for app_key, app_outpost in local.oauth_outpost_assignments : + module.oauth_applications[app_key].provider_id if app_outpost == outpost_key], + [for app_key, app_outpost in local.proxy_outpost_assignments : + module.proxy_applications[app_key].provider_id if app_outpost == outpost_key] + ) + } +} + +resource "authentik_outpost" "outposts" { + for_each = { + for k, v in var.outposts : k => v + if length(lookup(local.outpost_providers, k, [])) > 0 + } + + name = each.value.name + type = "proxy" + protocol_providers = local.outpost_providers[each.key] + service_connection = data.authentik_service_connection_kubernetes.local_k8s.id + config = jsonencode({ + log_level = "info" + docker_labels = null + authentik_host = var.authentik_url + docker_network = null + container_image = null + docker_map_ports = true + refresh_interval = "minutes=5" + kubernetes_replicas = 1 + kubernetes_namespace = "authentik" + authentik_host_browser = "" + object_naming_template = "ak-outpost-%(name)s" + authentik_host_insecure = false + kubernetes_json_patches = null + kubernetes_service_type = "ClusterIP" + kubernetes_image_pull_secrets = [] + kubernetes_ingress_class_name = null + kubernetes_disabled_components = [] + kubernetes_ingress_annotations = {} + kubernetes_ingress_secret_name = "authentik-outpost-tls" + }) + + depends_on = [ + module.oauth_applications, + module.proxy_applications + ] +} \ No newline at end of file diff --git a/terraform/authentik/modules/oauth-provider/main.tf b/terraform/authentik/modules/oauth-provider/main.tf new file mode 100644 index 0000000..b136b3e --- /dev/null +++ b/terraform/authentik/modules/oauth-provider/main.tf @@ -0,0 +1,59 @@ +terraform { + required_providers { + authentik = { + source = "goauthentik/authentik" + version = ">= 2023.10.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.5.0" + } + } +} + +resource "random_password" "client_secret" { + count = var.client_secret == null ? 1 : 0 + length = 40 + special = true +} + +resource "authentik_provider_oauth2" "provider" { + name = var.name + client_id = var.client_id != null ? var.client_id : random_id.client_id[0].hex + client_secret = var.client_secret != null ? var.client_secret : random_password.client_secret[0].result + client_type = var.client_type + authorization_flow = var.authorization_flow + invalidation_flow = var.invalidation_flow + include_claims_in_id_token = var.include_claims_in_id_token + + property_mappings = var.property_mappings +} + +resource "random_id" "client_id" { + count = var.client_id == null ? 1 : 0 + byte_length = 20 +} + +resource "authentik_application" "app" { + name = var.app_name + slug = var.app_slug + protocol_provider = authentik_provider_oauth2.provider.id + group = var.app_group + policy_engine_mode = var.policy_engine_mode + meta_description = var.meta_description + meta_launch_url = var.meta_launch_url + meta_icon = var.meta_icon +} + +resource "authentik_policy_binding" "app_access" { + for_each = var.access_policies + + target = authentik_application.app.id + policy = each.value.policy_id + order = each.value.order + + enabled = lookup(each.value, "enabled", true) + timeout = lookup(each.value, "timeout", 30) + negate = lookup(each.value, "negate", false) + failure_result = lookup(each.value, "failure_result", true) +} \ No newline at end of file diff --git a/terraform/authentik/modules/oauth-provider/outputs.tf b/terraform/authentik/modules/oauth-provider/outputs.tf new file mode 100644 index 0000000..f57ff3b --- /dev/null +++ b/terraform/authentik/modules/oauth-provider/outputs.tf @@ -0,0 +1,30 @@ +output "provider_id" { + description = "ID of the OAuth2 provider" + value = authentik_provider_oauth2.provider.id +} + +output "application_id" { + description = "ID of the application" + value = authentik_application.app.id +} + +output "application_uuid" { + description = "UUID of the application" + value = authentik_application.app.id +} + +output "client_id" { + description = "OAuth2 Client ID" + value = authentik_provider_oauth2.provider.client_id +} + +output "client_secret" { + description = "OAuth2 Client Secret" + value = authentik_provider_oauth2.provider.client_secret + sensitive = true +} + +output "application_slug" { + description = "Application slug" + value = authentik_application.app.slug +} \ No newline at end of file diff --git a/terraform/authentik/modules/oauth-provider/variables.tf b/terraform/authentik/modules/oauth-provider/variables.tf new file mode 100644 index 0000000..afa2a3e --- /dev/null +++ b/terraform/authentik/modules/oauth-provider/variables.tf @@ -0,0 +1,138 @@ +variable "name" { + description = "Name of the OAuth2 provider" + type = string +} + +variable "app_name" { + description = "Name of the application" + type = string +} + +variable "app_slug" { + description = "Slug of the application" + type = string +} + +variable "app_group" { + description = "Group for the application" + type = string + default = "" +} + +variable "client_id" { + description = "OAuth2 Client ID" + type = string + default = null +} + +variable "client_secret" { + description = "OAuth2 Client Secret" + type = string + default = null + sensitive = true +} + +variable "client_type" { + description = "OAuth2 Client type (confidential or public)" + type = string + default = "confidential" + + validation { + condition = contains(["confidential", "public"], var.client_type) + error_message = "Client type must be either 'confidential' or 'public'." + } +} + +variable "authorization_flow" { + description = "Authorization flow UUID" + type = string +} + +variable "invalidation_flow" { + description = "Invalidation flow UUID" + type = string +} + +variable "redirect_uris" { + description = "List of allowed redirect URIs" + type = list(string) + default = [] +} + +variable "access_code_validity" { + description = "Access code validity duration" + type = string + default = "minutes=1" +} + +variable "access_token_validity" { + description = "Access token validity duration" + type = string + default = "minutes=5" +} + +variable "refresh_token_validity" { + description = "Refresh token validity duration" + type = string + default = "days=30" +} + +variable "include_claims_in_id_token" { + description = "Include claims in ID token" + type = bool + default = true +} + +variable "signing_key" { + description = "Signing key UUID" + type = string + default = null +} + +variable "property_mappings" { + description = "List of property mapping UUIDs" + type = list(string) + default = [] +} + +variable "policy_engine_mode" { + description = "Policy engine mode" + type = string + default = "all" + + validation { + condition = contains(["all", "any"], var.policy_engine_mode) + error_message = "Policy engine mode must be either 'all' or 'any'." + } +} + +variable "meta_description" { + description = "Application meta description" + type = string + default = "" +} + +variable "meta_launch_url" { + description = "Application launch URL" + type = string + default = "" +} + +variable "meta_icon" { + description = "Application icon URL" + type = string + default = "" +} + +variable "access_policies" { + description = "Access policies for the application" + type = map(object({ + policy_id = string + order = number + enabled = optional(bool, true) + timeout = optional(number, 30) + negate = optional(bool, false) + failure_result = optional(bool, true) + })) + default = {} +} \ No newline at end of file diff --git a/terraform/authentik/modules/proxy-provider/main.tf b/terraform/authentik/modules/proxy-provider/main.tf new file mode 100644 index 0000000..7ad2724 --- /dev/null +++ b/terraform/authentik/modules/proxy-provider/main.tf @@ -0,0 +1,49 @@ +terraform { + required_providers { + authentik = { + source = "goauthentik/authentik" + version = ">= 2023.10.0" + } + } +} + +resource "authentik_provider_proxy" "provider" { + name = var.name + external_host = var.external_host + internal_host = var.internal_host + internal_host_ssl_validation = var.internal_host_ssl_validation + authorization_flow = var.authorization_flow + invalidation_flow = var.invalidation_flow + mode = var.mode + cookie_domain = var.cookie_domain + skip_path_regex = var.skip_path_regex + intercept_header_auth = var.intercept_header_auth + basic_auth_enabled = var.basic_auth_enabled + basic_auth_password_attribute = var.basic_auth_password_attribute + + property_mappings = var.property_mappings +} + +resource "authentik_application" "app" { + name = var.app_name + slug = var.app_slug + protocol_provider = authentik_provider_proxy.provider.id + group = var.app_group + policy_engine_mode = var.policy_engine_mode + meta_description = var.meta_description + meta_launch_url = var.meta_launch_url + meta_icon = var.meta_icon +} + +resource "authentik_policy_binding" "app_access" { + for_each = var.access_policies + + target = authentik_application.app.id + policy = each.value.policy_id + order = each.value.order + + enabled = lookup(each.value, "enabled", true) + timeout = lookup(each.value, "timeout", 30) + negate = lookup(each.value, "negate", false) + failure_result = lookup(each.value, "failure_result", true) +} \ No newline at end of file diff --git a/terraform/authentik/modules/proxy-provider/outputs.tf b/terraform/authentik/modules/proxy-provider/outputs.tf new file mode 100644 index 0000000..17e4c8b --- /dev/null +++ b/terraform/authentik/modules/proxy-provider/outputs.tf @@ -0,0 +1,35 @@ +output "provider_id" { + description = "ID of the Proxy provider" + value = authentik_provider_proxy.provider.id +} + +output "application_id" { + description = "ID of the application" + value = authentik_application.app.id +} + +output "application_uuid" { + description = "UUID of the application" + value = authentik_application.app.id +} + +output "application_slug" { + description = "Application slug" + value = authentik_application.app.slug +} + +output "launch_url" { + description = "Application launch URL" + value = authentik_application.app.meta_launch_url +} + +output "external_host" { + description = "External host URL" + value = authentik_provider_proxy.provider.external_host +} + +output "internal_host" { + description = "Internal host URL" + value = authentik_provider_proxy.provider.internal_host +} + diff --git a/terraform/authentik/modules/proxy-provider/variables.tf b/terraform/authentik/modules/proxy-provider/variables.tf new file mode 100644 index 0000000..ba83526 --- /dev/null +++ b/terraform/authentik/modules/proxy-provider/variables.tf @@ -0,0 +1,145 @@ +variable "name" { + description = "Name of the Proxy provider" + type = string +} + +variable "app_name" { + description = "Name of the application" + type = string +} + +variable "app_slug" { + description = "Slug of the application" + type = string +} + +variable "app_group" { + description = "Group for the application" + type = string + default = "" +} + +variable "external_host" { + description = "External hostname for the proxy" + type = string +} + +variable "internal_host" { + description = "Internal hostname for the proxy" + type = string + default = "" +} + +variable "internal_host_ssl_validation" { + description = "Enable SSL validation for internal host" + type = bool + default = true +} + +variable "authorization_flow" { + description = "Authorization flow UUID" + type = string +} + +variable "invalidation_flow" { + description = "Invalidation flow UUID" + type = string +} + +variable "mode" { + description = "Proxy mode (proxy, forward_single, forward_domain)" + type = string + default = "proxy" + + validation { + condition = contains(["proxy", "forward_single", "forward_domain"], var.mode) + error_message = "Mode must be one of: proxy, forward_single, forward_domain." + } +} + + +variable "cookie_domain" { + description = "Cookie domain for the proxy" + type = string + default = "" +} + + +variable "skip_path_regex" { + description = "Regular expression for paths to skip authentication" + type = string + default = "" +} + +variable "intercept_header_auth" { + description = "Intercept header authentication" + type = bool + default = false +} + +variable "basic_auth_enabled" { + description = "Enable basic authentication" + type = bool + default = false +} + +variable "basic_auth_password_attribute" { + description = "Attribute for basic auth password" + type = string + default = "" +} + +variable "basic_auth_user_attribute" { + description = "Attribute for basic auth username" + type = string + default = "" +} + +variable "property_mappings" { + description = "List of property mapping UUIDs" + type = list(string) + default = [] +} + +variable "policy_engine_mode" { + description = "Policy engine mode" + type = string + default = "all" + + validation { + condition = contains(["all", "any"], var.policy_engine_mode) + error_message = "Policy engine mode must be either 'all' or 'any'." + } +} + +variable "meta_description" { + description = "Application meta description" + type = string + default = "" +} + +variable "meta_launch_url" { + description = "Application launch URL" + type = string + default = "" +} + +variable "meta_icon" { + description = "Application icon URL" + type = string + default = "" +} + + +variable "access_policies" { + description = "Access policies for the application" + type = map(object({ + policy_id = string + order = number + enabled = optional(bool, true) + timeout = optional(number, 30) + negate = optional(bool, false) + failure_result = optional(bool, true) + })) + default = {} +} \ No newline at end of file diff --git a/terraform/authentik/outputs.tf b/terraform/authentik/outputs.tf new file mode 100644 index 0000000..a6341c2 --- /dev/null +++ b/terraform/authentik/outputs.tf @@ -0,0 +1,70 @@ +output "oauth_applications" { + description = "OAuth2/OpenID applications details" + value = { + for k, v in module.oauth_applications : k => { + application_id = v.application_id + application_uuid = v.application_uuid + client_id = v.client_id + client_secret = v.client_secret + slug = v.application_slug + } + } + sensitive = true +} + +output "proxy_applications" { + description = "Proxy applications details" + value = { + for k, v in module.proxy_applications : k => { + application_id = v.application_id + application_uuid = v.application_uuid + external_host = v.external_host + internal_host = v.internal_host + slug = v.application_slug + } + } +} + +output "outposts" { + description = "Outposts details" + value = { + for k, v in authentik_outpost.outposts : k => { + id = v.id + name = v.name + type = v.type + } + } +} + +output "groups" { + description = "Groups details" + value = { + for k, v in authentik_group.groups : k => { + id = v.id + name = v.name + } + } +} + +output "flows" { + description = "Custom flows details" + value = { + for k, v in authentik_flow.flows : k => { + id = v.id + slug = v.slug + name = v.name + } + } +} + +output "certificates" { + description = "Certificates details" + value = { + for k, v in authentik_certificate_key_pair.certificates : k => { + id = v.id + name = v.name + fingerprint_sha256 = v.fingerprint_sha256 + fingerprint_sha1 = v.fingerprint_sha1 + } + } +} \ No newline at end of file diff --git a/terraform/authentik/providers.tf b/terraform/authentik/providers.tf new file mode 100644 index 0000000..7595061 --- /dev/null +++ b/terraform/authentik/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + authentik = { + source = "goauthentik/authentik" + version = "2025.8.1" + } + } +} + +provider "authentik" { + url = "https://idm.hexor.cy" + token = "qXcuoCg77JaRgqnU6rqIIBa8MBJ4UNyLPTL89dZI8zeC2jfaWqQ7k56BJs8F" +} \ No newline at end of file diff --git a/terraform/authentik/variables.tf b/terraform/authentik/variables.tf new file mode 100644 index 0000000..9e30894 --- /dev/null +++ b/terraform/authentik/variables.tf @@ -0,0 +1,137 @@ +variable "oauth_applications" { + description = "Map of OAuth2/OpenID applications" + type = map(object({ + name = string + slug = string + group = optional(string, "") + policy_engine_mode = optional(string, "all") + meta_description = optional(string, "") + meta_launch_url = optional(string, "") + meta_icon = optional(string, "") + redirect_uris = list(string) + client_type = optional(string, "confidential") + client_id = optional(string, null) + include_claims_in_id_token = optional(bool, true) + access_code_validity = optional(string, "minutes=1") + access_token_validity = optional(string, "minutes=5") + refresh_token_validity = optional(string, "days=30") + property_mappings = optional(list(string), []) + authorization_flow = optional(string, null) + signing_key = optional(string, null) + outpost = optional(string, null) + })) + default = {} +} + +variable "proxy_applications" { + description = "Map of Proxy applications" + type = map(object({ + name = string + slug = string + group = optional(string, "") + policy_engine_mode = optional(string, "all") + meta_description = optional(string, "") + meta_launch_url = optional(string, "") + meta_icon = optional(string, "") + external_host = string + internal_host = optional(string, "") + internal_host_ssl_validation = optional(bool, true) + mode = optional(string, "proxy") + intercept_header_auth = optional(bool, false) + basic_auth_enabled = optional(bool, false) + basic_auth_username_attribute = optional(string, "") + basic_auth_password_attribute = optional(string, "") + cookie_domain = optional(string, "") + authorization_flow = optional(string, null) + skip_path_regex = optional(string, "") + outpost = optional(string, null) + })) + default = {} +} + +variable "outposts" { + description = "Map of Outposts (only proxy type supported)" + type = map(object({ + name = string + config = optional(map(any), {}) + })) + default = {} +} + +variable "flows" { + description = "Map of authentication flows" + type = map(object({ + name = string + title = string + slug = string + designation = string + policy_engine_mode = optional(string, "all") + compatibility_mode = optional(bool, false) + layout = optional(string, "stacked") + denied_action = optional(string, "message_continue") + })) + default = {} +} + +variable "groups" { + description = "Map of user groups" + type = map(object({ + name = string + is_superuser = optional(bool, false) + parent = optional(string, null) + attributes = optional(map(any), {}) + })) + default = {} +} + +variable "certificates" { + description = "Map of certificates for HTTPS" + type = map(object({ + name = string + certificate_data = string + key_data = string + managed = optional(string, null) + })) + default = {} +} + +variable "property_mappings" { + description = "Custom property mappings for SAML/OAuth" + type = map(object({ + name = string + expression = string + saml_name = optional(string, null) + oidc_scope = optional(string, null) + })) + default = {} +} + + +variable "default_authorization_flow" { + description = "Default authorization flow slug" + type = string + default = "default-provider-authorization-implicit-consent" +} + +variable "default_authentication_flow" { + description = "Default authentication flow slug" + type = string + default = "default-authentication-flow" +} + +variable "default_invalidation_flow" { + description = "Default invalidation flow slug" + type = string + default = "default-provider-invalidation-flow" +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} + +variable "authentik_url" { + description = "Authentik URL" + type = string +} \ No newline at end of file