Manage the agent API service account and token in Terraform #30
@@ -9,6 +9,7 @@ Terraform configuration for managing the Authentik identity provider at identity
|
||||
- **OAuth2/OIDC providers** — OAuth2 and OpenID Connect integrations
|
||||
- **LDAP providers** — LDAP provider and outpost configuration
|
||||
- **Applications** — application definitions linked to providers
|
||||
- **Service accounts** — machine identities with RBAC roles and API tokens (keys published to Vault kv)
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -19,7 +20,8 @@ config/
|
||||
├── groups/ # Group definitions
|
||||
├── providers_saml/ # SAML provider definitions
|
||||
├── providers_oauth2/ # OAuth2/OIDC provider definitions
|
||||
└── providers_ldap/ # LDAP provider definitions
|
||||
├── providers_ldap/ # LDAP provider definitions
|
||||
└── service_accounts/ # Automation service accounts + API tokens
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -37,5 +37,10 @@ locals {
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "providers_ldap/")
|
||||
}
|
||||
service_accounts = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "service_accounts/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Service account sa-agent-api (username = filename). Machine identity for
|
||||
# estate automation that needs to read Authentik outpost tokens; replaces the
|
||||
# hand-created token an operator used to paste into Vault.
|
||||
name: Agent API
|
||||
permissions:
|
||||
# List outposts and read their bootstrap token keys.
|
||||
- authentik_outposts.view_outpost
|
||||
- authentik_core.view_token
|
||||
- authentik_core.view_token_key
|
||||
tokens:
|
||||
agent-api-token:
|
||||
description: >-
|
||||
Used by agentvault seed-outpost to look up Authentik outpost tokens.
|
||||
Managed by terraform-authentik; key published to Vault kv.
|
||||
expiring: false
|
||||
vault:
|
||||
mount: kv
|
||||
path: service/authentik/agent-api-token
|
||||
key: token
|
||||
@@ -23,4 +23,5 @@ inputs = {
|
||||
providers_saml = local.config.providers_saml
|
||||
providers_oauth2 = local.config.providers_oauth2
|
||||
providers_ldap = local.config.providers_ldap
|
||||
service_accounts = local.config.service_accounts
|
||||
}
|
||||
|
||||
@@ -242,3 +242,74 @@ resource "authentik_policy_binding" "app_access" {
|
||||
group = authentik_group.permission[each.key].id
|
||||
order = 0
|
||||
}
|
||||
|
||||
# Service accounts: non-human identities for automation. Kept out of the group
|
||||
# hierarchy above (which models human app access) and given capabilities through
|
||||
# RBAC roles instead.
|
||||
resource "authentik_user" "service_account" {
|
||||
for_each = var.service_accounts
|
||||
|
||||
username = each.key
|
||||
name = coalesce(each.value.name, each.key)
|
||||
type = "service_account"
|
||||
# roles is only populated for accounts that declare permissions; try() keeps
|
||||
# the reference lazy so accounts without a role still plan.
|
||||
roles = try([authentik_rbac_role.service_account[each.key].id], [])
|
||||
}
|
||||
|
||||
# One role per service account carrying its global permissions.
|
||||
# authentik_rbac_permission_user is deprecated in favour of the role form, so
|
||||
# permissions are attached to a role and the role to the account.
|
||||
resource "authentik_rbac_role" "service_account" {
|
||||
for_each = { for k, v in var.service_accounts : k => v if length(v.permissions) > 0 }
|
||||
|
||||
name = each.key
|
||||
}
|
||||
|
||||
locals {
|
||||
service_account_permissions = merge([
|
||||
for k, v in var.service_accounts : {
|
||||
for perm in v.permissions : "${k}/${perm}" => {
|
||||
service_account = k
|
||||
permission = perm
|
||||
}
|
||||
}
|
||||
]...)
|
||||
|
||||
service_account_tokens = merge([
|
||||
for k, v in var.service_accounts : {
|
||||
for identifier, t in v.tokens : identifier => merge(t, { service_account = k })
|
||||
}
|
||||
]...)
|
||||
}
|
||||
|
||||
resource "authentik_rbac_permission_role" "service_account" {
|
||||
for_each = local.service_account_permissions
|
||||
|
||||
role = authentik_rbac_role.service_account[each.value.service_account].id
|
||||
permission = each.value.permission
|
||||
}
|
||||
|
||||
# retrieve_key is required for `key` to be populated; without it the attribute
|
||||
# stays empty and nothing can be published to Vault.
|
||||
resource "authentik_token" "service_account" {
|
||||
for_each = local.service_account_tokens
|
||||
|
||||
identifier = each.key
|
||||
user = authentik_user.service_account[each.value.service_account].id
|
||||
description = each.value.description
|
||||
intent = "api"
|
||||
expiring = each.value.expiring
|
||||
retrieve_key = true
|
||||
}
|
||||
|
||||
# Publish token keys to kv-v2 so consumers (agentvault, CI) read them from Vault.
|
||||
# The key also lands in Terraform state, same as the oauth2 client secrets this
|
||||
# module already reads.
|
||||
resource "vault_kv_secret_v2" "service_account_token" {
|
||||
for_each = { for k, v in local.service_account_tokens : k => v if v.vault != null }
|
||||
|
||||
mount = each.value.vault.mount
|
||||
name = each.value.vault.path
|
||||
data_json = jsonencode({ (each.value.vault.key) = authentik_token.service_account[each.key].key })
|
||||
}
|
||||
|
||||
@@ -127,3 +127,28 @@ variable "providers_ldap" {
|
||||
}))
|
||||
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 = {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user