From 80fa1b284423e94070ef8fbd7494c051eb10be0e Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sat, 18 Jul 2026 16:25:18 +1000 Subject: [PATCH] Wire Rancher to Authentik ak_groups + akP-rancher global roles Consume the two-tier Authentik RBAC (terraform-authentik): read the hierarchical `ak_groups` claim and grant Rancher global roles to the akP-rancher permission groups. Members of akR-global-admin/akR-standard-user inherit these. - keycloakoidc: scopes += ak_groups; groups_field = ak_groups - global_role_bindings: akP-rancher-admin -> admin, akP-rancher-user -> user (group principal keycloakoidc_group://) --- config/config.hcl | 5 +++++ .../global_role_bindings/akP-rancher-admin.yaml | 3 +++ .../global_role_bindings/akP-rancher-user.yaml | 3 +++ config/keycloakoidc.yaml | 5 ++++- .../rancher.k8s.syd1.au.unkin.net/terragrunt.hcl | 3 ++- modules/rancher/main.tf | 15 +++++++++++++++ modules/rancher/variables.tf | 16 ++++++++++++++-- 7 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 config/global_role_bindings/akP-rancher-admin.yaml create mode 100644 config/global_role_bindings/akP-rancher-user.yaml diff --git a/config/config.hcl b/config/config.hcl index 016df4a..683481a 100644 --- a/config/config.hcl +++ b/config/config.hcl @@ -8,5 +8,10 @@ locals { config = { keycloakoidc = try(local.all_configs["keycloakoidc.yaml"], null) + global_role_bindings = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "global_role_bindings/") + } } } diff --git a/config/global_role_bindings/akP-rancher-admin.yaml b/config/global_role_bindings/akP-rancher-admin.yaml new file mode 100644 index 0000000..eee54f6 --- /dev/null +++ b/config/global_role_bindings/akP-rancher-admin.yaml @@ -0,0 +1,3 @@ +# akP-rancher-admin -> Rancher global admin. Granted to akR-global-admin members +# (inherited) and direct members. +global_role_id: admin diff --git a/config/global_role_bindings/akP-rancher-user.yaml b/config/global_role_bindings/akP-rancher-user.yaml new file mode 100644 index 0000000..10cc503 --- /dev/null +++ b/config/global_role_bindings/akP-rancher-user.yaml @@ -0,0 +1,3 @@ +# akP-rancher-user -> standard Rancher user global role. Granted to +# akR-standard-user members (inherited) and direct members. +global_role_id: user diff --git a/config/keycloakoidc.yaml b/config/keycloakoidc.yaml index 71ed52d..a6fbadb 100644 --- a/config/keycloakoidc.yaml +++ b/config/keycloakoidc.yaml @@ -6,7 +6,10 @@ rancher_url: https://rancher.k8s.syd1.au.unkin.net/verify-auth client_id: rancher issuer: https://identity.unkin.net/application/o/rancher/ auth_endpoint: https://identity.unkin.net/application/o/authorize/ -scopes: openid profile email +# ak_groups = hierarchical group claim from terraform-authentik (permission +# groups inherited via role groups). groups_field points Rancher at that claim. +scopes: openid profile email ak_groups +groups_field: ak_groups access_mode: unrestricted enabled: true client_secret_vault: diff --git a/environments/rancher.k8s.syd1.au.unkin.net/terragrunt.hcl b/environments/rancher.k8s.syd1.au.unkin.net/terragrunt.hcl index c5f82f9..7a75b1c 100644 --- a/environments/rancher.k8s.syd1.au.unkin.net/terragrunt.hcl +++ b/environments/rancher.k8s.syd1.au.unkin.net/terragrunt.hcl @@ -17,5 +17,6 @@ terraform { } inputs = { - keycloakoidc = local.config.keycloakoidc + keycloakoidc = local.config.keycloakoidc + global_role_bindings = local.config.global_role_bindings } diff --git a/modules/rancher/main.tf b/modules/rancher/main.tf index 99b9edf..d28e8ac 100644 --- a/modules/rancher/main.tf +++ b/modules/rancher/main.tf @@ -17,6 +17,21 @@ resource "rancher2_auth_config_keycloak_oidc" "this" { 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 } + +# 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 + + name = "akgroup-${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] +} diff --git a/modules/rancher/variables.tf b/modules/rancher/variables.tf index e834dcb..8340ee4 100644 --- a/modules/rancher/variables.tf +++ b/modules/rancher/variables.tf @@ -6,8 +6,10 @@ variable "keycloakoidc" { issuer = string # OIDC issuer (Authentik application URL) auth_endpoint = string # OIDC authorization endpoint scopes = optional(string, "openid profile email") - access_mode = optional(string, "unrestricted") - enabled = optional(bool, true) + # OIDC claim to read group names from (default Rancher uses "groups"). + groups_field = optional(string, "groups") + access_mode = optional(string, "unrestricted") + enabled = optional(bool, true) # client_secret is never committed. Point at a Vault kv-v2 secret whose # `client_secret` key holds the value (seeded out of band); TF reads it. client_secret_vault = optional(object({ @@ -17,3 +19,13 @@ variable "keycloakoidc" { }) default = null } + +# Global role bindings mapping an Authentik group (permission group) to a Rancher +# global role. The group name is the map key; the OIDC group principal id is +# derived as keycloakoidc_group://. +variable "global_role_bindings" { + type = map(object({ + global_role_id = string # e.g. "admin", "user" + })) + default = {} +}