Files
terraform-authentik/modules/authentik/variables.tf
T
unkinben a93205bc82
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
Set explicit launch URL for the LiteLLM application
Ben (akR-global-admin) does not see the LiteLLM tile on the Authentik user
dashboard, while ArgoCD/Grafana/Rancher appear normally. The live API shows
LiteLLM is configured identically to those apps: the app exists, its access
binding akP-litellm-admin -> litellm is present, and akR-global-admin nests
akP-litellm-admin (bidirectionally, same as the others). A CI-style plan against
live state reports "No changes" -- so this is not terraform-correctable drift,
and a plain re-apply fixes nothing. Yet check_access for Ben returns
passing=false for litellm and passing=true for the rest: a stale cached access
policy result inside Authentik.

Add an optional per-app launch_url to the providers_oauth2 config (default null,
which keeps Authentik's redirect-derived URL) and wire it to the application's
meta_launch_url. Set it for LiteLLM to its UI. This makes the dashboard tile
deterministic and, on apply, re-saves the application -- invalidating the stale
access-policy cache so Ben's (already-correct) access re-evaluates and the tile
appears.

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
2026-07-31 20:46:35 +10:00

122 lines
5.0 KiB
Terraform

variable "groups" {
type = map(object({
name = string
is_superuser = optional(bool, false)
# PKs of existing parent groups. These must be literal group PKs, not keys
# into this map: authentik_group cannot reference itself.
parents = optional(list(string), null)
attributes = optional(map(string), {})
}))
default = {}
}
# Two-tier RBAC. Permission groups (akP-*) are the atomic units mapped to app
# roles and bound to applications for access. Role groups (akR-*) are what users
# are assigned to; each nests permission groups via `parents`, so a member of a
# 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({
# 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)
attributes = optional(map(string), {})
}))
default = {}
}
variable "role_groups" {
type = map(object({
# keys into var.permission_groups that this role nests (becomes its parents).
permissions = optional(list(string), [])
is_superuser = optional(bool, false)
attributes = optional(map(string), {})
}))
default = {}
}
variable "providers_saml" {
type = map(object({
name = string
authorization_flow = string
invalidation_flow = string
acs_url = string
sp_binding = optional(string, "redirect")
audience = optional(string, "")
name_id_mapping = optional(string, null)
signing_kp = optional(string, null)
}))
default = {}
}
variable "providers_oauth2" {
type = map(object({
name = string
authorization_flow = string # flow slug, resolved to id via data.authentik_flow
invalidation_flow = string # flow slug, resolved to id via data.authentik_flow
client_type = optional(string, "confidential")
client_id = string
# 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({
mount = string
path = string
}), null)
# Managed identifiers of scope property mappings (e.g.
# goauthentik.io/providers/oauth2/scope-openid). Resolved to ids.
scope_mappings = optional(list(string), [])
# OAuth2 grant types the provider permits. Authentik 2026.5 added this as an
# explicit allow-list on the provider (models default = empty); an empty list
# rejects every authorize request with "invalid_request / The request is
# otherwise malformed". Default to the standard confidential web-app set so
# authorization_code (login) and refresh_token (offline access) work.
grant_types = optional(list(string), ["authorization_code", "refresh_token"])
# allowed_redirect_uris is list(map(string)); the API always stores a
# redirect_uri_type key, so it must be set here or every plan drifts.
redirect_uris = optional(list(object({
matching_mode = optional(string, "strict")
url = string
redirect_uri_type = optional(string, "authorization")
})), [])
signing_key = optional(string, null)
access_token_validity = optional(string, "minutes=10")
# Explicit launch URL for the app tile on the user dashboard ("My
# applications"). Null lets Authentik derive it from the first redirect_uri;
# set it to the app's UI to make the tile deterministic and to force an
# application re-save (which invalidates Authentik's cached access policy).
launch_url = optional(string, null)
# 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 = {}
}
variable "providers_ldap" {
type = map(object({
name = string
bind_flow = string
unbind_flow = string
base_dn = string
certificate = optional(string, null)
tls_server_name = optional(string, null)
uid_start_number = optional(number, 2000)
gid_start_number = optional(number, 4000)
search_mode = optional(string, "direct")
bind_mode = optional(string, "direct")
mfa_support = optional(bool, true)
}))
default = {}
}