3 Commits

Author SHA1 Message Date
unkinben 00a122135e Use identity.k8s.syd1.au.unkin.net as provider endpoint
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
2026-06-28 12:11:47 +10:00
unkinben 8aa2273dcf Fix provider schema for goauthentik/authentik 2026.5.0
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
- group: parent → parents (list)
- saml/oauth2: add required invalidation_flow
- oauth2: remove redirect_uris (use allowed_redirect_uris via config)
- ldap: replace authorization_flow/search_group with bind_flow/unbind_flow
- Add versions.tf with required_providers block
- Remove service_connection from outpost (auto-discovered)
2026-06-28 12:04:19 +10:00
unkinben 4042760a16 Initial scaffold
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline failed
- Terraform module for groups, SAML/OAuth2/LDAP providers, applications, and LDAP outposts
- Data-driven YAML config with Terragrunt config loader
- Environment: identity.unkin.net with Consul backend
- Provider: goauthentik/authentik 2026.5.0
- Woodpecker CI pipelines (pre-commit, plan, apply)
- Makefile with Vault AppRole and K8s auth support
2026-06-28 11:55:26 +10:00
14 changed files with 416 additions and 1 deletions
+6
View File
@@ -0,0 +1,6 @@
.terraform/
*.tfstate
*.tfstate.backup
*.tfplan
backend.tf
.terragrunt-cache/
+24
View File
@@ -0,0 +1,24 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
types: [yaml]
- id: trailing-whitespace
types: [yaml]
- repo: https://github.com/gruntwork-io/pre-commit
rev: v0.1.30
hooks:
- id: tofu-fmt
- id: tofu-validate
- id: tflint
- id: terragrunt-hcl-fmt
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
hooks:
- id: yamllint
args:
[
"-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}",
"-s",
]
+23
View File
@@ -0,0 +1,23 @@
when:
- event: push
branch: main
steps:
- name: apply
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
commands:
- dnf install vault -y
- make plan
- make apply
backend_options:
kubernetes:
serviceAccountName: terraform-authentik
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+21
View File
@@ -0,0 +1,21 @@
when:
- event: pull_request
steps:
- name: plan
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
environment:
VAULT_AUTH_METHOD: kubernetes
commands:
- dnf install vault -y
- make plan
backend_options:
kubernetes:
serviceAccountName: terraform-authentik
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+18
View File
@@ -0,0 +1,18 @@
when:
- event: pull_request
steps:
- name: pre-commit
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
commands:
- uvx pre-commit run --all-files
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 512Mi
cpu: 1
limits:
memory: 2Gi
cpu: 2
+34
View File
@@ -0,0 +1,34 @@
.PHONY: init plan apply format
VAULT_AUTH_METHOD ?= approle
VAULT_K8S_ROLE ?= woodpecker_terraform_authentik
VAULT_K8S_MOUNT ?= auth/k8s/au/syd1
VAULT_K8S_JWT_PATH ?= /var/run/secrets/kubernetes.io/serviceaccount/token
define vault_env
@export VAULT_ADDR="https://vault.service.consul:8200" && \
if [ "$(VAULT_AUTH_METHOD)" = "kubernetes" ]; then \
export VAULT_TOKEN=$$(vault write -field=token $(VAULT_K8S_MOUNT)/login role=$(VAULT_K8S_ROLE) jwt=$$(cat $(VAULT_K8S_JWT_PATH))); \
else \
export VAULT_TOKEN=$$(vault write -field=token auth/approle/login role_id=$$VAULT_ROLEID); \
fi && \
export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-authentik)
endef
init:
@$(call vault_env) && \
terragrunt run --all --non-interactive init -- -upgrade
plan: init
@$(call vault_env) && \
terragrunt run --all --parallelism 4 --non-interactive plan
apply: init
@$(call vault_env) && \
terragrunt run --all --parallelism 2 --non-interactive apply
format:
@echo "Formatting OpenTofu files..."
@tofu fmt -recursive .
@echo "Formatting Terragrunt files..."
@terragrunt hcl fmt
+33 -1
View File
@@ -1,3 +1,35 @@
# terraform-authentik
Terraform configuration for managing Authentik identity provider
Terraform configuration for managing the Authentik identity provider at identity.unkin.net.
## Managed Resources
- **Groups** — roles and group hierarchy (users are invited manually)
- **SAML providers** — SAML application integrations
- **OAuth2/OIDC providers** — OAuth2 and OpenID Connect integrations
- **LDAP providers** — LDAP provider and outpost configuration
- **Applications** — application definitions linked to providers
## Configuration
Resources are defined as YAML files under `config/`:
```
config/
├── groups/ # Group definitions
├── providers_saml/ # SAML provider definitions
├── providers_oauth2/ # OAuth2/OIDC provider definitions
└── providers_ldap/ # LDAP provider definitions
```
## Usage
```sh
make plan # init + plan
make apply # init + plan + apply
make format # format all .tf and .hcl files
```
### Authentication
Set `VAULT_ROLEID` for local AppRole auth, or `VAULT_AUTH_METHOD=kubernetes` for CI.
+31
View File
@@ -0,0 +1,31 @@
locals {
config_files = fileset(".", "**/*.yaml")
all_configs = {
for file_path in local.config_files :
file_path => yamldecode(file(file_path))
}
config = {
groups = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "groups/")
}
providers_saml = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "providers_saml/")
}
providers_oauth2 = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "providers_oauth2/")
}
providers_ldap = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "providers_ldap/")
}
}
}
@@ -0,0 +1,24 @@
include "root" {
path = find_in_parent_folders("root.hcl")
expose = true
}
include "config" {
path = "${get_repo_root()}/config/config.hcl"
expose = true
}
locals {
config = include.config.locals.config
}
terraform {
source = "../../modules/authentik"
}
inputs = {
groups = local.config.groups
providers_saml = local.config.providers_saml
providers_oauth2 = local.config.providers_oauth2
providers_ldap = local.config.providers_ldap
}
+32
View File
@@ -0,0 +1,32 @@
generate "backend" {
path = "backend.tf"
if_exists = "overwrite"
contents = <<EOF
provider "authentik" {
url = "https://${path_relative_to_include()}"
token = var.authentik_token
}
variable "authentik_token" {
type = string
sensitive = true
}
terraform {
backend "consul" {
address = "https://consul.service.consul"
path = "infra/terraform/authentik/${path_relative_to_include()}/state"
scheme = "https"
lock = true
ca_file = "/etc/pki/tls/certs/ca-bundle.crt"
}
required_version = ">= 1.10"
required_providers {
authentik = {
source = "goauthentik/authentik"
version = "2026.5.0"
}
}
}
EOF
}
+23
View File
@@ -0,0 +1,23 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/goauthentik/authentik" {
version = "2026.5.0"
constraints = ">= 2026.5.0"
hashes = [
"h1:SeznjPKBzSrgo8WasRnuxiGMDSeQHEKsv3U/xw8bhQE=",
"zh:0dc1706f6fbff866f4a96de56a4934b9a277954bcdd0713549a29a9b8ec85153",
"zh:218417ec4e864f2d7e585d6c08d39bccb96d8f3bca16c6f762be15365e434234",
"zh:24f9afa7a1174316da3478811848cd76ef348d8a983310b8d75ed6f45abe1a92",
"zh:560092e47cb8a72b890b3eeafe1803202cd25cf27f5f5a6e2c370f645f5d86ae",
"zh:5bc69d8de198007ad1587e146f98cffacf0d1a571800da549b308ff5f4541474",
"zh:65248dce941472ad2a30d0754d2f3c2db6bb6fe5080946316fb097d6ba7cc79f",
"zh:79c9a59a8d3c60280e27a064668889594da44c60f940b046b7c8e63be01067d0",
"zh:87f26cadcd842d6e6d0af94ef0e56860557f5d07f487b10d69d38b63af68bea5",
"zh:8e42c9d0e77d61cc2e5f8c8b761f6e484774d93771927b4cb5fbdae41209dd33",
"zh:94ff632b9b4841527c6b652d51a850a8a47c84c0308a3efc189e0ff7e2558f87",
"zh:b8d32d9f17a905b63c87a23306c02c295b7c8b70f72950071aa3086396932816",
"zh:c91982af99474fc2e4e69be36ed3a68847f261963ed79f6a546fc75703992f99",
"zh:eb9c1fd3020cf61e9b7a6a38d2965f4b521495a9928705e963459a4af857f97d",
]
}
+83
View File
@@ -0,0 +1,83 @@
resource "authentik_group" "this" {
for_each = var.groups
name = each.value.name
is_superuser = each.value.is_superuser
parents = each.value.parents != null ? [for p in each.value.parents : authentik_group.this[p].id] : []
attributes = jsonencode(each.value.attributes)
}
resource "authentik_provider_saml" "this" {
for_each = var.providers_saml
name = each.value.name
authorization_flow = each.value.authorization_flow
invalidation_flow = each.value.invalidation_flow
acs_url = each.value.acs_url
sp_binding = each.value.sp_binding
audience = each.value.audience
name_id_mapping = each.value.name_id_mapping
signing_kp = each.value.signing_kp
}
resource "authentik_provider_oauth2" "this" {
for_each = var.providers_oauth2
name = each.value.name
authorization_flow = each.value.authorization_flow
invalidation_flow = each.value.invalidation_flow
client_type = each.value.client_type
client_id = each.value.client_id
client_secret = each.value.client_secret
property_mappings = each.value.property_mappings
signing_key = each.value.signing_key
access_token_validity = each.value.access_token_validity
}
resource "authentik_provider_ldap" "this" {
for_each = var.providers_ldap
name = each.value.name
bind_flow = each.value.bind_flow
unbind_flow = each.value.unbind_flow
base_dn = each.value.base_dn
certificate = each.value.certificate
tls_server_name = each.value.tls_server_name
uid_start_number = each.value.uid_start_number
gid_start_number = each.value.gid_start_number
search_mode = each.value.search_mode
bind_mode = each.value.bind_mode
mfa_support = each.value.mfa_support
}
resource "authentik_application" "saml" {
for_each = var.providers_saml
name = each.value.name
slug = each.key
protocol_provider = authentik_provider_saml.this[each.key].id
}
resource "authentik_application" "oauth2" {
for_each = var.providers_oauth2
name = each.value.name
slug = each.key
protocol_provider = authentik_provider_oauth2.this[each.key].id
}
resource "authentik_application" "ldap" {
for_each = var.providers_ldap
name = each.value.name
slug = each.key
protocol_provider = authentik_provider_ldap.this[each.key].id
}
resource "authentik_outpost" "ldap" {
for_each = var.providers_ldap
name = "${each.key}-outpost"
type = "ldap"
protocol_providers = [authentik_provider_ldap.this[each.key].id]
}
+55
View File
@@ -0,0 +1,55 @@
variable "groups" {
type = map(object({
name = string
is_superuser = optional(bool, false)
parents = optional(list(string), null)
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
invalidation_flow = string
client_type = optional(string, "confidential")
client_id = string
client_secret = optional(string, null)
property_mappings = optional(list(string), [])
signing_key = optional(string, null)
access_token_validity = optional(string, "minutes=10")
}))
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 = {}
}
+9
View File
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
authentik = {
source = "goauthentik/authentik"
version = ">= 2026.5.0"
}
}
}