This overlay is for an existing -mc-dev checkout. It assumes MC is already deployed with Keycloak, Envoy Gateway, cert-manager, and Flux controllers running.
This version is intentionally manual and staged. The scripts are helpers only. They do not perform the full onboarding flow for you. The goal is to let a developer render the contract, inspect the output, apply one step at a time, and validate where each change happens.
Identity contract YAML
→ render Keycloak Config CLI YAML
→ inspect generated config
→ apply ConfigMap + Job
→ Keycloak creates/updates realms and clients
→ platform publishes OIDC Secret
→ tenant/vcluster app consumes the Secret and CA bundle
→ Envoy exposes the app
- vcluster chart
0.20.0works with minimal values. vcluster connect ... --printis the supported CLI flow.- Keycloak admin secret uses
KEYCLOAK_ADMINandKEYCLOAK_ADMIN_PASSWORD. scripts/keycloak/prepare-admin-secret.shnormalizes those keys tousernameandpasswordfor the Keycloak Config CLI Job.- Keycloak internal URL is
http://keycloak-keycloakx-http.keycloak.svc.cluster.local:80/auth. - Default shared-service model is token validation first. Tenant users are not stored, synced, or linked in a shared realm.
Tenant landing zone first
vcluster + namespace + trust preparation
Identity creation second
realm + app client + app OIDC Secret
App onboarding third
app deployment + service + route + validation
Apps should not mutate Keycloak. Apps only consume identity outputs.
Start by looking at the contract examples. These are the intent files.
find gitops/platform/identity/contracts/examples -type f -name '*.yaml' -print
cat gitops/platform/identity/contracts/examples/tenants/opp/realm-identity.yaml
cat gitops/platform/identity/contracts/examples/tenants/opp/app-smoke-app.yaml
cat gitops/platform/identity/contracts/examples/platform/shared-client--shell.yaml
cat gitops/platform/identity/contracts/examples/platform/redirect-workspaces.yamlThe important scenarios are:
RealmIdentity creates an identity boundary / tenant realm
ApplicationIdentity creates an app client and output Secret
ApplicationIdentity shared creates shared platform clients like -app-envoy
ApplicationRedirectRegistration adds redirect URIs to an existing shared client
SharedServiceAccess documents token-validation-first shared service access
python3 scripts/identity/identityctl.py demo --repo-root .Expected output:
OK: validated 6 contract file(s)
Rendered demo output: build/identity/demo
Inspect everything before applying:
ls -l build/identity/demo
cat build/identity/demo/platform.yaml
cat build/identity/demo/tenant-opp.yaml
cat build/identity/demo/configmap.yaml
cat build/identity/demo/job.yaml
cat build/identity/demo/output-secrets.yamlThis is the most important step. It proves the contract can be translated into Keycloak Config CLI input and Kubernetes manifests without touching the cluster yet.
Create the namespace expected by the demo Job:
kubectl create ns platform-identity --dry-run=client -o yaml | kubectl apply -f -Normalize the Keycloak admin secret for the Job:
bash scripts/keycloak/prepare-admin-secret.sh
kubectl -n platform-identity get secret keycloak-admin-secret -o json | jq '.data'Expected keys:
{
"password": "...",
"username": "..."
}Validate that Keycloak is reachable from the Job namespace:
kubectl -n platform-identity run curl-test --rm -it --image=curlimages/curl --restart=Never -- \
curl -v http://keycloak-keycloakx-http.keycloak.svc.cluster.local:80/authApply only the rendered ConfigMap first:
kubectl apply -f build/identity/demo/configmap.yaml
kubectl -n platform-identity get configmap keycloak-rendered-configApply the output Secrets next. For the demo, these are host-cluster Secrets used as app outputs:
kubectl create ns opp-apps --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f build/identity/demo/output-secrets.yaml
kubectl -n opp-apps get secret opp-smoke-app-oidcRun the Keycloak Config CLI Job last:
kubectl -n platform-identity delete job keycloak-config-cli --ignore-not-found
kubectl apply -f build/identity/demo/job.yaml
kubectl -n platform-identity get pods -wCheck the Job logs:
kubectl -n platform-identity logs job/keycloak-config-cliExpected successful pattern:
Importing file 'file:/config/platform.yaml'
Importing file 'file:/config/tenant-opp.yaml'
keycloak-config-cli ran in ...
Port-forward Keycloak:
kubectl -n keycloak port-forward svc/keycloak-keycloakx-http 8080:80In another terminal:
curl -s http://localhost:8080/auth/realms/platform/.well-known/openid-configuration | jq .issuer
curl -s http://localhost:8080/auth/realms/tenant-opp/.well-known/openid-configuration | jq .issuerIf the realms exist, the contract successfully changed Keycloak state.
Apply the vcluster source and tenant namespace:
kubectl apply -f gitops/platform/vcluster/sources/loft-helmrepository.yaml
kubectl apply -f gitops/tenants/opp/namespace.yaml
kubectl apply -f gitops/tenants/opp/vcluster/values.yaml
kubectl apply -f gitops/tenants/opp/vcluster/helmrelease.yamlWatch:
kubectl -n tenant-opp-system get helmrelease tenant-opp -w
kubectl -n tenant-opp-system get pods,svcExpected host resources:
pod/tenant-opp-0
service/tenant-opp
service/tenant-opp-headless
Connect to the vcluster:
vcluster connect tenant-opp -n tenant-opp-system --print > ./tenant-opp.kubeconfig
kubectl --kubeconfig ./tenant-opp.kubeconfig get nsCreate the app namespace inside the vcluster:
kubectl --kubeconfig ./tenant-opp.kubeconfig apply -f gitops/tenants/opp/app/namespace.yaml
kubectl --kubeconfig ./tenant-opp.kubeconfig get nsFor MVP only, copy the generated OIDC Secret from host cluster into the vcluster:
kubectl -n opp-apps get secret opp-smoke-app-oidc -o yaml \
| sed '/resourceVersion:/d;/uid:/d;/creationTimestamp:/d;/managedFields:/,/^$/d' \
| kubectl --kubeconfig ./tenant-opp.kubeconfig apply -f -
kubectl --kubeconfig ./tenant-opp.kubeconfig -n opp-apps get secret opp-smoke-app-oidcLater this manual copy should be replaced with ExternalSecrets, OpenBao, or supported vcluster sync.
Later this should be replaced with cert-manager trust-manager bundle distribution.
The app onboarding contract is the ApplicationIdentity file. For the demo:
cat gitops/platform/identity/contracts/examples/tenants/opp/app-smoke-app.yamlIt says:
Create an app identity for opp-smoke-app in the tenant-opp realm.
Publish the OIDC output as opp-apps/opp-smoke-app-oidc.
After the identity output and CA bundle exist in the vcluster, deploy the app:
kubectl --kubeconfig ./tenant-opp.kubeconfig apply -f gitops/tenants/opp/app/deployment.yaml
kubectl --kubeconfig ./tenant-opp.kubeconfig apply -f gitops/tenants/opp/app/service.yaml
kubectl --kubeconfig ./tenant-opp.kubeconfig -n opp-apps get pods,svc,secret,configmapThis keeps the dependency correct:
identity + trust first
app deployment second
Check host-side service names generated by vcluster:
kubectl -n tenant-opp-system get svc | grep opp-demo-app || true
bash scripts/vcluster/tenantctl.sh host-services oppIf the host-side service name differs from the sample, update:
vi gitops/tenants/opp/gateway/httproute.yamlApply the route:
kubectl apply -f gitops/tenants/opp/gateway/httproute.yaml
kubectl -n tenant-opp-system get httproute opp-demo-app
Expected:
hello from opp vcluster app
These helpers only print or inspect; they do not onboard everything.
bash scripts/vcluster/tenantctl.sh plan opp
bash scripts/vcluster/tenantctl.sh status opp
bash scripts/vcluster/tenantctl.sh connect opp
bash scripts/vcluster/tenantctl.sh host-services oppbash scripts/cleanup/cleanup-mvp.sh oppThis removes MVP Kubernetes resources, vcluster namespace, generated files, and local kubeconfig. It does not delete Keycloak realms by default because destructive identity deletion should be explicit.
Flux should eventually own steady-state objects:
tenant namespace
vcluster HelmRelease
app manifests
HTTPRoute
CA bundle resources
validation jobs
The Keycloak Config CLI Job can remain a controlled one-time execution until the onboarding API writes contracts to Git and Flux reconciles them.