Your first directory user signs into the OpenShift console, lands on a working dashboard, and the migration feels finished. Then you run oc get groups and the cluster answers with No resources found.
That gap between authentication and authorization is where most Tanzu Kubernetes Grid Integrated (TKGI) teams lose a week. TKGI shipped User Account and Authentication (UAA) as its identity broker, and UAA handed group claims straight back in the token. OpenShift Container Platform (OCP) does not work that way. An identity provider proves who somebody is. Group membership arrives through a separate sync job you schedule yourself, and until that job runs, every migrated developer is an authenticated user holding exactly zero permissions.
Part 14 gave every team a Project with a quota around it. This part decides who can open that door, and where the container images behind it come from. Both answers need to land before wave one moves, because a workload nobody can pull and a developer who cannot log in look identical from a stakeholder chair.
1. Point OpenShift OAuth straight at your directory, never at UAA, because UAA gets decommissioned along with TKGI.
2. Authentication and group membership are two separate jobs. A login creates a User object, and only a sync creates a Group object.
3. Run group sync with a whitelist every single time, otherwise you import the entire directory into your cluster.
4. Image registry on vSphere starts Removed, and needs a Recreate rollout, one replica and a ReadWriteOnce claim before it serves anything.
5. ImageDigestMirrorSet ignores tag based pulls, and almost every manifest coming off TKGI pulls by tag. Headline command: oc adm groups sync –sync-config=sync-config.yaml –whitelist=ocp-groups.txt –confirm
Preflight and Versions Tested
Every command below ran against one combination, and drift on the directory side is the usual reason a copied snippet fails. Check yours before you start.
Three preflight facts decide whether this runbook is a twenty minute job or a two day one. You need a read only service account bind DN that can see both the user organizational unit and the group organizational unit, and it should not be your own account. You need the PEM encoded certificate authority that signed the directory server certificate, root and intermediates concatenated into one file. And you need to know exactly which attribute your directory uses for a short login name, because sAMAccountName and uid are not interchangeable, and choosing wrong hands every user a second identity three weeks later when somebody corrects it.
Wiring OAuth to Your Existing Directory
Step 1. Store the bind password and the certificate authority. OpenShift keeps identity provider material in the openshift-config namespace, separate from the OAuth resource that references it. Two objects, created once, reused by every identity provider you add later.
Step 2. Write the OAuth custom resource and apply it. A custom resource (CR) is a cluster object whose shape comes from an installed API rather than core Kubernetes. Only one OAuth object exists per cluster and it is always named cluster, so you are editing a singleton, not creating a new record. Note the id field set to dn. Distinguished names change rarely, short login names change often, and identity records in OpenShift are keyed on whatever you put in id.
Spend a minute on mappingMethod before you apply anything, because changing it afterwards is messy. Four values exist. Setting claim, which is the default, links an identity to a user carrying the same preferred user name and fails outright when that name already belongs to a different identity. Setting lookup uses only mappings you created yourself and refuses to create anything new, which suits regulated estates where user provisioning is a separate approval step. Setting add attaches the identity to an existing user of that name, joining accounts rather than colliding. Setting generate quietly invents a fresh name such as asharma2 whenever a collision happens, and that is how one person ends up with two accounts and a confused support ticket six weeks later. Use claim, and let a collision fail loudly during your pilot rather than silently during wave three.
That closing message is the whole problem in one sentence. Authentication worked. Authorization has not started.
Group Sync, Because Login Alone Grants Nothing
OpenShift creates a User object and an Identity object the moment somebody authenticates. It creates no Group object, ever, from a login. Groups come from oc adm groups sync, a command you run on a schedule against a config file that describes your directory schema. Until it runs, every RoleBinding you wrote against a group name matches nobody.
flowchart TD
A[User opens console] --> B[oauth openshift pod]
B --> C{LDAP bind and search}
C -- fails --> D[Login rejected, check ca.crt key and bindDN]
C -- succeeds --> E[Identity and User objects created]
E --> F{Group object exists from sync}
F -- no --> G[Authenticated with zero roles, empty project list]
F -- yes --> H[RoleBinding on Project matches the group]
H --> I[oc and console actions authorized]Step 3. Write a sync config and a whitelist, then run the sync. Pick the schema section that matches your directory. Plain RFC 2307 stores membership on the group entry. Active Directory stores it on the user entry. Augmented Active Directory has first class group entries and user side membership attributes, which is what most enterprise AD deployments actually look like once somebody has added group naming. Group sync runs as a dry run by default, and the –confirm flag is the only thing that writes.
Run this on a CronJob, not from your laptop. A directory change that removes somebody from payments-ops does nothing to their OpenShift access until the next sync lands, and a sixty minute schedule is a reasonable starting point for a migration window. oc adm prune groups is the companion command that deletes Group objects whose directory record has disappeared.
Here is what skipping the whitelist looks like. On the staging cluster I ran the sync against the directory root, because writing out a whitelist file felt like paperwork on a Friday. Directory had 1,412 group entries inside that base DN. All 1,412 landed in OpenShift as Group objects, the console group picker turned into a scroll of distribution lists and printer queues, and two imported names collided with Projects the platform team had already created that morning. Cleanup took 40 minutes of scripted deletes plus one awkward message in the migration channel. Worth knowing: a second, correct run does not fix it. Groups outside the whitelist are not considered at all, so the junk simply stays until you remove it by hand.
Role Bindings and Self Service Project Control
Step 4. Bind roles to groups, never to users. Role Based Access Control (RBAC) in OpenShift attaches a role to a subject inside a scope. Bind to a group and the directory stays the source of truth. Bind to a user and you have built a second, silent access control system that nobody remembers to update when somebody moves teams. TKGI plans mapped to clusters; OpenShift roles map to Projects, and the three that cover most of a migrated estate are view, edit and admin.
Self service project creation is on by default, granted to every authenticated user through the self-provisioners cluster role binding. On a TKGI estate this is the wrong default. Your teams are used to asking for a cluster and receiving one, and Part 14 built a project request template with quotas already inside it. Leave self service on and developers route around that template within a week.
Choosing between admin and edit matters more than it looks on a role list. Granting edit lets somebody deploy, scale, read logs and manage config inside a Project, which covers nearly every developer task migrated off TKGI. Granting admin adds the power to hand access to other people, change the Project itself and rewrite its role bindings. On TKGI most teams held cluster level credentials, because a plan gave them a whole cluster of their own, so their instinct is to ask for admin everywhere and treat anything less as a demotion. Give edit to developer groups, admin to whoever genuinely owns the Project, and keep cluster-admin for a group small enough that you can name every member from memory. A migration is the one moment when nobody expects old permissions to carry across untouched, so spend that goodwill here.
Registry Choice, Internal Beside Harbor
On vSphere the internal image registry ships switched off. vSphere installs do not provision shareable object storage, so the Image Registry Operator sets managementState to Removed and waits for you to give it somewhere to write. That is a decision point, not a chore, and the three real options behave very differently during a migration.
| Option | What it costs you | Pick it when |
|---|---|---|
| Internal registry only, Harbor retired at cutover | Re-push or rebuild every image before wave one, plus a registry claim you now own, monitor and prune | Harbor is TKGI era infrastructure you also want to retire, and every image is rebuildable from a pipeline you control |
| Harbor only, internal registry left Removed | A pull secret in every Project, no ImageStreams, no source to image builds, and a hard dependency on a box outside the cluster | Harbor is a shared corporate registry serving platforms beyond TKGI and has its own roadmap |
| Internal registry with Harbor as upstream mirror | Two systems to watch for the length of the migration, plus mirror sets to maintain and retire | You are migrating in waves and want a rollback that does not involve re-pushing images at 2am. This is the pick. |
Step 5. Turn the registry on, give it storage, and point pulls where you want them. Block storage on vSphere means ReadWriteOnce, ReadWriteOnce means one replica, and one replica means the Recreate rollout strategy. Apply that patch before you attach the claim, not after.
Applying an ImageDigestMirrorSet on its own is the single most common registry mistake I see on migrations, and it fails silently. Digest mirroring only matches image references pinned to a sha256 value. Manifests coming off a TKGI estate overwhelmingly pull by tag, because that is what a decade of Helm charts and CI templates produce. Pods keep resolving to Harbor, everybody assumes the mirror is working because nothing errored, and the dependency you were trying to remove is still load bearing on cutover day. Apply the tag mirror set too, then confirm on a node rather than in the API.
Registry sizing deserves a second look as well. Red Hat documentation uses 100Gi in its vSphere example, and a great many clusters get built with exactly that number copied across. During a migration you are not running a steady state registry. You are pushing every image from every wave, keeping older tags because rollback needs them, and accumulating build layers nobody prunes yet. Here is what one claim did across eight weeks of waves on the reference estate.
Start at 250Gi, and schedule the image pruner before wave three rather than after it runs out. Growth flattens once old tags start aging out, but that flattening does not happen on its own.
Verification, Rollback and Failure Remediation
Five checks tell you this part landed. Run them as a block, and treat any one of them coming back wrong as a stop signal for the wave.
Rollback is straightforward here, which is unusual and worth using. Identity, RBAC and registry changes are all declarative singletons, so backing out means restoring a previous object rather than rebuilding anything. Keep a copy of the OAuth resource before you touch it, keep the kubeadmin secret until the following change window, and remember that removing a mirror set rolls every node again, so budget the same twenty to thirty minutes on the way out that you budgeted on the way in.
This table is the artifact worth keeping from this Part. Print it, or paste it into the runbook your on call rotation actually opens at 3am.
| Symptom | Error you will actually see | Fix |
|---|---|---|
| Every login rejected | LDAP Result Code 200 Network Error: x509 certificate signed by unknown authority | ConfigMap key must be exactly ca.crt. Recreate with –from-file=ca.crt=/path/to/ca.pem |
| Login works, console shows no projects | No error at all, just an empty project list | No Group objects exist yet. Run group sync, then bind roles to the groups |
| Sync prints group names, oc get groups stays empty | No error, output looks identical to a real run | Sync is dry run by default. Add –confirm |
| self-provisioner comes back after removal | Binding silently reappears within minutes | Set rbac.authorization.kubernetes.io/autoupdate to false BEFORE removing it |
| Image registry operator Degraded | Degraded: Storage is not configured | vSphere leaves managementState Removed. Patch to Managed and attach a claim |
| Second registry pod Pending forever | Multi-Attach error for volume, already exclusively attached to one node | ReadWriteOnce allows one replica. Patch rolloutStrategy to Recreate and replicas to 1 |
| Pods still pull from Harbor after mirroring | No error, pulls simply resolve to the source | ImageDigestMirrorSet matches digests only. Add an ImageTagMirrorSet |
| Duplicate users after a directory tidy up | Two Identity objects mapping to one person, one with no roles | Keep id set to dn so identity survives a login name change |
A clean result looks like this. Authentication operator available and not progressing. Three Group objects with the members you expect and nothing else. A developer who can create a pod in their Project and cannot create a project. An image-registry cluster operator reporting True, False, False, backed by a claim sized for a migration rather than a demo. And a mirror stanza visible in the container runtime config on a worker node, not just an accepted object in the API server.
Directory First, Registry Second, Kubeadmin Last
My recommendation for this stage is a strict order, and it survives contact with real estates. Wire OAuth to the directory that UAA was already proxying, and skip any idea of keeping UAA in the path. Sync groups with a whitelist on a CronJob before you write a single RoleBinding. Bind roles to groups only. Turn off self service project creation so the quota template from Part 14 keeps meaning something. Stand up the internal registry with Harbor mirrored behind both an ImageDigestMirrorSet and an ImageTagMirrorSet, sized at 250Gi. Then, in the next change window and not this one, delete kubeadmin.
Teams that reach this point sometimes reconsider the whole landing place, and that is a fair moment to do it. If your organisation is heading toward VMware Cloud Foundation 9 rather than Red Hat, the equivalent work is covered in the TKGI to VKS Series instead. Everyone else carries on. Next up is network policy and microsegmentation, translating NSX-T distributed firewall rules into OVN-Kubernetes NetworkPolicy, which is where the security team finally gets involved.
One thing to do on Monday: run oc get groups on whatever OpenShift cluster you already have. If it comes back empty while people are logging in successfully, you have found the same gap, and you now know it takes a sync config and a whitelist to close it. Related reading in this series: Part 10 on identity and multi-tenancy design, and Part 7 on Security Context Constraints, because admission is the next thing that will reject a workload after RBAC lets it through.
References
2. Red Hat, Syncing LDAP groups, Authentication and authorization, OCP 4.18
3. Red Hat, Setting up and configuring the registry, Registry, OCP 4.18
4. Red Hat, ImageTagMirrorSet config.openshift.io/v1, Config APIs, OCP 4.18


DrJha