locals { app_slug = "${var.instance}${var.component == "" ? "" : "-"}${var.component}" ldap_labels = merge(var.labels, { "app.kubernetes.io/component" = "authentik-ldap" }) base_dn = format("dc=%s", join(",dc=", split(".", var.dns_name))) base_group_dn = format("ou=groups,%s", local.base_dn) base_user_dn = format("ou=users,%s", local.base_dn) authentik_base_url = "http://authentik.${var.domain}-auth.svc" ldap_outpost_providers = jsondecode(data.http.get_ldap_outpost.response_body).results[0].providers ldap_outpost_pk = jsondecode(data.http.get_ldap_outpost.response_body).results[0].pk app_name = var.component == var.instance ? var.instance : format("%s-%s", var.component, var.instance) main_group = format("app-%s", local.app_name) sorted_group_names = reverse(distinct(sort([ for grp in var.user_groups : grp.name ]))) sorted_groups = flatten([ for name in local.sorted_group_names : [ for grp in var.user_groups : grp if grp.name == name ] ]) } data "authentik_group" "vynil_admin" { name = "vynil-ldap-admins" } resource "authentik_group" "groups" { count = length(local.sorted_groups) name = local.sorted_groups[count.index].name attributes = jsonencode({ local.app_name = true }) } data "authentik_group" "readed_groups" { depends_on = [authentik_group.groups] count = length(local.sorted_groups) name = local.sorted_groups[count.index].name } resource "authentik_application" "application_ldap" { name = local.app_slug slug = "${local.app_slug}-ldap" protocol_provider = authentik_provider_ldap.provider_ldap.id meta_launch_url = "blank://blank" } resource "authentik_policy_expression" "policy" { name = local.main_group expression = <<-EOF attr = request.user.group_attributes() return attr['${local.app_name}'] if '${local.app_name}' in attr else False EOF } resource "authentik_policy_binding" "ldap_access_users" { target = authentik_application.application_ldap.uuid policy = authentik_policy_expression.policy.id order = 0 } resource "authentik_policy_binding" "ldap_access_ldap" { target = authentik_application.application_ldap.uuid group = authentik_group.ldapsearch.id order = 1 } resource "authentik_policy_binding" "ldap_access_vynil" { target = authentik_application.application_ldap.uuid group = data.authentik_group.vynil_admin.id order = 2 } resource "kubectl_manifest" "ldap" { ignore_fields = ["metadata.annotations"] yaml_body = <<-EOF apiVersion: "secretgenerator.mittwald.de/v1alpha1" kind: "StringSecret" metadata: name: "${local.app_slug}-ldapsearch" namespace: "${var.namespace}" labels: ${jsonencode(local.ldap_labels)} data: LDAP_ADMIN_USR: "${local.app_slug}-ldapsearch" spec: forceRegenerate: false fields: - fieldName: "LDAP_ADMIN_PASS" length: "32" EOF } data "kubernetes_secret_v1" "ldap_password" { depends_on = [kubectl_manifest.ldap] metadata { name = kubectl_manifest.ldap.name namespace = var.namespace } } resource "authentik_user" "ldapsearch" { username = "${local.app_slug}-ldapsearch" name = "${local.app_slug}-ldapsearch" } resource "authentik_group" "ldapsearch" { name = "${local.app_slug}-ldapsearch" users = [authentik_user.ldapsearch.id] } data "http" "ldapsearch_password" { url = "${local.authentik_base_url}/api/v3/core/users/${authentik_user.ldapsearch.id}/set_password/" method = "POST" request_headers = var.request_headers request_body = jsonencode({ password = data.kubernetes_secret_v1.ldap_password.data["LDAP_ADMIN_PASS"] }) lifecycle { postcondition { condition = contains([201, 204], self.status_code) error_message = "Status code invalid" } } } data "authentik_flow" "ldap_authentication_flow" { slug = "ldap-authentication-flow" } resource "authentik_provider_ldap" "provider_ldap" { name = "${local.app_slug}-ldap" base_dn = local.base_dn search_group = authentik_group.ldapsearch.id bind_flow = data.authentik_flow.ldap_authentication_flow.id } data "http" "get_ldap_outpost" { depends_on = [authentik_policy_binding.ldap_access_users] # fake dependency so it is not evaluated at plan stage url = "${local.authentik_base_url}/api/v3/outposts/instances/?name__iexact=ldap" method = "GET" request_headers = var.request_headers lifecycle { postcondition { condition = contains([200], self.status_code) error_message = "Status code invalid" } } } resource "restapi_object" "ldap_outpost_binding" { path = "/outposts/instances/${local.ldap_outpost_pk}/" data = jsonencode({ name = "ldap" providers = contains(local.ldap_outpost_providers, authentik_provider_ldap.provider_ldap.id) ? local.ldap_outpost_providers : concat(local.ldap_outpost_providers, [authentik_provider_ldap.provider_ldap.id]) }) }