Derive group name from filename; use distinct ak_groups claim
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful

- Permission/role group name now comes from the config filename (the map key),
  dropping the redundant `name` field from each YAML and the object types.
- The hierarchical mapping emits an `ak_groups` claim (scope `ak_groups`) instead
  of `groups`, so it never collides with the direct-groups the default profile
  mapping already emits under `groups` (Authentik overrides same-key claims in an
  unpredictable order). Apps request the `ak_groups` scope and read that claim.
This commit is contained in:
2026-07-18 16:19:26 +10:00
parent 1dab2ecc6f
commit 805ea48a36
18 changed files with 41 additions and 50 deletions
@@ -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
-4
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
@@ -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
+3
View File
@@ -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
@@ -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
+3
View File
@@ -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
-7
View File
@@ -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
-6
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+12 -11
View File
@@ -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
}
+1 -2
View File
@@ -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)