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
|
- **OAuth2/OIDC providers** — OAuth2 and OpenID Connect integrations
|
||||||
- **LDAP providers** — LDAP provider and outpost configuration
|
- **LDAP providers** — LDAP provider and outpost configuration
|
||||||
- **Applications** — application definitions linked to providers
|
- **Applications** — application definitions linked to providers
|
||||||
|
- **Service accounts** — machine identities with RBAC roles and API tokens (keys published to Vault kv)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@ config/
|
|||||||
├── groups/ # Group definitions
|
├── groups/ # Group definitions
|
||||||
├── providers_saml/ # SAML provider definitions
|
├── providers_saml/ # SAML provider definitions
|
||||||
├── providers_oauth2/ # OAuth2/OIDC 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
|
## Usage
|
||||||
|
|||||||
@@ -37,5 +37,10 @@ locals {
|
|||||||
trimsuffix(basename(file_path), ".yaml") => content
|
trimsuffix(basename(file_path), ".yaml") => content
|
||||||
if startswith(file_path, "providers_ldap/")
|
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_saml = local.config.providers_saml
|
||||||
providers_oauth2 = local.config.providers_oauth2
|
providers_oauth2 = local.config.providers_oauth2
|
||||||
providers_ldap = local.config.providers_ldap
|
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
|
group = authentik_group.permission[each.key].id
|
||||||
order = 0
|
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 = {}
|
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