feat: manage Keycloak realm config via Terraform provider #142

Closed
opened 2026-03-21 21:49:01 +00:00 by forgejo_admin · 4 comments

Type

Feature

Lineage

plan-pal-e-platform → Platform Hardening → discovered scope from Keycloak theme deployment (PR #130)

Repo

forgejo_admin/pal-e-platform

User Story

As a platform operator
I want Keycloak realm settings (login theme, clients, etc.) managed declaratively in Terraform
So that tofu apply configures everything — no manual Admin API calls or console clicks

Labels: story:keycloak-iac, arch:keycloak, type:feature

Context

PR #130 deployed a custom Keycloak login theme via ConfigMap, but the realm-level setting (loginTheme: "westside") had to be applied separately via the Keycloak Admin REST API. This is a gap — theme files are in Terraform but realm config is not.

The Terraform Keycloak provider (mrparkers/keycloak) can manage realms, clients, theme assignments, and more as Terraform resources. Adopting it would close this gap and bring all Keycloak config under IaC.

[SCOPE] Potential Redundancy with Phase 28

Phase 28 (board items #276, #277, #278 — all DONE) already imported Keycloak realms and clients into Terraform via pal-e-services. The mrparkers/keycloak provider is active there and managing realm config declaratively.

Before implementing this ticket, verify what (if anything) remains undone after Phase 28. This ticket may be partially or fully redundant. Check:

  • Are all realms (master, westside-basketball, mcd-tracker) imported in pal-e-services?
  • Are client configurations already managed?
  • Is the login theme assignment already declared?
  • Does any Keycloak config still belong in pal-e-platform vs. pal-e-services?

If Phase 28 covers everything, close this ticket as redundant.

File Targets

Files the agent should modify:

  • terraform/main.tf — add Keycloak provider, import existing realms/clients as resources
  • terraform/providers.tf or equivalent — declare Keycloak provider with admin credentials

Files the agent should NOT touch:

  • keycloak/themes/ — theme files are already managed correctly

Acceptance Criteria

  • tofu apply sets realm login themes (no manual API calls)
  • Existing realms (master, westside-basketball, mcd-tracker) imported without drift
  • Client configurations for westside-app and other OIDC clients managed in Terraform

Test Expectations

  • tofu plan shows no changes after initial import (clean state)
  • Changing login_theme in Terraform and applying updates the realm
  • Run command: cd ~/pal-e-platform/terraform && tofu plan -lock=false

Constraints

  • Must import existing Keycloak state cleanly — no destructive changes on first apply
  • Keycloak admin credentials already in Salt pillar, reuse them
  • This is a significant scope expansion — may warrant its own plan phase

Checklist

  • Verify Phase 28 coverage before starting any work
  • PR opened
  • Tests pass
  • No unrelated changes
  • project-pal-e-platform — platform hardening
  • PR #130 — Keycloak theme (exposed the gap)
  • Issue #140 — secrets pillar (related infra-as-code gap)
  • Board items #276/#277/#278 — Phase 28 Keycloak imports (pal-e-services)
### Type Feature ### Lineage `plan-pal-e-platform` → Platform Hardening → discovered scope from Keycloak theme deployment (PR #130) ### Repo `forgejo_admin/pal-e-platform` ### User Story As a platform operator I want Keycloak realm settings (login theme, clients, etc.) managed declaratively in Terraform So that `tofu apply` configures everything — no manual Admin API calls or console clicks Labels: `story:keycloak-iac`, `arch:keycloak`, `type:feature` ### Context PR #130 deployed a custom Keycloak login theme via ConfigMap, but the realm-level setting (`loginTheme: "westside"`) had to be applied separately via the Keycloak Admin REST API. This is a gap — theme files are in Terraform but realm config is not. The Terraform Keycloak provider (`mrparkers/keycloak`) can manage realms, clients, theme assignments, and more as Terraform resources. Adopting it would close this gap and bring all Keycloak config under IaC. ### [SCOPE] Potential Redundancy with Phase 28 > **Phase 28** (board items #276, #277, #278 — all DONE) already imported Keycloak realms and clients into Terraform via `pal-e-services`. The `mrparkers/keycloak` provider is active there and managing realm config declaratively. > > **Before implementing this ticket, verify what (if anything) remains undone after Phase 28.** This ticket may be partially or fully redundant. Check: > - Are all realms (master, westside-basketball, mcd-tracker) imported in pal-e-services? > - Are client configurations already managed? > - Is the login theme assignment already declared? > - Does any Keycloak config still belong in pal-e-platform vs. pal-e-services? > > If Phase 28 covers everything, close this ticket as redundant. ### File Targets Files the agent should modify: - `terraform/main.tf` — add Keycloak provider, import existing realms/clients as resources - `terraform/providers.tf` or equivalent — declare Keycloak provider with admin credentials Files the agent should NOT touch: - `keycloak/themes/` — theme files are already managed correctly ### Acceptance Criteria - [ ] `tofu apply` sets realm login themes (no manual API calls) - [ ] Existing realms (master, westside-basketball, mcd-tracker) imported without drift - [ ] Client configurations for westside-app and other OIDC clients managed in Terraform ### Test Expectations - [ ] `tofu plan` shows no changes after initial import (clean state) - [ ] Changing `login_theme` in Terraform and applying updates the realm - Run command: `cd ~/pal-e-platform/terraform && tofu plan -lock=false` ### Constraints - Must import existing Keycloak state cleanly — no destructive changes on first apply - Keycloak admin credentials already in Salt pillar, reuse them - This is a significant scope expansion — may warrant its own plan phase ### Checklist - [ ] Verify Phase 28 coverage before starting any work - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-pal-e-platform` — platform hardening - PR #130 — Keycloak theme (exposed the gap) - Issue #140 — secrets pillar (related infra-as-code gap) - Board items #276/#277/#278 — Phase 28 Keycloak imports (pal-e-services)
Author
Owner

Design Spec Review: Keycloak Terraform Onboarding (Revision 2)

Spec: docs/superpowers/specs/2026-03-21-keycloak-terraform-onboarding-design.md

MUST-FIX VERIFICATION

Checking the 5 must-fix items from the first review:

1. Master realm / pal-e-app excluded with rationale -- RESOLVED

Lines 28-35: Dedicated "Scope Decision: Master Realm" section with four clear bullet points. The exclusion rationale is sound -- master realm is Keycloak's admin realm and managing it via Terraform is a lockout risk. The inventory table (line 51) marks pal-e-app as **No** (excluded with master realm). Clean.

2. Multi-client problem solved -- RESOLVED

Lines 124-162: var.keycloak_clients is a new top-level variable, decoupled from var.services. The "Why Clients Are Separate From Services" section (lines 156-162) explicitly addresses the original problem: mcd-tracker-ios has no corresponding deployed service, and multiple clients per project would force service infrastructure duplication. The k3s.tfvars example (lines 251-258) confirms services are unchanged. This is the correct architecture.

3. Client scopes contradiction resolved -- RESOLVED

Lines 80-82: Default client scopes are now listed under "Medium" risk with lifecycle { ignore_changes } as mitigation. Lines 357-367 show the actual lifecycle block on keycloak_realm.this. Line 350 in "What This Does NOT Manage" reiterates: "managed via lifecycle ignore_changes to prevent drift. Only manage explicitly when customizing." Consistent throughout.

4. secrets.auto.tfvars fiction fixed -- RESOLVED

Lines 286-301: "Secret Delivery" section correctly describes the k3s.tfvars pattern. Verified against actual codebase -- /home/ldraney/pal-e-services/terraform/k3s.tfvars has harbor_admin_password, argocd_admin_password, forgejo_argocd_token as plaintext, and *.tfvars is in .gitignore. The spec's proposed keycloak_admin_password follows the identical pattern. The CI note (line 301) is accurate -- pal-e-services has no .woodpecker.yaml.

5. Provider field names corrected -- RESOLVED

Lines 138, 141, 142: Uses public_client, pkce_code_challenge_method, valid_redirect_uris. These are the correct field names for the mrparkers/keycloak provider keycloak_openid_client resource. The k3s.tfvars example (lines 188-249) uses them consistently.

Also addressed items:

  • Import commands with real UUIDs for clients (lines 319-322) -- confirmed with proper realm/client-uuid format.
  • required_providers block shown (lines 265-271) with mrparkers/keycloak ~> 5.0.
  • SMTP exclusion documented (line 66 and line 352).
  • Plan-time dependency noted (lines 88-90) with two-phase apply reference.
  • Protocol mapper resource shown explicitly (lines 385-398) as keycloak_openid_user_realm_role_protocol_mapper.

All 5 must-fixes are resolved.

DOMAIN REVIEW (Terraform/IaC)

Positive observations:

  • Architecture decision table (lines 18-27) is crisp -- clear separation between pal-e-platform (infra) and pal-e-services (onboarding).
  • Risk surface section (lines 68-93) is unusually thorough for a design spec. Critical vs Medium vs Operational categorization with specific mitigations.
  • Import-first strategy (lines 305-337) with "zero changes" gate is the right approach. The explicit "No tofu apply until the plan shows zero changes" callout prevents the most dangerous failure mode.
  • The lifecycle { ignore_changes } patterns (lines 357-376) correctly protect both client secrets and default client scopes.
  • The for_each filtering pattern on the protocol mapper (line 386) is idiomatic Terraform.

Remaining items (nits, not blockers):

  1. Missing keycloak_role resource block. The spec shows import commands for keycloak_role.this["westside-basketball/admin"] (lines 325-327) and the variable has roles = optional(list(string), []), but the actual keycloak_role resource HCL is never shown. The implementer will need to construct the for_each that flattens realm+role into composite keys like "westside-basketball/admin". This is a non-trivial local transform (likely using flatten + for). Recommend adding the resource block to the spec for completeness, or at minimum noting the flattening pattern.

  2. Role import UUIDs are placeholders. Lines 325-327 show <role-uuid> placeholders with a comment to fetch via Admin API. The client imports (lines 319-322) have real UUIDs. For implementation consistency, either fetch all UUIDs upfront and document them, or note that role UUIDs must be fetched at implementation time. Minor inconsistency.

  3. sops_age_private_key in actual tfvars but not in variable definition. The actual k3s.tfvars has sops_age_private_key and source_repo/source_path fields that don't exist in the current variables.tf. This is pre-existing drift unrelated to this spec, but worth noting -- the implementer will encounter it during tofu plan.

  4. Provider version ~> 5.0 should be verified. The spec proposes mrparkers/keycloak ~> 5.0. The Keycloak server is quay.io/keycloak/keycloak:26.0.7 (confirmed in pal-e-platform main.tf line 1883). Provider v5.x should support Keycloak 26, but this should be confirmed during implementation -- provider/server version compatibility is a common gotcha.

  5. direct_access_grants_enabled default. The variable defaults to false (line 140), but westside-app sets it to true (line 194). The default is correct (direct access grants should be opt-in), but the spec could note that this is the Resource Owner Password Credentials flow and explain why westside-app needs it (likely legacy basketball-api auth pattern). This helps future onboarding decisions.

  6. Missing access_type consideration. The keycloak_openid_client resource in mrparkers/keycloak has access_type (one of CONFIDENTIAL, PUBLIC, BEARER-ONLY) as an alternative to public_client boolean in some provider versions. Verify which attribute the v5.x provider expects. If v5.x uses access_type string instead of public_client boolean, the variable schema needs adjustment.

BLOCKERS

None. This is a design spec, not executable code. All 5 previously identified must-fix items are resolved. The remaining items are implementation guidance (nits).

NITS

See items 1-6 in the domain review above. The most important is nit #1 (missing keycloak_role resource block) -- it is the only resource referenced in import commands that has no corresponding HCL shown.

SOP COMPLIANCE

  • Issue #142 exists and is referenced in the spec header
  • Spec lives in docs/superpowers/specs/ with date prefix
  • Architecture decisions have clear rationale
  • Risk surface documented with mitigations
  • Testing strategy defined
  • Success criteria defined
  • No secrets committed (tfvars is gitignored, confirmed)
  • Scope is bounded -- "What This Does NOT Manage" section prevents creep

PROCESS OBSERVATIONS

This design spec is high quality. The import-first strategy with a zero-changes gate is the correct approach for bringing existing stateful infrastructure under Terraform management. The risk surface analysis (client secrets, protocol mappers, custom roles, registrationEmailAsUsername) demonstrates deep understanding of the failure modes.

The two-variable architecture (keycloak_realms + keycloak_clients) is the right call. It mirrors the real-world domain model: realms are project boundaries, clients are app configurations, and they have independent lifecycles. This will scale cleanly as new projects are onboarded.

The spec is ready for implementation.

VERDICT: APPROVED

## Design Spec Review: Keycloak Terraform Onboarding (Revision 2) Spec: `docs/superpowers/specs/2026-03-21-keycloak-terraform-onboarding-design.md` ### MUST-FIX VERIFICATION Checking the 5 must-fix items from the first review: **1. Master realm / pal-e-app excluded with rationale** -- RESOLVED Lines 28-35: Dedicated "Scope Decision: Master Realm" section with four clear bullet points. The exclusion rationale is sound -- master realm is Keycloak's admin realm and managing it via Terraform is a lockout risk. The inventory table (line 51) marks `pal-e-app` as `**No** (excluded with master realm)`. Clean. **2. Multi-client problem solved** -- RESOLVED Lines 124-162: `var.keycloak_clients` is a new top-level variable, decoupled from `var.services`. The "Why Clients Are Separate From Services" section (lines 156-162) explicitly addresses the original problem: `mcd-tracker-ios` has no corresponding deployed service, and multiple clients per project would force service infrastructure duplication. The k3s.tfvars example (lines 251-258) confirms services are unchanged. This is the correct architecture. **3. Client scopes contradiction resolved** -- RESOLVED Lines 80-82: Default client scopes are now listed under "Medium" risk with `lifecycle { ignore_changes }` as mitigation. Lines 357-367 show the actual lifecycle block on `keycloak_realm.this`. Line 350 in "What This Does NOT Manage" reiterates: "managed via lifecycle ignore_changes to prevent drift. Only manage explicitly when customizing." Consistent throughout. **4. secrets.auto.tfvars fiction fixed** -- RESOLVED Lines 286-301: "Secret Delivery" section correctly describes the `k3s.tfvars` pattern. Verified against actual codebase -- `/home/ldraney/pal-e-services/terraform/k3s.tfvars` has `harbor_admin_password`, `argocd_admin_password`, `forgejo_argocd_token` as plaintext, and `*.tfvars` is in `.gitignore`. The spec's proposed `keycloak_admin_password` follows the identical pattern. The CI note (line 301) is accurate -- pal-e-services has no `.woodpecker.yaml`. **5. Provider field names corrected** -- RESOLVED Lines 138, 141, 142: Uses `public_client`, `pkce_code_challenge_method`, `valid_redirect_uris`. These are the correct field names for the `mrparkers/keycloak` provider `keycloak_openid_client` resource. The k3s.tfvars example (lines 188-249) uses them consistently. **Also addressed items:** - Import commands with real UUIDs for clients (lines 319-322) -- confirmed with proper `realm/client-uuid` format. - `required_providers` block shown (lines 265-271) with `mrparkers/keycloak ~> 5.0`. - SMTP exclusion documented (line 66 and line 352). - Plan-time dependency noted (lines 88-90) with two-phase apply reference. - Protocol mapper resource shown explicitly (lines 385-398) as `keycloak_openid_user_realm_role_protocol_mapper`. All 5 must-fixes are resolved. ### DOMAIN REVIEW (Terraform/IaC) **Positive observations:** - Architecture decision table (lines 18-27) is crisp -- clear separation between pal-e-platform (infra) and pal-e-services (onboarding). - Risk surface section (lines 68-93) is unusually thorough for a design spec. Critical vs Medium vs Operational categorization with specific mitigations. - Import-first strategy (lines 305-337) with "zero changes" gate is the right approach. The explicit "No `tofu apply` until the plan shows zero changes" callout prevents the most dangerous failure mode. - The `lifecycle { ignore_changes }` patterns (lines 357-376) correctly protect both client secrets and default client scopes. - The `for_each` filtering pattern on the protocol mapper (line 386) is idiomatic Terraform. **Remaining items (nits, not blockers):** 1. **Missing `keycloak_role` resource block.** The spec shows import commands for `keycloak_role.this["westside-basketball/admin"]` (lines 325-327) and the variable has `roles = optional(list(string), [])`, but the actual `keycloak_role` resource HCL is never shown. The implementer will need to construct the `for_each` that flattens realm+role into composite keys like `"westside-basketball/admin"`. This is a non-trivial local transform (likely using `flatten` + `for`). Recommend adding the resource block to the spec for completeness, or at minimum noting the flattening pattern. 2. **Role import UUIDs are placeholders.** Lines 325-327 show `<role-uuid>` placeholders with a comment to fetch via Admin API. The client imports (lines 319-322) have real UUIDs. For implementation consistency, either fetch all UUIDs upfront and document them, or note that role UUIDs must be fetched at implementation time. Minor inconsistency. 3. **`sops_age_private_key` in actual tfvars but not in variable definition.** The actual `k3s.tfvars` has `sops_age_private_key` and `source_repo`/`source_path` fields that don't exist in the current `variables.tf`. This is pre-existing drift unrelated to this spec, but worth noting -- the implementer will encounter it during `tofu plan`. 4. **Provider version `~> 5.0` should be verified.** The spec proposes `mrparkers/keycloak ~> 5.0`. The Keycloak server is `quay.io/keycloak/keycloak:26.0.7` (confirmed in pal-e-platform main.tf line 1883). Provider v5.x should support Keycloak 26, but this should be confirmed during implementation -- provider/server version compatibility is a common gotcha. 5. **`direct_access_grants_enabled` default.** The variable defaults to `false` (line 140), but `westside-app` sets it to `true` (line 194). The default is correct (direct access grants should be opt-in), but the spec could note that this is the Resource Owner Password Credentials flow and explain why westside-app needs it (likely legacy basketball-api auth pattern). This helps future onboarding decisions. 6. **Missing `access_type` consideration.** The `keycloak_openid_client` resource in mrparkers/keycloak has `access_type` (one of `CONFIDENTIAL`, `PUBLIC`, `BEARER-ONLY`) as an alternative to `public_client` boolean in some provider versions. Verify which attribute the v5.x provider expects. If v5.x uses `access_type` string instead of `public_client` boolean, the variable schema needs adjustment. ### BLOCKERS None. This is a design spec, not executable code. All 5 previously identified must-fix items are resolved. The remaining items are implementation guidance (nits). ### NITS See items 1-6 in the domain review above. The most important is nit #1 (missing `keycloak_role` resource block) -- it is the only resource referenced in import commands that has no corresponding HCL shown. ### SOP COMPLIANCE - [x] Issue #142 exists and is referenced in the spec header - [x] Spec lives in `docs/superpowers/specs/` with date prefix - [x] Architecture decisions have clear rationale - [x] Risk surface documented with mitigations - [x] Testing strategy defined - [x] Success criteria defined - [x] No secrets committed (tfvars is gitignored, confirmed) - [x] Scope is bounded -- "What This Does NOT Manage" section prevents creep ### PROCESS OBSERVATIONS This design spec is high quality. The import-first strategy with a zero-changes gate is the correct approach for bringing existing stateful infrastructure under Terraform management. The risk surface analysis (client secrets, protocol mappers, custom roles, registrationEmailAsUsername) demonstrates deep understanding of the failure modes. The two-variable architecture (`keycloak_realms` + `keycloak_clients`) is the right call. It mirrors the real-world domain model: realms are project boundaries, clients are app configurations, and they have independent lifecycles. This will scale cleanly as new projects are onboarded. The spec is ready for implementation. ### VERDICT: APPROVED
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-270-2026-03-27

Well-structured feature issue with complete template sections, but has critical scope questions that must be resolved before execution.

Key issues:

  • Phase 28 overlap — board items #276/#277/#278 (Keycloak Declarative Onboarding) are all DONE. pal-e-services#23 imported Keycloak realms/clients into Terraform. This ticket may be partially or fully redundant. Need to clarify what remains.
  • Missing traceability labels — no story:X or arch:X labels on board item #270. Recommend story:superuser-deploy + arch:keycloak.
  • State ownership conflict — if pal-e-services already manages Keycloak via Terraform provider, adding a second provider in pal-e-platform creates dual-ownership of the same resources. Must decide which repo owns Keycloak TF state.
  • Decomposition needed — ticket itself notes "may warrant its own plan phase." Multiple realms x multiple clients across potentially 2 repos exceeds single-agent scope.
  • File targets verifiedterraform/main.tf (has Keycloak module), terraform/providers.tf (no Keycloak provider yet), keycloak/themes/ (correctly excluded) all confirmed.
## Scope Review: NEEDS_REFINEMENT Review note: `review-270-2026-03-27` Well-structured feature issue with complete template sections, but has critical scope questions that must be resolved before execution. **Key issues:** - **Phase 28 overlap** — board items #276/#277/#278 (Keycloak Declarative Onboarding) are all DONE. pal-e-services#23 imported Keycloak realms/clients into Terraform. This ticket may be partially or fully redundant. Need to clarify what remains. - **Missing traceability labels** — no `story:X` or `arch:X` labels on board item #270. Recommend `story:superuser-deploy` + `arch:keycloak`. - **State ownership conflict** — if pal-e-services already manages Keycloak via Terraform provider, adding a second provider in pal-e-platform creates dual-ownership of the same resources. Must decide which repo owns Keycloak TF state. - **Decomposition needed** — ticket itself notes "may warrant its own plan phase." Multiple realms x multiple clients across potentially 2 repos exceeds single-agent scope. - **File targets verified** — `terraform/main.tf` (has Keycloak module), `terraform/providers.tf` (no Keycloak provider yet), `keycloak/themes/` (correctly excluded) all confirmed.
Author
Owner

Issue body updated per scope review corrections.

Issue body updated per scope review corrections.
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-142-2026-03-28

This ticket is redundant. Phase 28 (board items #276, #277, #278 — all DONE) already delivered all three acceptance criteria in pal-e-services:

  • login_theme = "westside" is declared in pal-e-services/terraform/k3s.tfvars and applied via keycloak_realm.this
  • westside-basketball and mcd-tracker realms are imported and managed (validated by spike #280)
  • Four OIDC clients managed: westside-app, westside-spa, mcd-tracker-app, mcd-tracker-ios

The ticket's own [SCOPE] section anticipated this: "If Phase 28 covers everything, close this ticket as redundant."

Recommendations:

  • [SCOPE] Close this issue as redundant — no implementation work remains
  • [LABEL] Remove board item #270 from board-pal-e-platform or move to done
## Scope Review: NEEDS_REFINEMENT Review note: `review-142-2026-03-28` **This ticket is redundant.** Phase 28 (board items #276, #277, #278 — all DONE) already delivered all three acceptance criteria in `pal-e-services`: - `login_theme = "westside"` is declared in `pal-e-services/terraform/k3s.tfvars` and applied via `keycloak_realm.this` - `westside-basketball` and `mcd-tracker` realms are imported and managed (validated by spike #280) - Four OIDC clients managed: `westside-app`, `westside-spa`, `mcd-tracker-app`, `mcd-tracker-ios` The ticket's own `[SCOPE]` section anticipated this: *"If Phase 28 covers everything, close this ticket as redundant."* **Recommendations:** - `[SCOPE]` Close this issue as redundant — no implementation work remains - `[LABEL]` Remove board item #270 from board-pal-e-platform or move to done
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/pal-e-platform#142
No description provided.