diff --git a/config/permissions/ak-p-argocd-admin.yaml b/config/permissions/ak-p-argocd-admin.yaml deleted file mode 100644 index 22f7078..0000000 --- a/config/permissions/ak-p-argocd-admin.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Permission: admin access to argocd. Bound to the argocd application for -# access, and mapped to the argocd admin role via the groups claim. -name: akP-argocd-admin -application: argocd diff --git a/config/permissions/ak-p-argocd-user.yaml b/config/permissions/ak-p-argocd-user.yaml deleted file mode 100644 index b369880..0000000 --- a/config/permissions/ak-p-argocd-user.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Permission: user access to argocd. Bound to the argocd application for -# access, and mapped to the argocd user role via the groups claim. -name: akP-argocd-user -application: argocd diff --git a/config/permissions/ak-p-grafana-admin.yaml b/config/permissions/ak-p-grafana-admin.yaml deleted file mode 100644 index 955e29a..0000000 --- a/config/permissions/ak-p-grafana-admin.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Permission: admin access to grafana. Bound to the grafana application for -# access, and mapped to the grafana admin role via the groups claim. -name: akP-grafana-admin -application: grafana diff --git a/config/permissions/ak-p-grafana-user.yaml b/config/permissions/ak-p-grafana-user.yaml deleted file mode 100644 index 2092784..0000000 --- a/config/permissions/ak-p-grafana-user.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Permission: user access to grafana. Bound to the grafana application for -# access, and mapped to the grafana user role via the groups claim. -name: akP-grafana-user -application: grafana diff --git a/config/permissions/ak-p-rancher-admin.yaml b/config/permissions/ak-p-rancher-admin.yaml deleted file mode 100644 index 1acc5e5..0000000 --- a/config/permissions/ak-p-rancher-admin.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Permission: admin access to rancher. Bound to the rancher application for -# access, and mapped to the rancher admin role via the groups claim. -name: akP-rancher-admin -application: rancher diff --git a/config/permissions/ak-p-rancher-user.yaml b/config/permissions/ak-p-rancher-user.yaml deleted file mode 100644 index d596373..0000000 --- a/config/permissions/ak-p-rancher-user.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Permission: user access to rancher. Bound to the rancher application for -# access, and mapped to the rancher user role via the groups claim. -name: akP-rancher-user -application: rancher diff --git a/config/permissions/akP-argocd-admin.yaml b/config/permissions/akP-argocd-admin.yaml new file mode 100644 index 0000000..50af97c --- /dev/null +++ b/config/permissions/akP-argocd-admin.yaml @@ -0,0 +1,3 @@ +# Permission group akP-argocd-admin (name = filename). Grants admin +# access to argocd: bound to the argocd application and mapped to its admin role. +application: argocd diff --git a/config/permissions/akP-argocd-user.yaml b/config/permissions/akP-argocd-user.yaml new file mode 100644 index 0000000..c0ad827 --- /dev/null +++ b/config/permissions/akP-argocd-user.yaml @@ -0,0 +1,3 @@ +# Permission group akP-argocd-user (name = filename). Grants user +# access to argocd: bound to the argocd application and mapped to its user role. +application: argocd diff --git a/config/permissions/akP-grafana-admin.yaml b/config/permissions/akP-grafana-admin.yaml new file mode 100644 index 0000000..ee8ff1f --- /dev/null +++ b/config/permissions/akP-grafana-admin.yaml @@ -0,0 +1,3 @@ +# Permission group akP-grafana-admin (name = filename). Grants admin +# access to grafana: bound to the grafana application and mapped to its admin role. +application: grafana diff --git a/config/permissions/akP-grafana-user.yaml b/config/permissions/akP-grafana-user.yaml new file mode 100644 index 0000000..718c224 --- /dev/null +++ b/config/permissions/akP-grafana-user.yaml @@ -0,0 +1,3 @@ +# Permission group akP-grafana-user (name = filename). Grants user +# access to grafana: bound to the grafana application and mapped to its user role. +application: grafana diff --git a/config/permissions/akP-rancher-admin.yaml b/config/permissions/akP-rancher-admin.yaml new file mode 100644 index 0000000..e312834 --- /dev/null +++ b/config/permissions/akP-rancher-admin.yaml @@ -0,0 +1,3 @@ +# Permission group akP-rancher-admin (name = filename). Grants admin +# access to rancher: bound to the rancher application and mapped to its admin role. +application: rancher diff --git a/config/permissions/akP-rancher-user.yaml b/config/permissions/akP-rancher-user.yaml new file mode 100644 index 0000000..e59c1d1 --- /dev/null +++ b/config/permissions/akP-rancher-user.yaml @@ -0,0 +1,3 @@ +# Permission group akP-rancher-user (name = filename). Grants user +# access to rancher: bound to the rancher application and mapped to its user role. +application: rancher diff --git a/config/roles/ak-r-global-admin.yaml b/config/roles/ak-r-global-admin.yaml deleted file mode 100644 index d9d0a7a..0000000 --- a/config/roles/ak-r-global-admin.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Role: full admin across all onboarded apps. Assign users here for org-wide admin. -name: akR-global-admin -is_superuser: false -permissions: - - ak-p-grafana-admin - - ak-p-argocd-admin - - ak-p-rancher-admin diff --git a/config/roles/ak-r-standard-user.yaml b/config/roles/ak-r-standard-user.yaml deleted file mode 100644 index 8e21d35..0000000 --- a/config/roles/ak-r-standard-user.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Role: standard (non-admin) access across all onboarded apps. -name: akR-standard-user -permissions: - - ak-p-grafana-user - - ak-p-argocd-user - - ak-p-rancher-user diff --git a/config/roles/akR-global-admin.yaml b/config/roles/akR-global-admin.yaml new file mode 100644 index 0000000..cb24e50 --- /dev/null +++ b/config/roles/akR-global-admin.yaml @@ -0,0 +1,5 @@ +# Role akR-global-admin (name = filename): full admin across all onboarded apps. +permissions: + - akP-grafana-admin + - akP-argocd-admin + - akP-rancher-admin diff --git a/config/roles/akR-standard-user.yaml b/config/roles/akR-standard-user.yaml new file mode 100644 index 0000000..14b8836 --- /dev/null +++ b/config/roles/akR-standard-user.yaml @@ -0,0 +1,5 @@ +# Role akR-standard-user (name = filename): standard (non-admin) access everywhere. +permissions: + - akP-grafana-user + - akP-argocd-user + - akP-rancher-user diff --git a/environments/identity.k8s.syd1.au.unkin.net/.terraform.lock.hcl b/environments/identity.k8s.syd1.au.unkin.net/.terraform.lock.hcl new file mode 100644 index 0000000..825a7c6 --- /dev/null +++ b/environments/identity.k8s.syd1.au.unkin.net/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/goauthentik/authentik" { + version = "2026.5.0" + constraints = ">= 2026.5.0" + hashes = [ + "h1:SeznjPKBzSrgo8WasRnuxiGMDSeQHEKsv3U/xw8bhQE=", + "zh:0dc1706f6fbff866f4a96de56a4934b9a277954bcdd0713549a29a9b8ec85153", + "zh:218417ec4e864f2d7e585d6c08d39bccb96d8f3bca16c6f762be15365e434234", + "zh:24f9afa7a1174316da3478811848cd76ef348d8a983310b8d75ed6f45abe1a92", + "zh:560092e47cb8a72b890b3eeafe1803202cd25cf27f5f5a6e2c370f645f5d86ae", + "zh:5bc69d8de198007ad1587e146f98cffacf0d1a571800da549b308ff5f4541474", + "zh:65248dce941472ad2a30d0754d2f3c2db6bb6fe5080946316fb097d6ba7cc79f", + "zh:79c9a59a8d3c60280e27a064668889594da44c60f940b046b7c8e63be01067d0", + "zh:87f26cadcd842d6e6d0af94ef0e56860557f5d07f487b10d69d38b63af68bea5", + "zh:8e42c9d0e77d61cc2e5f8c8b761f6e484774d93771927b4cb5fbdae41209dd33", + "zh:94ff632b9b4841527c6b652d51a850a8a47c84c0308a3efc189e0ff7e2558f87", + "zh:b8d32d9f17a905b63c87a23306c02c295b7c8b70f72950071aa3086396932816", + "zh:c91982af99474fc2e4e69be36ed3a68847f261963ed79f6a546fc75703992f99", + "zh:eb9c1fd3020cf61e9b7a6a38d2965f4b521495a9928705e963459a4af857f97d", + ] +} + +provider "registry.opentofu.org/hashicorp/vault" { + version = "5.10.1" + constraints = ">= 4.0.0" + hashes = [ + "h1:wo5cTkl/1nlxMfdn1yEDIHNoRLMczuK6COH2Id4/zeY=", + "zh:0abf976c01f0c0732d0ccc6481e52008be5ee9c8e3d9b5eba0573c640fcf7019", + "zh:2aff4d7ee7ba9eb3de2cd5cda16ba92b4ec7a2b43232aec180984241a323b216", + "zh:2cc186fd0bfc44e100a22b0b40ae8ddcd0ec210a53c1da65d310ee758b1d2b08", + "zh:3f8fb8594736b34af4b26437dd4df4dd4042ad4905223995cfebb8a1f10682ec", + "zh:47fb41b18b74073f557dbcd6aad2183e416293405ccd70c0691a279cfe97f8cd", + "zh:517e2f2764d671c22d22def0384fdfc521b456458189189c0363375495d114dc", + "zh:5a49a2003636f2b8a547d494a6c06d43d62a68299775305408c52eff22b1c11f", + "zh:66d4e716920ada84b0c768f4aca4c8948388995462923349a01bd3818d82b618", + "zh:7599f652e89a3f18fa4b76a59d115cc63255cc36ce6b273850509ba25031abca", + "zh:9c3e38ae7e670de973b6255d7050f526cd2b3ca7c383d7ba7226fc204d97c507", + "zh:d04b046023fa9fd69def678f27e001c298ea34fc99ba51f835cda82e496fdb57", + "zh:d9acd8810f6660cd51bb4c25596632984ae18e93340c82a102d074c6eac95151", + "zh:e161bcb9a22607270b980eeff2ba693335fb62d6978516dff93dd4c91cda99b3", + "zh:ef47502f08cfcb5311b7b16a7905e0052bf28359e07cc00ed080ef454e0946cf", + "zh:f0640ddb52e7e90c5006ff571f6ad0554e593665320c764c57a3d8b7ec31b490", + ] +} diff --git a/modules/authentik/main.tf b/modules/authentik/main.tf index 69f6e8f..fa84126 100644 --- a/modules/authentik/main.tf +++ b/modules/authentik/main.tf @@ -12,7 +12,7 @@ resource "authentik_group" "this" { resource "authentik_group" "permission" { for_each = var.permission_groups - name = each.value.name + name = each.key attributes = jsonencode(each.value.attributes) } @@ -23,21 +23,22 @@ resource "authentik_group" "permission" { resource "authentik_group" "role" { for_each = var.role_groups - name = each.value.name + name = each.key is_superuser = each.value.is_superuser parents = [for p in each.value.permissions : authentik_group.permission[p].id] attributes = jsonencode(each.value.attributes) } -# Expand the OIDC `groups` claim to include inherited (ancestor) groups. The -# default profile mapping only emits direct groups (goauthentik/authentik#15579), -# so a member of a role group would not see the permission groups it nests. This -# walks each of the user's direct groups up through `.parents` and emits the full -# set of names, so role -> permission nesting drives in-app roles. Only evaluated -# when a provider requests the `groups` scope. +# Emit an `ak_groups` claim containing the user's groups AND all inherited +# (ancestor) groups, so role -> permission nesting reaches apps. The default +# profile mapping only emits *direct* groups under `groups` +# (goauthentik/authentik#15579); we use a distinct claim key so there is no +# collision with that (Authentik dict-overrides same-key claims in an +# unpredictable order). Apps request the `ak_groups` scope and read the +# `ak_groups` claim. Walks each direct group up through `.parents`. resource "authentik_property_mapping_provider_scope" "groups_hierarchical" { - name = "unkin: groups (hierarchical)" - scope_name = "groups" + name = "unkin: ak_groups (hierarchical)" + scope_name = "ak_groups" expression = <<-EOT groups = {} pending = list(user.ak_groups.all()) @@ -47,7 +48,7 @@ resource "authentik_property_mapping_provider_scope" "groups_hierarchical" { continue groups[grp.pk] = grp.name pending += list(grp.parents.all()) - return {"groups": sorted(groups.values())} + return {"ak_groups": sorted(groups.values())} EOT } diff --git a/modules/authentik/variables.tf b/modules/authentik/variables.tf index 86bc700..3569aec 100644 --- a/modules/authentik/variables.tf +++ b/modules/authentik/variables.tf @@ -16,9 +16,9 @@ variable "groups" { # role is an effective member of every permission it grants (Authentik membership # propagates child -> parent). Split into two variables/resources so roles can # reference permission group ids without the authentik_group self-reference error. +# The group name is the map key (the config filename); no `name` field needed. variable "permission_groups" { type = map(object({ - name = string # slug of the oauth2 application this permission grants *access* to; when set, # a policy binding is created gating that app to this group (and its children). application = optional(string, null) @@ -29,7 +29,6 @@ variable "permission_groups" { variable "role_groups" { type = map(object({ - name = string # keys into var.permission_groups that this role nests (becomes its parents). permissions = optional(list(string), []) is_superuser = optional(bool, false)