# Read the OAuth client secret from Vault so nothing sensitive is committed. data "vault_kv_secret_v2" "keycloakoidc" { count = var.keycloakoidc != null && var.keycloakoidc.client_secret_vault != null ? 1 : 0 mount = var.keycloakoidc.client_secret_vault.mount name = var.keycloakoidc.client_secret_vault.path } # Read the internal PKI CA chain (intermediate + root) from Vault. Rancher makes # its OIDC discovery call to https://identity.unkin.net server-side; without the # internal CA it fails with x509 "certificate signed by unknown authority". The # ca_chain field is public/non-sensitive. Sourced from Vault (not hardcoded) so # the trust never goes stale on rotation. data "vault_generic_secret" "internal_ca" { count = var.keycloakoidc != null ? 1 : 0 path = "pki_int/cert/ca_chain" } # Rancher's Keycloak(OIDC) auth provider, pointed at Authentik. access_mode # "unrestricted" means any authenticated Authentik user can log in; Rancher # roles are granted to users/groups separately (avoids admin lockout on enable). resource "rancher2_auth_config_keycloak_oidc" "this" { count = var.keycloakoidc != null ? 1 : 0 rancher_url = var.keycloakoidc.rancher_url client_id = var.keycloakoidc.client_id client_secret = data.vault_kv_secret_v2.keycloakoidc[0].data["client_secret"] issuer = var.keycloakoidc.issuer auth_endpoint = var.keycloakoidc.auth_endpoint scopes = var.keycloakoidc.scopes groups_field = var.keycloakoidc.groups_field access_mode = var.keycloakoidc.access_mode enabled = var.keycloakoidc.enabled # CA cert Rancher uses to trust the IdP's TLS during OIDC discovery. Defaults # to the internal PKI chain from Vault; an explicit config value overrides it. certificate = coalesce(var.keycloakoidc.certificate, data.vault_generic_secret.internal_ca[0].data["ca_chain"]) } # Grant Rancher global roles to Authentik permission groups. The keycloak_oidc # group principal id is keycloakoidc_group://. Members of a role # group (e.g. akR-global-admin) inherit the permission group, so they receive # the bound global role. resource "rancher2_global_role_binding" "group" { for_each = var.global_role_bindings # Binding name must be an RFC 1123 label (lowercase); the group name may be # mixed-case (akP-*), so lowercase it here. The principal id keeps the original # case to match the group. name = "akgroup-${lower(each.key)}" global_role_id = each.value.global_role_id group_principal_id = "keycloakoidc_group://${each.key}" depends_on = [rancher2_auth_config_keycloak_oidc.this] } # Rancher-local users. rancher2_user makes `password` required, so each user's # password comes from a Vault kv-v2 secret rather than the repo. Vault data # sources resolve at plan time: seed the secret before adding the yaml. data "vault_kv_secret_v2" "user_password" { for_each = var.users mount = each.value.password_vault.mount name = each.value.password_vault.path } resource "rancher2_user" "this" { for_each = var.users username = each.key name = coalesce(each.value.name, each.key) enabled = each.value.enabled must_change_password = each.value.must_change_password password = data.vault_kv_secret_v2.user_password[each.key].data[each.value.password_vault.key] } # Flatten users -> their global roles into "/" keys so adding or # removing one role never re-indexes the others. locals { user_global_role_bindings = merge([ for username, user in var.users : { for role in user.global_role_bindings : "${username}/${role}" => { username = username global_role_id = role } } ]...) } resource "rancher2_global_role_binding" "user" { for_each = local.user_global_role_bindings # Binding name must be an RFC 1123 label, so lowercase it and swap the key's # "/" separator for "-". name = "akuser-${lower(replace(each.key, "/", "-"))}" global_role_id = each.value.global_role_id user_id = rancher2_user.this[each.value.username].id } # Custom global roles. Rules are optional: a role with none grants nothing on its # own and is useful purely as a container for inherited_cluster_roles. resource "rancher2_global_role" "this" { for_each = var.global_roles name = each.key description = each.value.description new_user_default = each.value.new_user_default inherited_cluster_roles = each.value.inherited_cluster_roles dynamic "rules" { for_each = each.value.rules content { api_groups = rules.value.api_groups resources = rules.value.resources verbs = rules.value.verbs non_resource_urls = rules.value.non_resource_urls resource_names = rules.value.resource_names } } } # CAVEAT: rancher2_token has no user selector — `user_id` is computed by the # provider at 14.1.1, not settable. Every token declared here is minted for the # identity the rancher2 provider authenticates as (the CI admin service account), # NOT for any user in config/users/. There is no way to mint a token on another # user's behalf through this provider; that has to be done by that user. # The token/secret_key values land in Terraform state, so treat state as secret. resource "rancher2_token" "this" { for_each = var.tokens description = coalesce(each.value.description, each.key) ttl = each.value.ttl renew = each.value.renew cluster_id = each.value.cluster_id } # Rancher settings, e.g. server-url. Rancher ships defaults for these, so an # entry here takes over an existing setting rather than creating a new one. resource "rancher2_setting" "this" { for_each = var.settings name = each.key value = each.value.value }