Configure External OIDC
The Virtual Machine Orchestrator (VMO) pack can authenticate users against a third-party OpenID Connect (OIDC) identity provider (IdP) instead of using Palette-managed OIDC. Use this configuration when you want VMO to authenticate against your own IdP, or against a different application than the one Palette uses.
This page uses Okta as the example IdP. The same steps apply to any OIDC-compliant IdP, such as Microsoft Entra ID or Google, with the IdP-specific console labels changed accordingly.
A Single Sign-On (SSO) sign-in to VMO follows this sequence.
-
The user opens the VMO UI in a browser.
-
VMO redirects the browser to the IdP for authentication.
-
The IdP returns an ID token to the VMO callback endpoint.
-
VMO stores the session and passes the ID token as the
Authorization: Bearerheader on subsequent requests to the Kubernetes API server. -
The Kubernetes API server validates the ID token against the IdP and applies RBAC based on the username and groups claims.
Because the Kubernetes API server validates the token, the OIDC settings in the pack must match the OIDC flags that the API server was started with.
VMO also supports API key sessions for automation. API key tokens cannot be validated by the Kubernetes API server, so
these requests use the VMO service account token together with impersonation headers that reconstruct the identity of
the user who created the key. This is why the oidc.k8sUsernamePrefix and oidc.k8sGroupPrefix parameters must mirror
the API server flags.
Prerequisites
-
A cluster provisioned through Palette with the VMO pack in an add-on cluster profile. Refer to Create a VMO Profile for guidance.
-
Administrator access to your IdP, so that you can create or modify the OIDC application registration that represents VMO. This registration is what tells the IdP how to handle sign-in requests that arrive from VMO.
-
Cluster admin access through a kubeconfig file, so that you can create cluster role bindings.
-
The Kubernetes API server must be configured with OIDC flags that match your IdP application. Issue the following command to confirm the flags in use.
kubectl --namespace kube-system get pod --selector component=kube-apiserver \
--output jsonpath='{.items[0].spec.containers[0].command}' | tr ',' '\n' | grep -- --oidcExample output--oidc-issuer-url=https://<your-okta-domain>
--oidc-client-id=<your-client-id>
--oidc-username-claim=email
--oidc-groups-claim=groupsIf the output also contains
--oidc-username-prefixor--oidc-groups-prefix, make a note of those values. You must mirror them in the pack so that API key impersonation constructs identities with matching prefixes.
Configure the IdP Application
You can reuse the same OIDC application that the cluster's Kubernetes layer uses, or register a separate one for VMO.
The following steps use Okta console labels. Every setting has an equivalent in any OIDC-compliant IdP, though the field names differ.
When the IdP is Keycloak, VMO requires two realm-side configurations for group-based RBAC to work end-to-end:
- A
groupsclient scope on the realm. VMO force-appends thegroupsscope to every OIDC authentication request, regardless of what is set inoidc.scopeson the pack. If the realm does not recognizegroupsas a client scope, Keycloak returnsinvalid_scopeand sign-in fails. - A Group Membership mapper on the
profileclient scope. Without it, VMO Manager receives an emptygroupsclaim, and everyClusterRoleBindingwith aGroupsubject silently fails.
Palette Console SSO and Palette-managed Kubernetes clusters do not require the mapper, because they do not consume the
groups claim directly. Refer to
Sync Keycloak Groups and Palette Teams
for the mapper configuration, including the four token-inclusion toggles that VMO requires.
-
Log in to your IdP administration console.
-
Open the OIDC application that represents VMO, or create a new one.
-
Set the application type to Web, and enable both the Authorization Code and Refresh Token grant types.
Without the Refresh Token grant type, users are prompted to sign in again when the access token expires.
-
Add the VMO callback URL to the Sign-in redirect URIs field. The callback URL is
<baseUrl>/auth/callback, where<baseUrl>is the public URL that users reach the VMO UI at. For example,https://vmo.example.com/auth/callback.When VMO is reached through the Palette tenant apps proxy, the base URL includes the tenant app path prefix. Enter the exact URL that appears in the browser address bar at sign-in time.
-
Assign the users and groups that need access to VMO to the application.
-
Configure the groups claim so that the IdP includes group memberships in the ID token. In Okta, open your application and select Sign On > OpenID Connect ID Token > Edit. Then set the following fields.
Field Value Groups claim type FilterGroups claim name groups, which must match the Kubernetes API server--oidc-groups-claimflagFilter Matches regex with a pattern that covers the groups you want to grant access to, such as vmo-.* -
Save the application configuration.
New sign-ins include the groups claim. Users with an existing session must sign out and sign in again.
Okta caps the groups claim at 100 entries in the Implicit Flow. VM Launchpad's Keycloak broker uses the Authorization Code Flow, which does not have this cap, so an unbounded filter is safe. If you configure Keycloak to broker over the Implicit Flow instead, the cap applies and a user who belongs to more than 100 groups receives a truncated claim. Refer to Error: The groups claim matched too many groups and must be configured to match fewer groups in Okta's documentation for the underlying constraint.
The groups claim is derived from the user's actual group memberships, not from application assignments. A user must belong to a group whose name matches the filter for that group to appear in the token.
Configure the VMO Pack
-
Log in to Palette.
-
From the left main menu, select Profiles, and select your VMO add-on cluster profile.
-
Select the Virtual Machine Orchestrator layer.
-
In the Presets panel, locate the Alternative Authentication group and select External OIDC.
This sets
oidc.enabledtotrue, setspalette.managedOidctofalse, and turns local authentication off. Refer to Authentication Options for the complete list of values applied.If the save fails with an error naming undefined
spectro.var.*variables, define the missing profile variables and select the preset again. Refer to Preset Save Fails with Undefined Variables for the full list. -
Select Values and enter the parameters that are specific to your IdP.
charts:
virtual-machine-orchestrator:
vmo-manager:
palette:
enabled: true
managedOidc: false
oidc:
enabled: true
issuerUrl: "https://<your-okta-domain>"
clientId: "<your-client-id>"
clientSecret: "<your-client-secret>"
# Optional. Defaults to "openid,profile,email,groups". The pack
# force-appends "groups" if you omit it, so removing it from this
# list has no effect on the outbound OIDC request against any IdP.
# When the IdP is Keycloak, see the Keycloak admonition above for
# the realm-side prerequisites (the "groups" client scope plus the
# Group Membership mapper).
scopes: ""
# Set when the OIDC callback needs to go through an external proxy
# such as the Palette tenant apps proxy. Obtain the callback URL
# from the deployed VMO Manager pack after the profile deploys; the
# UI does not surface this URL directly. Defaults to
# <baseUrl>/auth/callback when left empty.
callbackUrl: ""
# Set to match the API server --oidc-username-prefix flag, such as "oidc:".
k8sUsernamePrefix: ""
# Set to match the API server --oidc-groups-prefix flag.
k8sGroupPrefix: ""
platform:
baseUrl: "https://<the-url-users-reach-vmo-at>"
features:
localAuth:
# The External OIDC option turns local authentication off. Set this
# back to true to keep a Day 0 local admin account as a fallback.
enabled: true
adminUsername: "admin"
adminPassword: "{{ .spectro.var.LOCAL_ADMIN_PASSWORD }}"The pack renders
oidc.clientSecretinto a Kubernetes Secret rather than a ConfigMap. -
Add a
LOCAL_ADMIN_PASSWORDvariable to the profile and set it to a strong password. Together withfeatures.localAuth.enabled, this seeds a Day 0 local admin account that you use to verify the deployment before OIDC users are mapped to roles. Refer to Define Cluster Profile Variables for guidance. -
Select Confirm Updates, and then Save Changes.
-
Apply the profile to your cluster and wait for the cluster to report a Healthy status.
Use an Existing Secret
Instead of entering the client secret in the pack values, you can pre-create a Kubernetes Secret and reference it. The
Secret must contain the OIDC_CLIENT_SECRET and SESSION_KEY keys. The SESSION_KEY value must be at least 32
characters. VMO Manager fails to start if the value is shorter.
charts:
virtual-machine-orchestrator:
vmo-manager:
secrets:
create: false
existingSecret: "vmo-manager-oidc"
Configure Non-Federated Kubernetes API Servers
If the cluster's Kubernetes API server is not federated with the same OIDC issuer that VMO uses, cluster-wide Settings
pages return 403 forbidden: cluster-wide K8s access required for this Setting, and the Settings menu is hidden from
the sidebar. VMO passes the user's ID token as an Authorization: Bearer header to the Kubernetes API server, and the
API server rejects the token when its OIDC flags do not match.
The recommended fix is to configure the Kubernetes API server with --oidc-issuer-url and --oidc-client-id flags that
match the OIDC application VMO uses, so that OIDC identity flows through the same trust chain in both layers. Refer to
Prerequisites for the verification command.
If reconfiguring the API server is not an option, set oidc.k8sNotFederated to true in the pack values.
charts:
virtual-machine-orchestrator:
vmo-manager:
oidc:
k8sNotFederated: true
The default is false. Leave the default in place whenever the API server is federated with the same OIDC issuer as
VMO. This includes Palette-managed OIDC and the VM Launchpad appliance, which ships with Keycloak federating both VMO
and the Kubernetes API server.
With k8sNotFederated set to true, cookie and OIDC sessions call the Kubernetes API using the VMO Manager service
account token, without impersonation headers. The cluster-scope Self-Subject Access Review probe is bypassed, and the
cluster-scoped Settings pages open on the VMO IAM Platform Admin role alone. Namespace-scoped VM writes (create, delete,
clone, disks, NICs, snapshots) also ride the cluster-wide service account token, so Kubernetes RoleBinding resources
that scope users to specific namespaces no longer apply to those writes.
Setting oidc.k8sNotFederated to true collapses two independent authorization gates into one. Any user granted a VMO
IAM Platform Admin role reaches cluster-wide Settings without an independent Kubernetes RBAC check, and a user bound to
a single namespace through Kubernetes RBAC can create VMs in any managed namespace in this mode. Use this option only
for deployments where every OIDC user is trusted with cluster-wide reach, and restrict VMO IAM Platform Admin group and
user mappings accordingly.
API key sessions, bearer-token endpoints, and local-authentication sessions are not affected by this setting.
Grant Access to Groups
Two authorization layers govern VMO access, and both must recognize the user's IdP group for a session to reach every part of the product.
- VMO IAM roles control what the UI and API expose to the session, such as which pages are visible and which VMO actions the session can invoke. Four built-in roles (Platform Admin, Editor, Operator, Viewer) map to fine-grained VMO permissions. VMO Manager checks every request against this layer first.
- Kubernetes RBAC controls the writes VMO issues to the Kubernetes API server on the user's behalf, along with any
direct
kubectlaccess the user makes. Fourspectro-vm-*cluster roles cover the common access patterns.
The two layers are complementary, not redundant. VMO IAM decides what the UI exposes. Kubernetes RBAC decides what the
API server accepts. Cluster-wide Settings pages require both layers to recognize the user's group, unless
oidc.k8sNotFederated is set to true. In that mode, VMO IAM alone
gates cluster-wide Settings. Refer to VM User Roles and Permissions for the full role
catalog and permission list.
VMO IAM Roles
VMO Manager seeds four group mappings when the pack starts. If your IdP group name exactly matches one of the following, users in that group receive the matching VMO role automatically without further configuration.
| IdP group name | VMO role | What the user can do |
|---|---|---|
cluster-admins | Platform Admin | Full platform control. Every VMO permission, including Users, Groups, Settings, Infrastructure CRUD, and all VM, template, network, storage, namespace, and package operations. |
vmo-editors | Editor | VM workflows and Image Catalog. VM create, update, delete, operate, and snapshot. Templates and packages read and write. Network, storage, and namespace read for the create-VM wizard. No infrastructure write and no Settings. |
vmo-operators | Operator | Day-to-day VM operations. VM read, operate, snapshot, and restore. Templates, dashboards, audit, and system or monitoring read. No writes. |
cluster-viewers | Viewer | Read-only. VM, template, dashboard, audit, and system or monitoring reads only. No operate, no snapshot, no writes. |
If your IdP group is named something other than the four above, users in it do not receive a VMO role by default. Extend
the built-in VMORole custom resource that matches the VMO role you want to grant. The four built-in VMORole
resources are vmo-platform-admin, vmo-editor, vmo-operator, and vmo-viewer.
For example, to grant the Platform Admin role to an IdP group named platform-team, edit the vmo-platform-admin
resource.
kubectl --namespace vm-dashboard edit vmorole vmo-platform-admin
Under spec, add your IdP group name to the groupMappings list.
spec:
groupMappings:
- cluster-admins # keep the seeded default
- platform-team # your IdP group name
Save the change. The user must sign out and sign in again for the new mapping to take effect.
To grant a different VMO role, edit the corresponding resource instead: vmo-editor for Editor, vmo-operator for
Operator, or vmo-viewer for Viewer.
Per-user mapping is also supported when a group is unavailable or too broad. Set userMappings instead of
groupMappings, and provide the value that appears in the token's email, sub, or preferred_username claim.
Kubernetes RBAC
VMO Manager also creates four Kubernetes cluster roles when it starts. Bind these roles to your IdP groups with standard
ClusterRoleBinding resources. The group names must match exactly what the IdP emits in the groups claim, including any
prefix that the API server was started with.
-
Open a terminal and set the
KUBECONFIGenvironment variable to point at your cluster's kubeconfig file.export KUBECONFIG=<path-to-downloaded-kubeconfig-file> -
Create a manifest that maps your groups to the VMO cluster roles. The following example assumes that the API server does not use
--oidc-groups-prefix.apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vmo-admins
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: vmo-admins
roleRef:
kind: ClusterRole
name: spectro-vm-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vmo-operators
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: vmo-operators
roleRef:
kind: ClusterRole
name: spectro-vm-power-user
apiGroup: rbac.authorization.k8s.ioIf the API server uses
--oidc-groups-prefix=oidc:, the subject name becomesoidc:vmo-admins, and you must setoidc.k8sGroupPrefixin the pack tooidc:so that API key impersonation reconstructs the same identity. -
Apply the manifest.
kubectl apply --filename <your-manifest-file>.yamlTo keep these bindings in place when a cluster is rebuilt, add them as a manifest layer in the cluster profile alongside the VMO pack. Refer to Create a Manifest Add-on Profile for guidance.
For cluster-wide Settings pages to work, both authorization layers must recognize the same group name. Choosing an IdP
group name that lines up with the VMO IAM seeded defaults (such as cluster-admins for administrators
or vmo-editors for editors) means a single group grants both the VMO role from the seeded mapping and the Kubernetes
RBAC from the ClusterRoleBinding above. For a custom name, add it to both the VMORole groupMappings list and the
ClusterRoleBinding subject.
Bootstrap Order
The External OIDC option turns local authentication off, which means a misconfigured issuer URL or an unreachable IdP leaves no way to sign in. Use the following order when you bring up a new cluster with a third-party IdP, so that you always have a way in.
-
Deploy the pack with
features.localAuth.enabledset totrueandLOCAL_ADMIN_PASSWORDset to a strong value. This overrides the value that the External OIDC option applies. -
Wait for the VMO Manager pod to report a
Readystatus. -
Sign in with the local admin account at the
/local-loginpath and confirm that the UI is reachable. VMO requires a password change on first sign-in. -
Apply the cluster role bindings that map your IdP groups to the VMO cluster roles.
-
Sign out of the local account, and sign in through your IdP with a user who belongs to one of the bound groups.
-
Confirm that the user has the expected permissions.
Keep features.localAuth.enabled set to true afterward if you want a recovery path. Local authentication is a
separate path from OIDC, so an IdP outage cannot lock you out of the cluster.
Local sessions authenticate to the Kubernetes API with the VMO Manager service account token rather than with a user-scoped token, so the per-user Kubernetes RBAC described on this page does not apply to a local admin. If you keep local authentication on, treat the local admin password as a cluster-admin credential.
Validate
Validate the Sign-in Flow
-
Open the VMO UI in a new private browsing session.
-
Confirm that the browser is redirected to your IdP.
-
Sign in, and confirm that the browser returns to the VMO UI with an established session.
Validate the Groups Claim
The Kubernetes API server records the authenticated identity in its audit log for every request. Use the audit log to confirm that group memberships reach the API server.
-
Identify the node that runs the API server.
kubectl --namespace kube-system get pod --selector component=kube-apiserver \
--output jsonpath='{.items[0].spec.nodeName}' -
Open a debug shell on that node. Replace the placeholder with the node name from the previous step.
kubectl debug node/<node-name> --image=busybox --profile=general --namespace=kube-system --stdin --tty -
Read the audit log entries for your user. Replace the placeholder with your own email address. The audit log path can differ by Kubernetes distribution.
grep "<your-email-address>" /host/var/log/apiserver/audit.log | tail --lines=3 -
Inspect the
userobject in each event. Thegroupsarray must contain your IdP groups.Example output{
"username": "<your-email-address>",
"groups": ["vmo-admins", "system:authenticated"]
}If
groupscontains onlysystem:authenticated, one of the following is true.-
The groups claim is not configured on the IdP application, or the user does not match the filter.
-
The user does not belong to any group that matches the filter.
-
The Kubernetes API server was not started with
--oidc-groups-claim=groups.
-
Validate RBAC Enforcement
With a group binding in place and the user's group present in the token, the user can list VMs in the permitted namespaces. To confirm that RBAC also denies access correctly, delete the binding temporarily and refresh the UI. The user receives a 403 response with a message similar to the following.
virtualmachines.kubevirt.io is forbidden: User "..." cannot list resource
Restore the binding to grant access again.
Troubleshooting
Scenario - The IdP Returns an Invalid Redirect URI Error
The sign-in redirect URI configured in the IdP application does not exactly match <baseUrl>/auth/callback. Compare the
configured value with the browser address bar during sign-in, including the scheme, the host, and any tenant app path
prefix.
Scenario - Sign-in Succeeds but the VMs Page Returns 403
Kubernetes RBAC has no binding for the authenticated user or for any of their groups. Check the API server audit log for
the exact user.username and user.groups values, and then create a matching cluster role binding. Refer to
Validate the Groups Claim for guidance.
Scenario - Groups Appear in Palette but Not in VMO
Each OIDC application carries its own groups claim configuration. Confirm that the groups claim rule is set on the application that VMO uses, not only on the application that Palette uses.
Scenario - Repeated Sign-in Prompts
The IdP application does not have the Refresh Token grant type enabled. Enable it in the application settings.
Scenario - Sign-in Breaks After a Client Secret Rotation
Update oidc.clientSecret in the pack values, or update the referenced Secret, and then save and apply the profile.
Wait for the reconcile to redeploy the VMO Manager pod.
Scenario - Authentication Service Unavailable at Sign-in
Sign-in redirects fail with "Authentication service unavailable" in the UI, and the VMO Manager pod log contains
HandleLogin: oidcProvider is nil. The pack starts with oidc.enabled: true but no issuer URL, so VMO Manager cannot
initialize the OIDC provider.
The most common cause is a configuration mismatch: OIDC values are set on the Kubernetes pack,
palette.managedOidc: true is on the vmo-manager sub-chart, but the cluster's identity provider is an external IdP
such as Okta rather than Palette Hubble. Palette injects OIDC values into VMO only when Palette Hubble itself is the
identity provider. For any external IdP, VMO needs the values on its own pack.
Set palette.managedOidc: false and provide oidc.issuerUrl, oidc.clientId, and oidc.clientSecret explicitly on
the vmo-manager sub-chart. Refer to Configure the VMO Pack for the YAML sample.
Scenario - Settings Menu Missing Despite Platform Admin Role
A user signs in with a role that grants Platform Admin permissions in VMO. VM lifecycle, templates, and dashboards work,
but the Settings group is hidden from the sidebar. Direct navigation to a Settings path such as
/settings/configuration/kubevirt returns 403 forbidden: cluster-wide K8s access required for this Setting, and the
VMO Manager pod log at sign-in contains Cluster-scope probe: SSAR returned 401.
The Kubernetes API server does not federate the same OIDC issuer as VMO Manager, so the cluster-scope probe in VMO Manager cannot validate the user's identity against Kubernetes. VMO IAM authorizes the session at the application layer, but the Settings pages route Kubernetes API calls through the user's OIDC ID token as a Bearer, and the API server rejects the token.
Two options fix this behavior:
- Configure the Kubernetes API server with
--oidc-issuer-urland--oidc-client-idvalues that match the OIDC application VMO uses, and bind the user's group tospectro-vm-adminwith aClusterRoleBinding. Refer to Prerequisites for the verification command. This option keeps VMO IAM and Kubernetes RBAC as two independent authorization gates. - Set
oidc.k8sNotFederatedtotruein the pack values. Refer to Configure Non-Federated Kubernetes API Servers for the setting, the behavior it changes, and the security trade-off. This option makes VMO IAM the sole authorization gate for cluster-wide Settings.
For a temporary workaround, sign in with the local admin account at <baseUrl>/local-login. Local sessions bypass the
cluster-scope probe and reach all Settings pages.
Next Steps
Review Authentication Options to learn how the authentication options interact with the Deployment Mode selection, and which parameters each option applies.
If you are moving an existing cluster from an earlier VMO pack version, refer to Upgrade the VMO Pack for the differences that affect an existing OIDC configuration.