Add LiteLLM RBAC: akP-litellm groups + role claim mapping
Bring LiteLLM into the two-tier RBAC and map groups to LiteLLM roles. - akP-litellm-admin / akP-litellm-user permission groups (bound to the litellm app for access); added to akR-global-admin / akR-standard-user roles. - Generic per-provider role_mappings: emit an app role claim computed from effective (hierarchical) group membership. LiteLLM: emits `litellm_role` (proxy_admin for akP-litellm-admin, internal_user for akP-litellm-user, else internal_user_view_only); LiteLLM reads it via GENERIC_USER_ROLE_ATTRIBUTE. Validated: plan 5 to add, 3 to change; generated role expression renders correctly.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Permission group akP-litellm-admin (name = filename). Grants admin
|
||||
# access to litellm: bound to the litellm application and mapped to its role.
|
||||
application: litellm
|
||||
@@ -0,0 +1,3 @@
|
||||
# Permission group akP-litellm-user (name = filename). Grants user
|
||||
# access to litellm: bound to the litellm application and mapped to its role.
|
||||
application: litellm
|
||||
@@ -16,3 +16,14 @@ scope_mappings:
|
||||
redirect_uris:
|
||||
- matching_mode: strict
|
||||
url: https://litellm.k8s.syd1.au.unkin.net/sso/callback
|
||||
# Emit a `litellm_role` claim from group membership (rules in privilege order).
|
||||
# LiteLLM requests the `litellm_role` scope and reads it via
|
||||
# GENERIC_USER_ROLE_ATTRIBUTE. akP-litellm-admin is inherited by akR-global-admin.
|
||||
role_mappings:
|
||||
claim: litellm_role
|
||||
default: internal_user_view_only
|
||||
rules:
|
||||
- group: akP-litellm-admin
|
||||
role: proxy_admin
|
||||
- group: akP-litellm-user
|
||||
role: internal_user
|
||||
|
||||
@@ -3,3 +3,4 @@ permissions:
|
||||
- akP-grafana-admin
|
||||
- akP-argocd-admin
|
||||
- akP-rancher-admin
|
||||
- akP-litellm-admin
|
||||
|
||||
@@ -3,3 +3,4 @@ permissions:
|
||||
- akP-grafana-user
|
||||
- akP-argocd-user
|
||||
- akP-rancher-user
|
||||
- akP-litellm-user
|
||||
|
||||
@@ -65,6 +65,43 @@ resource "authentik_provider_saml" "this" {
|
||||
signing_kp = each.value.signing_kp
|
||||
}
|
||||
|
||||
# Build a Python expression per app that emits a role claim from the user's
|
||||
# effective (hierarchical) group membership. Assembled from the config rules so
|
||||
# the generated code has predictable indentation (no template-directive quirks).
|
||||
locals {
|
||||
role_mapping_expr = {
|
||||
for k, v in var.providers_oauth2 : k => join("\n", concat(
|
||||
[
|
||||
"groups = {}",
|
||||
"pending = list(user.ak_groups.all())",
|
||||
"while pending:",
|
||||
" grp = pending.pop()",
|
||||
" if grp.pk in groups:",
|
||||
" continue",
|
||||
" groups[grp.pk] = grp.name",
|
||||
" pending += list(grp.parents.all())",
|
||||
"names = set(groups.values())",
|
||||
],
|
||||
flatten([
|
||||
for rule in coalesce(try(v.role_mappings.rules, null), []) : [
|
||||
"if ${jsonencode(rule.group)} in names:",
|
||||
" return {${jsonencode(try(v.role_mappings.claim, ""))}: ${jsonencode(rule.role)}}",
|
||||
]
|
||||
]),
|
||||
["return {${jsonencode(try(v.role_mappings.claim, ""))}: ${jsonencode(try(v.role_mappings.default, ""))}}"],
|
||||
))
|
||||
if v.role_mappings != null
|
||||
}
|
||||
}
|
||||
|
||||
resource "authentik_property_mapping_provider_scope" "role" {
|
||||
for_each = { for k, v in var.providers_oauth2 : k => v if v.role_mappings != null }
|
||||
|
||||
name = "unkin: ${each.key} role"
|
||||
scope_name = each.value.role_mappings.claim
|
||||
expression = local.role_mapping_expr[each.key]
|
||||
}
|
||||
|
||||
# Resolve oauth2 flows by slug and scope mappings by managed identifier, and
|
||||
# read client secrets from Vault so nothing sensitive is committed.
|
||||
data "authentik_flow" "oauth2_authorization" {
|
||||
@@ -100,6 +137,7 @@ resource "authentik_provider_oauth2" "this" {
|
||||
property_mappings = concat(
|
||||
try(data.authentik_property_mapping_provider_scope.oauth2[each.key].ids, []),
|
||||
[authentik_property_mapping_provider_scope.groups_hierarchical.id],
|
||||
try([authentik_property_mapping_provider_scope.role[each.key].id], []),
|
||||
)
|
||||
signing_key = each.value.signing_key
|
||||
access_token_validity = each.value.access_token_validity
|
||||
|
||||
@@ -76,6 +76,18 @@ variable "providers_oauth2" {
|
||||
})), [])
|
||||
signing_key = optional(string, null)
|
||||
access_token_validity = optional(string, "minutes=10")
|
||||
# Optional app-role claim computed from (hierarchical) group membership: emit
|
||||
# `claim` = the first matching rule's role, else `default`. The app requests
|
||||
# `claim` as a scope and reads it (e.g. LiteLLM GENERIC_USER_ROLE_ATTRIBUTE).
|
||||
# rules are evaluated in order, so list highest privilege first.
|
||||
role_mappings = optional(object({
|
||||
claim = string
|
||||
default = string
|
||||
rules = list(object({
|
||||
group = string # permission group name (akP-*)
|
||||
role = string # app role value
|
||||
}))
|
||||
}), null)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user