PR #911 wired the SME tenant orchestrator to emit
realmConfig.tenant.enabled=true on the per-tenant bp-keycloak
HelmRelease — but the chart had no template that consumed those values,
so the WordPress / OpenClaw / Stalwart OIDC integrations had no client
registered in the tenant realm and SSO failed end-to-end.
This change adds the chart-side template the orchestrator was already
emitting for. When realmConfig.tenant.enabled=true:
* configmap-sovereign-realm.yaml SKIPS (mutual-exclusion guard added
on the existing template) so only one realm CM is rendered.
* NEW templates/configmap-tenant-realm.yaml renders a realm import
ConfigMap (same name `<release>-sovereign-realm-config` so the
upstream keycloak-config-cli existingConfigmap reference still
resolves) carrying the tenant realm + 3 OIDC clients:
- wordpress (confidential, auth-code; redirect URIs cover the
openid-connect-generic plugin's admin-ajax.php
callback + /wp-login.php fallback)
- openclaw (confidential, auth-code; redirect URI /oauth/callback
per #915 spec)
- stalwart (confidential, serviceAccountsEnabled=true so the
directory.keycloak type=oidc backend can use
client_credentials to introspect IMAP/SMTP tokens;
standardFlowEnabled=true for webmail UI auth-code)
* NEW per-app Secrets emitted in the same template scope as the realm
ConfigMap so the realm JSON's `secret` field and the K8s Secret
bytes never drift:
- wordpress-oidc-client-secret
- openclaw-oidc-client-secret
- stalwart-oidc-client-secret (carries BOTH client-secret AND
OIDC_CLIENT_SECRET keys for the
two consumer paths)
* Each per-app secret persists across helm upgrade via
lookup-or-generate (mirrors marketplace-api/secret.yaml pattern from
issue #887 and the existing catalyst-api-server secret in
configmap-sovereign-realm.yaml). helm.sh/resource-policy: keep so
bytes outlive uninstall.
* Fail-closed validation when realmConfig.tenant.enabled=true and
any of realmName / parentDomain / subdomain is unset (Inviolable
Principle #4).
NEW tests/tenant-realm-oidc-clients.sh covers 6 cases:
1. Sovereign-mode default render unchanged (kubectl + catalyst-ui +
catalyst-api-server clients present, no tenant artefacts leak).
2. Tenant-mode render produces exactly ONE realm CM under the
expected name + zero leaked Sovereign-only resources.
3. Tenant realm JSON parses + 3 OIDC clients present with the
redirect-URI / publicClient / serviceAccountsEnabled shape per
#915 spec; Secret bytes match realm JSON's `secret` fields.
4. Fail-closed validation when tenant fields missing.
5. keycloak-config-cli post-install Job projects the realm CM by
SAME name in BOTH modes.
6. Operator-supplied per-app clientSecret overrides the
lookup-or-generate path.
Existing tests/observability-toggle.sh + tests/oidc-kubectl-client.sh
still pass.
Sovereign-mode unchanged. The chart now consumes the values the
orchestrator (PR #911) was already emitting; no orchestrator change
needed.
Closes#915 (C1 sub-task) and unblocks #899 (per-tenant Keycloak
realm-config materialisation).
Co-authored-by: hatiyildiz <hatiyildiz@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>