OpenShift SSO

OGO includes an auth-bridge sidecar that translates OpenShift OAuth tokens into standard OIDC JWTs. This lets users log in to the OpenShell gateway with their OpenShift credentials - no external identity provider needed.

How it works

Browser ──▶ OpenShift OAuth ──▶ auth-bridge ──▶ JWT
                                    │
CLI ◀── stores JWT ◀────────────────┘
  1. User runs openshell gateway login
  2. Browser opens the auth-bridge's /authorize endpoint
  3. Auth-bridge redirects to OpenShift OAuth login
  4. User authenticates with OpenShift credentials
  5. OAuth redirects back to auth-bridge with an authorization code
  6. Auth-bridge exchanges the code for an OpenShift token
  7. Auth-bridge calls the OpenShift UserInfo API to get identity
  8. Auth-bridge mints a short-lived JWT with the user's identity
  9. CLI receives and stores the JWT

Configuration

Auth-bridge is enabled by default on OpenShift. Control it via the CR:

spec:
  auth:
    openshift:
      enabled: true              # default on OpenShift, false on vanilla K8s
      userGroup: openshell-users  # required - only members can authenticate
      adminGroup: openshell-admins # OpenShift group for admin role
      tokenTTL: "8h"             # JWT lifetime

Browser SSO requires spec.route.enabled to be true and an explicit spec.route.hostname. The OAuth callback uses the derived public auth Route hostname. With routing disabled, use the internal HTTPS Service for headless token exchange instead; no browser callback is available.

If browser SSO and routing are both enabled but spec.route.hostname is left empty, OGO does not create the auth Route or OAuthClient — a router-assigned host isn't known until after the Route exists, so there is nothing stable to register as the OAuth redirect URI. The gateway reports Available: False with a BrowserSSOReady condition (Reason: HostnameMissing) instead of silently registering a redirect that can't work. Headless (mTLS/ServiceAccount) access is unaffected either way.

User group (required)

The userGroup field specifies the OpenShift group required for SSO access. Only members of this group can authenticate via the browser login flow. Users not in the group are rejected with a 403 error at login.

Set up the group and add users:

oc adm groups new openshell-users
oc adm groups add-users openshell-users alice bob

This check does not affect mTLS authentication (used by CI/automation) or the internal sandbox bootstrap (K8s ServiceAccount tokens).

Internal workload clients

When gateway TLS is enabled, the auth bridge exposes HTTPS through the Gateway Service on port 8085. OGO publishes the verification certificate without private keys in the <gateway>-auth-ca ConfigMap in the gateway namespace.

This ConfigMap is specific to the auth bridge's HTTPS listener. Clients connecting to the gateway's own gRPC listener with an OIDC or bearer token instead should use <gateway>-gateway-ca — see TLS trust for OIDC and bearer-token clients.

A workload in that namespace can mount the ConfigMap directly. For a workload in another namespace, copy only ca.crt into a ConfigMap in the workload namespace and mount that copy. The client can then verify the internal endpoint without access to a TLS Secret:

curl --cacert /path/to/ca.crt \
  https://<gateway>.<gateway-namespace>.svc:8085/healthz

The co-located OpenShell gateway continues to use the loopback-only HTTP listener for OIDC discovery. Plain HTTP is not exposed through the Service when TLS is enabled.

If spec.tls.serverCertSecretName supplies a custom server certificate, its SANs must cover <gateway>.<gateway-namespace>.svc and, when browser SSO is used, the derived auth Route hostname. The Secret should include ca.crt; OGO falls back to publishing the public tls.crt when it is absent.

The auth bridge validates the certificate and key before serving, then caches the last valid pair for five minutes. It reloads rotated files after the cache expires. If the files are temporarily inconsistent during rotation, the bridge continues serving the last valid pair until it can load the replacement.

Troubleshooting

Gateway reports BrowserSSOReady: False, Reason: HostnameMissing

Browser SSO (spec.auth.openshift.enabled) and routing are both enabled, but spec.route.hostname is empty. Set it explicitly:

spec:
  route:
    hostname: openshell.apps.example.com

Or, for headless-only access (mTLS/ServiceAccount, no browser login), disable browser SSO instead:

spec:
  auth:
    openshift:
      enabled: false

"access denied: you are not a member of the required OpenShift group"

Your OpenShift user is not in the group specified by spec.auth.openshift.userGroup. Ask your cluster admin to add you:

oc adm groups add-users <group-name> <your-username>

Check your current groups:

oc get users <your-username> -o jsonpath='{.groups}'

Emergency token revocation

To immediately invalidate all active tokens (e.g., a user who should not have access obtained a valid token):

oc delete secret openshell-auth-bridge-keys -n ogo
oc delete pod -n ogo -l app.kubernetes.io/name=openshell

The operator generates new signing keys and the gateway restarts with fresh JWKS. All existing tokens become invalid within ~30 seconds. Every user must re-login, and users removed from the userGroup will be blocked.

For less disruptive revocation, reduce spec.auth.openshift.tokenTTL to a shorter duration (e.g., "30m"). Revoked users lose access when their token expires naturally.

"authentication failed" after gateway restart

As of v0.2.1, the operator syncs the OAuthClient secret and redirect URI on every reconcile, so this self-heals within one reconcile loop (a few seconds) — no manual action needed.

On older versions, or if the issue persists, force it by deleting both and letting the operator recreate them:

oc delete secret openshell-oauth-client -n ogo
oc delete oauthclient openshell
# Wait 60s for the operator to reconcile, then restart the gateway pod
oc delete pod -n ogo -l app.kubernetes.io/name=openshell

Token lifetime

The tokenTTL field controls how long the JWT is valid. After expiry, the CLI prompts for re-authentication. The default is 8 hours.

The auth-bridge Ed25519 signing keypair is persisted in a Kubernetes Secret (openshell-auth-bridge-keys). Tokens survive pod restarts. The operator creates the keypair once; subsequent pod restarts load the same keys.

OAuthClient

The operator creates an OpenShift OAuthClient CR named openshell with the auth-bridge route as the redirect URI. The client secret is stored in the openshell-oauth-client Secret.

If you delete and recreate the OAuthClient, also delete the openshell-oauth-client Secret to avoid a client secret mismatch.

Admin role

Users in the OpenShift group specified by spec.auth.openshift.adminGroup are granted the openshell-admin role in the gateway. This role allows managing other users' sandboxes and viewing all sandbox logs.

See also