infra/stacks/postiz/authentik.tf
Viktor Barzin 8236ae309d
All checks were successful
ci/woodpecker/push/default Pipeline was successful
postiz: reconcile HCL to live (adopt unmerged stack config), keep parked
postiz's live deployment (Helm + Temporal + Elasticsearch + Authentik
OIDC + static-DB password) came from the never-merged branch
`wizard/postiz-cnpg-oidc`, so master's HCL was stale and a `terragrunt
apply` would have DESTROYED the stack. This lands that postiz config to
master so HCL == state == live (CI green; destroy-landmine gone).

Kept PARKED (postiz + temporal replicas = 0): IG-via-postiz is Meta-
blocked (it hardcodes retired Instagram scopes → OAuth "Invalid Scopes"),
which is why it was parked; IG runs via the instagram-poster service. To
revive later: flip postiz `replicaCount` + temporal `replicas` back to 1
and re-check image pins.

Notes captured in this reconcile:
- ES image pinned to 7.17.28 (the branch's 7.17.24 was a DOWNGRADE vs the
  live data → ES refused to start "cannot downgrade node 7.17.28→7.17.24";
  caught + rolled back during this work).
- The 4 Authentik resources (app/provider/group/binding) were re-imported
  into state (adopted, not recreated — no duplicate AK objects); the
  obsolete `external_secret_jwt` ExternalSecret was removed (Retain → its
  synced secret was kept).
- Vault-side cleanup (removing the unused pg-postiz rotated role) is
  deliberately NOT included here — deferred, postiz uses a static
  secret/postiz database_url.

State was already reconciled by a local `scripts/tg apply`; this commit is
the HCL catch-up (CI re-apply is a no-op).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:54:59 +00:00

112 lines
3.7 KiB
HCL

# Authentik OIDC for the Postiz UI (2026-06-16, issue #45).
#
# Postiz keeps the Authentik forward-auth outer gate (ingress auth="required")
# AND offers "Login with Authentik" via generic OIDC. Postiz cannot disable its
# own local-login endpoint, so forward-auth stays in front to keep that endpoint
# off the public internet; OIDC is the SSO path. Access is gated to the
# `Postiz Users` group (Viktor + Anca) bound to this application — non-members
# cannot complete the OIDC flow. confidential client (Postiz is a server-side
# web app and can hold the secret); the secret is passed into the Helm release.
#
# Provider/app/group pattern mirrors stacks/tripit/authentik.tf. RS256 signing
# key (the self-signed cert) — required or Authentik signs HS256 with an empty
# JWKS and token verification fails.
data "vault_kv_secret_v2" "authentik_tf" {
mount = "secret"
name = "authentik"
}
provider "authentik" {
url = "https://authentik.viktorbarzin.me"
token = data.vault_kv_secret_v2.authentik_tf.data["tf_api_token"]
}
data "authentik_flow" "default_authorization_implicit_consent" {
slug = "default-provider-authorization-implicit-consent"
}
data "authentik_flow" "default_provider_invalidation" {
slug = "default-provider-invalidation-flow"
}
data "authentik_certificate_key_pair" "signing" {
name = "authentik Self-signed Certificate"
}
data "authentik_property_mapping_provider_scope" "openid" {
managed = "goauthentik.io/providers/oauth2/scope-openid"
}
data "authentik_property_mapping_provider_scope" "profile" {
managed = "goauthentik.io/providers/oauth2/scope-profile"
}
data "authentik_property_mapping_provider_scope" "email" {
managed = "goauthentik.io/providers/oauth2/scope-email"
}
# Usernames in this Authentik instance ARE the user's email (see
# stacks/authentik/t3-users.tf). Viktor + Anca are the only Postiz users.
data "authentik_user" "viktor" {
username = "vbarzin@gmail.com"
}
data "authentik_user" "anca" {
username = "ancaelena98@gmail.com"
}
resource "authentik_provider_oauth2" "postiz" {
name = "postiz"
client_id = "postiz"
client_type = "confidential"
# sub = the user's email (stable). Postiz keys OIDC accounts by the subject;
# an email sub keeps Anca's and Viktor's identities stable across logins.
sub_mode = "user_email"
authorization_flow = data.authentik_flow.default_authorization_implicit_consent.id
invalidation_flow = data.authentik_flow.default_provider_invalidation.id
# Postiz hardcodes its OIDC redirect to ${FRONTEND_URL}/settings.
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://postiz.viktorbarzin.me/settings"
},
]
access_token_validity = "hours=1"
refresh_token_validity = "days=30"
include_claims_in_id_token = true
signing_key = data.authentik_certificate_key_pair.signing.id
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.profile.id,
data.authentik_property_mapping_provider_scope.email.id,
]
}
resource "authentik_application" "postiz" {
name = "Postiz"
slug = "postiz"
protocol_provider = authentik_provider_oauth2.postiz.id
meta_launch_url = "https://postiz.viktorbarzin.me"
# "any" + at least one binding => only members of a bound group get access.
policy_engine_mode = "any"
}
# Access gate: only these two users may complete the Postiz OIDC flow.
resource "authentik_group" "postiz_users" {
name = "Postiz Users"
users = [
data.authentik_user.viktor.id,
data.authentik_user.anca.id,
]
}
resource "authentik_policy_binding" "postiz_access" {
target = authentik_application.postiz.uuid
group = authentik_group.postiz_users.id
order = 0
}