Skip to main content
VMware

Automating OIDC Identity for a VCF Lab

I run a nested VMware Cloud Foundation 9.1 lab — a private cloud’s worth of components and various applications — and I wanted one unglamorous thing from it: a single login.

OpenID Connect makes that possible and Keycloak makes it free. The protocol is the easy, standard part — identical whether you’re federating an application or a hypervisor. This post is how the pieces fit together — and where the friction actually lives.

Lab Environment

The lab runs as a vApp on vCloud Director. The vApp runs a private address space behind a single Linux host that acts as gateway for my operator laptop. The gateway runs tinyproxy and SOCKS proxy for connectivity and other supporting lab services — DNS, DHCP, certificate authority, and Keycloak in a container. For a lab, that’s exactly the right weight: something I can stand up, federate against, tear down, and rebuild without ceremony.

OpenID Connect (OIDC)

It is a thin identity layer on top of OAuth 2.0 — token-based and stateless, which suits distributed architectures: single-page apps, mobile clients, microservices, and APIs talking to each other rather than a single server with a session table. OIDC issues self-contained JSON Web Tokens (JWTs) that each service validates independently, without calling back to a central session store, so it scales cleanly across all of them.

OIDC defines a small, consistent vocabulary that every provider shares:

  • Issuer — the provider itself, identified by a URL, publishing a discovery document at /.well-known/openid-configuration that lists its endpoints and signing keys.
  • Client — an application registered with the issuer, identified by a client_id, a secret, and its permitted redirect URIs.
  • Scopes — the access an application requests, such as openid, profile, or email.
  • Claims — statements about the user carried inside a token, such as sub (a stable identifier), email, or name.
  • Tokens — the ID token asserts who the user is, the access token authorises access to a resource, and the optional refresh token obtains new tokens without re-authenticating.
  • Endpoints — the provider’s HTTP interfaces: authorization and token (which issue tokens), UserInfo (extra claims), and JWKS (the keys that verify token signatures).
  • Flow (grant type) — how an application obtains tokens; the authorization code flow is standard for web and mobile apps.

Because this vocabulary is standardised, any compliant provider will do — so a single one can sit behind both an application and the infrastructure it runs on.

Keycloak as the Lab’s Identity Provider

Keycloak is one such provider: open-source and self-hosted. I use it to authenticate my microservice applications, and because it speaks standard OIDC the same instance can serve my infrastructure too — one identity source, one set of users and groups, one login across the apps and the platform they run on.

Keycloak organises identity with its own structures, which sit above the protocol. It emits standard OIDC on the wire (and can speak SAML), but the internal arrangement is its own:

  • Realm — an isolated space with its own users, clients, roles, and groups. Each realm is a separate OIDC issuer, so it is what an application or, later, the Identity Broker actually points at.
  • Client — an application registered in a realm. The microservice and the VCF integration are each a client of the same realm, which lets them share one user base.
  • Groups — a hierarchical grouping of users, and the basis for authorisation: a consumer such as VCF maps them onto its own roles.
  • Roles — Keycloak’s own role model (realm and client roles), distinct from the roles defined inside VCF.
  • Protocol mapper — the rule that injects a user’s attributes, groups, or roles into the token as claims; without a groups mapper, a consumer never sees the membership it needs.
  • User federation — Keycloak can store users itself or federate to an upstream source such as LDAP; in the lab they live in Keycloak directly.

VCF Identity Broker

On the infrastructure side sits VMware Cloud Foundation (VCF), a private cloud platform of several components — vCenter, NSX, VCF Operations, and VCF Automation among them. From VCF 9.0 onward they share a single sign-on experience through the VCF Identity Broker (vIDB): the intermediary that federates authentication to an external provider — the Keycloak above — and brokers the resulting trust to each component.

A few terms recur when working with the broker. The capability itself is VCF SSO (Single Sign-On), introduced in VCF 9.0: authenticate once and reach every participating component. It works by delegating authentication to an external Identity Provider (IdP) — Keycloak here, though any compliant OIDC or SAML 2.0 provider would serve, Microsoft Entra ID or Okta included. All of this is configured under Fleet Management, the area of VCF Operations (beneath Identity & Access) where SSO lives — and a fleet can span multiple VCF instances served by a single broker. Authorisation comes from role mapping: groups from the IdP are mapped to roles in each component, controlling what a user may do, which VCF 9.1 formalised as VCF Roles. And the users and groups themselves reach the broker by provisioning — Just-in-Time (JIT) at first login, SCIM sync, or AD/LDAP import.

The result is the single identity source the lab set out to build: the microservice and VCF both authenticate against the same Keycloak realm, application and infrastructure alike.

The Two Sides Have to Agree

On the Keycloak side, a handful of objects carry the integration: a realm to hold them, the groups that map to VCF roles — vcf-admins for full administrators, vcf-operators for infrastructure operators, and vcf-viewers for read-only — a user in each, and a client representing VCF as a relying party. Three tiers, not one: the whole point of federated RBAC is that which group you’re in decides what you can do — and a small surprise along the way was that VCF ships only admin and read-only built-in roles, at two scopes (everything, or just the vCenter-and-NSX infrastructure), so the “operator” tier is really that infrastructure-scoped admin rather than a bespoke reduced-privilege role.

The bridge between the two worlds is a single claim. The client emits a groups claim in the token, listing the groups the user belongs to, and that claim is the entire surface VCF binds roles against. Get it wrong and nothing matches — and there are two ways to get it wrong, both of which I found. Keycloak’s default group mapper strips the leading slash, so the claim reads vcf-admins, not /vcf-admins. And the client only accepts redirect URIs on an explicit whitelist, so every VCF component that bounces a user through it has to be listed up front, or the login dies with a generic parameter error.

