Slice F of EPIC-3: per-Organization Azure SSO / Okta / generic-OIDC
federation reconciled into the per-Sovereign Keycloak realm.
F1 — catalyst-api keycloak client extension:
products/catalyst/bootstrap/api/internal/keycloak/admin_idp.go
- IdentityProvider + IdentityProviderMapper struct types
- GET/POST/PUT/DELETE on /identity-provider/instances/{alias}
- GET/POST/PUT on /identity-provider/instances/{alias}/mappers
- EnsureIdentityProvider — find-or-create + drift-correct via byte-equal
short-circuit on the catalyst-tracked field set; idempotent re-runs
- EnsureIdentityProviderMapper — same idempotency anchor by mapper Name
- 409 race path re-finds and reconciles drift after the sibling create
- Drift detection ignores unknown server-side Config keys (Keycloak
defaults like pkceEnabled) so we don't fight the admin UI
- 9 unit tests covering clean-create / steady-state-no-write /
drift-PUT / 409-race / not-found / list / mapper variants
F2 — organization-controller Reconcile extension:
core/controllers/organization/internal/controller/
- KeycloakClient interface gains EnsureIdentityProvider /
EnsureIdentityProviderMapper / DeleteIdentityProvider
- LiveKeycloak implementation mirrors the F1 admin_idp.go pattern
(no cross-module Go dep on catalyst-api — out-of-process callers
re-implement the narrow surface, like cert-manager-dynadot-webhook)
- Reconciler resolves clientSecretRef from a K8s Secret in the
controller's namespace (default catalyst-controllers) and passes
the value to Keycloak in-memory only (Inviolable Principle #5)
- Federation alias is deterministic: <provider>-<slug> (e.g.
azure-sso-acme) so two Orgs federating to the same upstream IdP
stay isolated
- Empty-federation path best-effort deletes any stray IdP under any
of the supported provider aliases
- Two new status conditions surfaced on every reconcile so the
access-matrix UI can render the federation column unconditionally:
IdentityProviderConfigured (True/AzureSSOConfigured|OktaConfigured|OIDCConfigured
or False/NoFederation|SecretMissing|KCUnreachable)
IdentityProviderClaimMappersConfigured
- 5 new unit tests: AzureSSO happy-path / Secret-missing requeue /
federation idempotent / cleanup-on-drop / Okta provider
- Existing TestReconcile_HappyPath updated for 3-condition assertion
CRD extension — products/catalyst/chart/crds/organization.yaml:
spec.identity.federationConfig already had {issuer, clientId,
clientSecretRef}; this PR adds {tenantId, authorizationUrl, tokenUrl,
jwksUrl, claimMappers[{src,dest}]}. No oneOf branches, no default
inside arrays — passes structural-schema admission. Sample fixture
(organization-sample-valid.yaml) extended.
RBAC — chart + kubebuilder source:
Adds secrets:get/list/watch to organization-controller ClusterRole
so the reconciler can read the federation client-secret K8s Secret.
Test coverage:
go test -count=1 -race ./internal/keycloak/... OK
go test -count=1 -race ./core/controllers/organization/... OK
go vet ./... clean across both modules
Pre-existing flake confirmed: TestPinIssue_ConcurrentRapidFireRateLimit
(canon §7 — CI-runner timing flake)
Refs: docs/EPICS-1-6-unified-design.md §6.4
docs/INVIOLABLE-PRINCIPLES.md §4 (no hardcoded values), §5 (secrets)
ADR-0001 §2.7 (Org CR is source of truth, KC is reconciliation target)
Co-authored-by: hatiyildiz <hati.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| admin | ||
| cmd/cert-manager-dynadot-webhook | ||
| console | ||
| controllers | ||
| marketplace | ||
| marketplace-api | ||
| pkg/dynadot-client | ||
| pool-domain-manager | ||
| services | ||
| README.md | ||
Catalyst Control Plane (core/)
The user-facing Catalyst control plane modules. Status: Consolidated and deployed on Catalyst-Zero (Contabo k3s) as of Pass 105 (2026-04-28).
Read first:
docs/PROVISIONING-PLAN.md,docs/GLOSSARY.md,docs/ARCHITECTURE.md,docs/IMPLEMENTATION-STATUS.md.
What this is
The four modules that constitute the Catalyst control plane's user-facing surface, plus the Go backend they share. Each is its own Containerfile-built workload, deployed on every Catalyst Sovereign (starting with Catalyst-Zero on Contabo, and on every franchised Sovereign provisioned thereafter).
| Module | Stack | Purpose | Deployed image |
|---|---|---|---|
console/ |
Astro + Svelte | Primary user-facing UI. Form / Advanced / IaC editor depths. The Sovereign-provisioning wizard at /sovereign (Phase 3) lives here. |
ghcr.io/openova-io/openova/console:<sha> |
admin/ |
Astro + Svelte | Sovereign-admin operations UI. Includes the canonical voucher / billing / catalog / orders / tenants admin surface that sovereign-admin uses to issue vouchers to franchised tenants. | ghcr.io/openova-io/openova/admin:<sha> |
marketplace/ |
Astro + Svelte | Public-facing Blueprint card grid (the "App Store"). 5-step Plan → Apps → Addons → Checkout → Review flow. |
ghcr.io/openova-io/openova/marketplace:<sha> |
marketplace-api/ |
Go | Backend API for marketplace and console. Handlers (handlers/), provisioner (provisioner/), store (store/). Phase 4 extends this with full Hetzner provisioning. |
ghcr.io/openova-io/openova/marketplace-api:<sha> |
The Helm chart that deploys all four (plus catalyst-ui, catalyst-api, and the legacy SME backend services) lives at products/catalyst/chart/.
CI / Build
Each module has a corresponding GitHub Actions workflow:
.github/workflows/console-build.yaml.github/workflows/admin-build.yaml.github/workflows/marketplace-build.yaml.github/workflows/marketplace-api-build.yaml.github/workflows/catalyst-build.yaml— coversproducts/catalyst/bootstrap/{ui,api}/(the React SPA + Go bootstrap API)
Each workflow watches its module path, builds the Containerfile, pushes to GHCR with a SHA tag, and pins the SHA into the corresponding manifest in products/catalyst/chart/templates/ (so Flux on Catalyst-Zero picks up the new image on the next reconciliation).
Migration history
- Pass 105 (2026-04-28):
console/,admin/,marketplace/consolidated fromopenova-private/apps/{console,admin,marketplace}/into this directory.marketplace-api/consolidated fromopenova-private/website/marketplace-api/. Six CI workflows migrated to.github/workflows/of the public repo. Catalyst-Zero K8s manifests migrated fromopenova-private/clusters/contabo-mkt/apps/{catalyst,sme/services,marketplace-api}/intoproducts/catalyst/chart/templates/. Image references updated fromghcr.io/openova-io/openova-private/sme-{admin,console,marketplace}toghcr.io/openova-io/openova/{admin,console,marketplace}. The 8 legacy SME backend services (auth,billing,catalog,domain,gateway,notification,provisioning,tenant) keep theiropenova-private/sme-*image refs until their source code migrates in a follow-up phase.
Part of OpenOva