8ec8bbda8e
The Authentik API token used by estate automation was created by hand in the UI and pasted into Vault, so it was undocumented, unauditable and impossible to rotate reproducibly. Model it as config instead. Add a service_accounts config kind, discovered from config/service_accounts/ like the other kinds. Each entry creates a service_account user, an RBAC role carrying its global permissions, its API tokens, and (optionally) a kv-v2 write publishing each token key. Add sa-agent-api granting view_outpost, view_token and view_token_key, with a non-expiring api token agent-api-token published to kv/service/authentik/agent-api-token.
155 lines
6.3 KiB
Terraform
155 lines
6.3 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 = {}
|
|
}
|
|
|
|
# Name of the RSA certificate keypair used to sign OAuth2 ID tokens (RS256) for
|
|
# every provider that does not set its own signing_key. Defaults to Authentik's
|
|
# built-in self-signed RSA keypair.
|
|
variable "oauth2_signing_key_name" {
|
|
type = string
|
|
default = "authentik Self-signed Certificate"
|
|
}
|
|
|
|
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 = {}
|
|
}
|
|
|
|
# Machine identities for automation (agents, CI). Each entry creates a service
|
|
# account user, an RBAC role carrying its global permissions, and any API tokens
|
|
# it needs. The username is the map key (the config filename).
|
|
variable "service_accounts" {
|
|
type = map(object({
|
|
name = optional(string, null) # display name; defaults to the key
|
|
description = optional(string, "")
|
|
# Global RBAC permissions granted via a dedicated role, in
|
|
# `<app_label>.<codename>` form (e.g. authentik_outposts.view_outpost).
|
|
permissions = optional(list(string), [])
|
|
# API tokens keyed by identifier. Set `vault` to publish the generated key
|
|
# into kv-v2 so consumers read it from Vault instead of an operator pasting it.
|
|
tokens = optional(map(object({
|
|
description = optional(string, "")
|
|
expiring = optional(bool, false)
|
|
vault = optional(object({
|
|
mount = string
|
|
path = string
|
|
key = optional(string, "token")
|
|
}), null)
|
|
})), {})
|
|
}))
|
|
default = {}
|
|
}
|