There’s a quieter trap on the user side. If the Keycloak user’s profile is incomplete, Keycloak wants to interrupt the first login with a “verify your profile” or “verify your email” screen — screens the VCF broker never renders, so the login simply dead-ends. The fix is to create the user complete and pre-verified, leaving Keycloak nothing to interrupt with.

None of these are hard once you know them. All of them cost time, because the failure mode is always the same generic “couldn’t authenticate the user” with nothing pointing at the cause.

Customising Keycloak

One of the great things about Keycloak is its flexibility in customisation. I gave it a custom login theme — a CSS-only overlay on the stock one: a dark glass card over a full-bleed wallpaper with a teal accent. Pure vanity, and it took far longer than it should have, mostly fighting an inherited stylesheet that quietly rescaled the whole layout. But signing into my own lab past my own login screen is a small daily pleasure I refuse to apologise for.

Lab Build Automation

The end-to-end build of the VCF 9.1 lab is automated with Ansible, backed by a mix of the VMware SDK and custom Python. The identity slice of that build stitches three things together: step-ca for HTTPS certificates, Keycloak, and the VCF Identity Broker.

VCF Operations exposes a clean, public, documented identity API. It’s tidy, it reads beautifully, and it is not the one that configures anything. My first implementation used it, checked whether a provider already existed, found the realm the installer creates by default, concluded everything was in order, and reported success — having federated precisely nothing. The public API reads. It does not write the federation.

The configuration actually happens through an internal API the public surface doesn’t advertise — a different path, gated behind a header that amounts to “yes, I know this is unsupported,” authenticated with a token type that isn’t the bearer token everything else uses. Once I understood that the public API is a read-only mirror and the internal one is where writes land, the whole thing fell open.

And it isn’t one call. It’s a stateful sequence — accept the terms, create the SSO domain, register the provider, attach vCenter and NSX, bind each group to its built-in role (vcf-admins to administrator, vcf-operators to an SDDC-scoped admin, vcf-viewers to a read-only role), attach Operations and Automation, mark it finished — each step depending on the last. With a parting gift: the component names are spelled one way on the write side and another on the read side, the same things in two vocabularies depending on which door you came in through.

This is exactly the kind of thing you don’t want to perform by hand at 11pm, trying to remember which step you were on. Fragile, multi-step, sparsely documented, unforgiving of order — which is to say, a thing to encode once, carefully, and never touch by hand again.

So the automation drives exactly that, across two phases. One stands up Keycloak on the gateway — container, certificate, theme — behind a health gate that waits for a fresh Keycloak to finish its slow first boot before anything tries to configure it; start too early and you get connection-refused noise that looks like a real failure. A later phase creates the realm, group, user, and client, then drives a custom module through the broker’s internal setup sequence — that same stateful series, automated end to end.

The provider registration is where the OIDC details concentrate: the discovery URL back to Keycloak, the client credentials, the subject claim as the stable user identifier, the attribute mappings from standard claims to VCF user fields, and the lab CA chain so the broker will trust Keycloak’s TLS. There’s also a small trick borrowed from William Lam — pre-publishing each bound group to the broker via SCIM. By default the broker only provisions a group on a user’s first login, which means a group’s role can’t be bound until someone in it has already signed in; pre-syncing every bound group breaks that chicken-and-egg, so the admin, operator, and read-only roles all land in a single run rather than waiting for three different people to log in first.

And because the groups, their users, and the role bindings are all just data the automation reads, adding another access tier is one entry in that list — the module, the data-driven bindings, and the SCIM pre-publish absorb it with no other code change. That’s the whole payoff of doing the fiddly part once: the careful work was encoding the stateful sequence, and extending it is now a line of configuration.

The rest of the effort went into making it safe to re-run, because a lab gets rebuilt constantly. The create steps aren’t idempotent, so the module checks for an existing provider first and reuses it. One honest sharp edge: it reuses that provider as-is and does not re-push the trusted certificate chain — so if the CA is re-keyed under a live federation, the broker keeps trusting the old CA and every login fails later with an unhelpful trust error. A normal rebuild sidesteps this entirely (it registers the provider fresh against the current CA); a deliberate re-key recovers by resetting the SSO domain so the next run re-registers cleanly. What it does not do is update that chain in place, so after a re-key a plain re-run won’t fix it — the reset is what recovers it. Async operations get bounded polling rather than blind sleeps — resetting the SSO domain, for instance, leaves it briefly in a state where further writes are rejected, so the reset waits for it to genuinely clear. “Already exists” responses are treated as success — a re-run finding its own previous work — while anything genuinely unexpected fails loudly.

One limit found the hard way: the setup sequence federates the management components only. A workload domain arrives with its own vCenter and NSX — separate security entities that the federation does not follow automatically, even on a re-run. The shared local administrator works on both, but the single login stops at the management pane. Extending the sequence to each new domain’s pair is the natural next step — and the data-driven shape above is what makes that an extension rather than a rewrite.

The lesson, in the end, wasn’t about OIDC. The protocol is the easy, standard, well-documented part. The hard part was finding that an enterprise platform had hidden the working half of its API behind an “unsupported” flag, and that getting both ends of a federation to agree means knowing a dozen small unwritten things that each announce themselves only by failing identically. The wizard in the UI clicks through in five minutes. Reproducing it as something that runs the same way every time, survives a rebuild, and re-federates new components as they appear — that’s the real work, and the only version worth keeping.

These days one login gets me into every console in the lab, past a login screen I built myself. Both ends of that handshake are mine now, and I understand every step in between.