Tenant onboarding — overview
End-to-end checklist a platform operator follows to onboard a new (division, team, app) — Vault role, AppProject, ESO Secret, pull secret, AppSet entry, RHACS exception flow.
This page is the single checklist a platform operator follows to take a new (division, team, app) from “nothing” to “Argo syncs a working Deployment that pulls images, reads Vault secrets, and serves a Route.” It is concrete enough that a new operator can follow it end to end without back-channel guidance.
The five “platform-side” pages below each expand one step in detail.
Whiteboard — onboarding step order
The numbering matters. Steps 1-4 are platform setup that must complete before any tenant manifest is reconciled. Step 5 is the per-tenant ESO bridge. Step 6 is the cluster-wide pull secret materialisation. Step 7 is one-time per division (skipped for new apps in an existing monorepo). Steps 8-10 are the tenant’s actual app delivery.
Checklist
The full sequence for a new division + new team + new app is:
1. Pick a division name
Lowercase, no underscores. Examples: platform, payments, risk, retail.
This becomes part of:
- the namespace prefix (
apps-<division>-*) - the Vault role (
apps-<cluster>-<division>) - the Vault policy (
apps-<division>-read) - the Vault path subtree (
secret/apps/<division>/...)
Add the entry to platform-gitops/divisions.yaml (one-line addition).
2. Run the Vault onboarding script
/home/ze/ops-workspace/scripts/vault-apps-onboard.sh <division> <cluster>
# e.g.
/home/ze/ops-workspace/scripts/vault-apps-onboard.sh platform spoke-dc-v6
This creates (or rewrites — idempotent):
- Vault ACL policy
apps-<division>-read - Vault role
apps-<cluster>-<division>underauth/kubernetes-<cluster>/
Full details: 02 — Vault path and bound role.
3. Create the AppProject + team RoleBinding
One MR to platform-gitops:
clusters/<cluster>/appprojects/team-<team>.yaml— Argo AppProject scoped to the team’s app monorepo andapps-<division>-*destinations.clusters/<cluster>/tenants/_template/rolebinding-edit.yaml— copied intoclusters/<cluster>/tenants/apps-<division>-<team>/rolebinding-team-developers.yamlwith placeholders substituted.
Full details: 03 — Project + RoleBinding template.
4. Create the tenant namespace
Same MR as step 3, adds:
clusters/<cluster>/tenants/apps-<division>-<team>-<env>/namespace.yaml(one per env: dev, stg, prd).- Labels:
apps.platform/division=<division>,apps.platform/team=<team>,apps.platform/env=<env>,apps.platform/tenant=true(this last label is what triggers the cluster-wide pull-secret fan-out). - PSA labels:
pod-security.kubernetes.io/enforce=restricted(PCI profile, ADR 0020). - The
argocd.argoproj.io/managed-by: openshift-gitopsannotation so Argo recognises the namespace.
5. Create the ResourceQuota + LimitRange
Same MR. See 06 — ResourceQuota and LimitRange for the canonical shapes.
Default budgets:
dev: 4 CPU / 8 Gi memory / 10 pods / 0 PVCsstg: 8 CPU / 16 Gi memory / 20 pods / 5 PVCsprd: 16 CPU / 32 Gi memory / 40 pods / 10 PVCs
Tenants can request increases via a GitLab MR to the namespace’s quota YAML, reviewed by the platform team.
6. Materialise the per-tenant SecretStore
In the tenant’s app monorepo, create apps/<team>/<app>/base/secretstore-vault-apps.yaml from the template at platform-gitops/clusters/<cluster>/tenants/_template/secretstore-vault-apps.yaml, substituting <DIVISION>, <APP>, and <CLUSTER> placeholders.
This is the only namespaced SecretStore for the tenant — not a ClusterSecretStore. It points the namespace’s app-eso ServiceAccount at the Vault role created in step 2.
Full details: 04 — ESO Secret and pullSecret.
7. Verify the cluster-wide app-registry pull Secret
The ClusterExternalSecret/app-registry-pull (shipped from platform-gitops/clusters/<cluster>/secrets/app-registry-pull/) watches namespaces labelled apps.platform/tenant=true and materialises a Secret/app-registry-pull of type kubernetes.io/dockerconfigjson inside each.
Confirm:
oc get clusterexternalsecret app-registry-pull -o jsonpath='{.status}'
oc -n apps-<division>-<team>-<env> get secret app-registry-pull
Attach it to the default ServiceAccount:
oc -n apps-<division>-<team>-<env> patch sa default \
-p '{"imagePullSecrets":[{"name":"app-registry-pull"}]}'
For workloads using a non-default SA, attach to that SA instead.
8. Add an AppSet entry (only if a new app monorepo)
If this division’s apps live in an app monorepo that the tenant-apps ApplicationSet is not already watching, one platform MR adds the new repoURL to the AppSet’s git generator. See 02 — ApplicationSet generator pattern.
If the new app is in an already-watched monorepo, this step is skipped — the AppSet auto-discovers the new overlay directory on its next refresh (default 3 min).
9. RHACS policy scope check
The five built-in deploy-time RHACS policies (Latest tag, No CPU request or memory limit specified, Privileged Container, CAP_SYS_ADMIN capability added, Required Image Label) auto-scope to apps-.*, so a new tenant namespace is covered without action. Verify scope is correct:
ROX_PW=$(oc -n stackrox get secret central-htpasswd -o jsonpath='{.data.password}' | base64 -d)
curl -fsSk -u "admin:$ROX_PW" \
"https://central-stackrox.apps.hub-dc-v6.sub.comptech-lab.com/v1/policies" \
| jq '.policies[] | select(.name == "Latest tag") | .scope'
If the tenant needs an exception, follow 05 — RHACS tenant exception process. The default is no exception — fix the violation in the manifest first.
10. Tenant onboards the app
Tenant opens an MR against their app monorepo that adds apps/<team>/<newapp>/{base,overlays/{dev,stg,prd}}/ following the overlay contract.
11. Smoke validation
Once tenant merges:
# AppSet should have created Applications for dev/stg/prd.
oc -n openshift-gitops get applications | grep '<team>-<newapp>'
# Argo should be Synced/Healthy on dev (no CI digest yet, so on initial onboard the
# placeholder digest causes ImagePullBackOff — that's expected. CI's first build patches it.)
oc -n openshift-gitops get app team-<team>-<newapp>-dev -o jsonpath='{.status.sync.status}{" "}{.status.health.status}{"\n"}'
# Tenant runs a manual build through Path A (Jenkins) or Path B (Tekton). After CI's
# first successful build merges the digest patch MR, Argo re-syncs and the Pod runs.
# Final smoke:
oc -n apps-<division>-<team>-dev get pods
oc -n apps-<division>-<team>-dev get route <newapp> -o jsonpath='{.status.ingress[0].host}{"\n"}'
curl -fsS https://$ROUTE_HOST/health
What the sub-pages cover
| Page | Step | Purpose |
|---|---|---|
| 02 — Vault path and bound role | 1, 2 | The per-division Vault role, policy, and path-tree convention. |
| 03 — Project + RoleBinding template | 3 | The Argo AppProject + the team’s RoleBinding to the namespace. |
| 04 — ESO Secret and pullSecret | 5, 6, 7 | The per-tenant SecretStore + the cluster-wide app-registry pull Secret. |
| 05 — RHACS tenant exception process | 9 | The exception-MR pattern when a tenant legitimately needs to bypass a RHACS policy. |
| 06 — ResourceQuota and LimitRange | 4 | The default per-env quota / limit shapes and how to request increases. |
| 07 — Tenant scaffolding template | 3, 4 | Canonical contents of _template/ plus the four active tenants and the AppProject convention. |
| 08 — Cluster pull-secret fan-out | 6 | The dedicated ClusterExternalSecret design — fan-out, Vault path, label selector. |
| 09 — Per-tenant Quay robot token | (CI) | The Path B push credential: per-tenant Vault path, ESO into openshift-pipelines, shared Tekton Task. |
Pre-onboarding requirements
Before step 1, these MUST be in place (they are platform install-time work, not per-tenant):
- The
vault-apps-onboard.shscript is on the operator’s machine with reachable Vault. - The platform
ClusterExternalSecret/app-registry-pullis shipped on the spoke and its Vault dependency (secret/ocp/<cluster>/registries/app-registry-pull) is populated. See 04 — ESO Secret and pullSecret. - The
tenant-appsApplicationSetand itsGitOpsClusterPlacement wiring are on the hub. See 02 — ApplicationSet generator pattern. - The five RHACS built-in policies are scoped to
apps-.*and set toSCALE_TO_ZERO_ENFORCEMENTon the DEPLOY lifecycle. Runscripts/rhacs-enable-app-policies.shonce.
What is NOT in scope of “onboarding”
A few things commonly conflated with tenant onboarding but governed elsewhere:
- The build pipeline itself. Jenkins job (Path A) or Tekton Pipeline (Path B) is the tenant’s responsibility; the platform provides templates only. See 03 — Build paths.
- The cluster’s identity provider integration. Tenant identity (the GitLab group the RoleBinding references) is platform-install work; per-tenant entries are just RoleBindings to existing groups.
- App configuration. Anything inside the tenant’s
base/andoverlays/is the tenant’s content. Platform onboarding only creates the frame — namespaces, quotas, ESO bridge, AppProject — that the tenant fills.
References
opp-full-plat/connection-details/vault-app-secrets.md(issue #174, DEV-OCP-0.4)opp-full-plat/connection-details/app-registry-pullsecret.md(issue #172, DEV-OCP-0.2)opp-full-plat/connection-details/rhacs-app-policy.md(issue #198, DEV-OCP-4.3)opp-full-plat/connection-details/vap-tenant-exclusions.md(issue #199, DEV-OCP-4.4)- Issue #173 (DEV-OCP-0.3) — tenant Project + Role + RoleBinding template.