Authentik OAuth2 with Terraform (Day 35)
I recently started using Authentik to provide auth for my services and applications in the homelab.
Authentik is an open-source identity provider that supports OAuth2, SAML, and more, and comes with a Terraform provider, so naturally, I defaulted to managing everything that way.
This means I no longer need to deal with multiple logins. Authentik acts as a single sign-on solution, letting me authenticate once and access everything.
See https://goauthentik.io/ for more info.
Set up
Create a token on Authentik's console under "Tokens and App passwords" and use it to set up the terraform provider
provider "authentik" {
url = "<the authentik url>"
token = "<api token>"
}Remember to use something like sops to encrypt your token / secrets.
Get the existing flows that Authentik provides out of the box:
data "authentik_flow" "default-authorization-flow" {
slug = "default-provider-authorization-explicit-consent"
}
data "authentik_flow" "default-invalidation-flow" {
slug = "default-provider-invalidation-flow"
}These flows handle the authorization process and session invalidation. One can still create other flows, but there are plenty of preconfigured flows that just work.
Creating OAuth2 providers
Then create the authentik_provider_oauth2 resource for each application that needs and supports it.
resource "authentik_provider_oauth2" "this" {
for_each = var.authentik_application
name = each.key
client_id = random_string.client_id[each.key].id
client_secret = random_password.client_secret[each.key].result
authorization_flow = data.authentik_flow.default-authorization-flow.id
invalidation_flow = data.authentik_flow.default-invalidation-flow.id
refresh_token_validity = var.refresh_token_validity
allowed_redirect_uris = each.value.allowed_redirect_uris
property_mappings = var.property_mappings
sub_mode = var.sub_mode
}Adding access policies
Add expression policies and bind them to the application. expression policies control who can access what, and are Python expressions that evaluate to true or false:
resource "authentik_policy_expression" "policy" {
name = var.policy_expression.name
expression = var.policy_expression.expression
}
resource "authentik_policy_binding" "app-access" {
for_each = var.authentik_application
target = authentik_application.this[each.key].uuid
policy = authentik_policy_expression.policy.id
order = 0
}Creating applications
The applications themselves are straightforward and tie everything together:
resource "authentik_application" "this" {
for_each = var.authentik_application
name = try(each.value.name, each.key)
slug = each.key
meta_icon = var.app_meta_icon
protocol_provider = authentik_provider_oauth2.this[each.key].id
}Property mappings
These tripped me up initially. They define what user information gets passed to the application during authentication:
variable "property_mappings" {
# authentik default OAuth Mapping: OpenID 'email'
# authentik default OAuth Mapping: OpenID 'openid
# authentik default OAuth Mapping: OpenID 'profile'
default = [
"4c94fd1d-1655-498f-94dc-e3be8506e0ec",
"8bb80d61-1994-4538-9942-633b45ecd879",
"660390cb-184a-4260-a4f0-7d69488a3037",
]
}
These UUIDs correspond to the default mappings in Authentik. Without them, authentication might work, but e.g, in Proxmox you may get a lot of 401. I suppose that's because it wasn't receiving the required user info.
Next Steps
- Integrating more services and maybe exploring SAML for applications that don't play nice with OAuth2.
- Figure out how to get Cilium ingress and Authentik working together