230db5ad7e
Adds config/providers_oauth2/vault.yaml so human logins to OpenBao go
through Authentik SSO (bao CLI and the web UI). Machine auth (approle,
kubernetes, CI) and break-glass are unchanged and stay on the OpenBao side.
Extends the oauth2 provider module so a config may generate its own client
secret instead of reading a pre-seeded one: client_secret_vault.generate
creates a random_password and writes {client_id, client_secret} to the given
kv-v2 path. Providers without the flag keep the existing read-only data source
behaviour. This is what lets the provider land with no manual Vault seed.
Gates the new application with akP-vault-admin and nests it in
akR-global-admin, matching how every other app in this repo is bound.
159 lines
6.5 KiB
Terraform
159 lines
6.5 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.
|
|
# generate = true flips that around: the secret is created here and written
|
|
# to that path as {client_id, client_secret}, so onboarding needs no manual
|
|
# seed. Requires write access to the kv path.
|
|
client_secret_vault = optional(object({
|
|
mount = string
|
|
path = string
|
|
generate = optional(bool, false)
|
|
}), 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 = {}
|
|
}
|