OIDC (Dex)
Sign in to the hub with GitHub, Google, LDAP, or any OIDC provider via Dex.
For teams or anywhere you want real per-user identities, use OIDC. The hub itself does not care which OIDC provider you use, but the easiest path is Dex : it sits in front of GitHub / Google / LDAP / SAML / OIDC backends and presents one OIDC endpoint to the hub.
Browser ──▶ Hub ──▶ Dex ──▶ Identity backend (GitHub, Google, LDAP, …)
│ │
└────────── OIDC redirect ────────────────────┘With external kcp , kcp’s front proxy and shards verify the same tokens, so they are configured with the same issuer and client.
Prerequisites
- A publicly reachable HTTPS URL for Dex, served through your ingress . The hub reads Dex’s discovery document at start and restarts until it can.
- An identity backend: a GitHub OAuth App, Google credentials, an LDAP server, and so on.
- The hub’s public URL, for the client’s redirect URI.
1. Deploy Dex
helm repo add dex https://charts.dexidp.io
helm repo update
kubectl create namespace dexDex needs somewhere to keep signing keys, auth requests, and refresh tokens. Two options:
- Kubernetes storage. Dex stores its state as custom resources in its namespace. No volume, no database, and it works with more than one replica; losing it signs everyone out and nothing more. This is what Railgrid runs.
- SQLite on a PVC. Simplest for a single replica with no CRDs.
Put the connector secret in a Secret and let Dex expand it from the environment rather than writing it into the values file:
kubectl -n dex create secret generic dex-secrets \
--from-literal=GITHUB_CLIENT_SECRET="<github-client-secret>"Dex values (dex-values.yaml), Kubernetes storage:
replicaCount: 2
config:
# Must equal idp.issuerURL in the hub values, character for character.
issuer: https://auth.example.com
storage:
type: kubernetes
config:
inCluster: true
# Plain HTTP in the cluster; TLS ends at your ingress.
web:
http: 0.0.0.0:5556
staticClients:
# Public PKCE client used by the console, `railgrid login`, and, as the
# token audience, kcp. Must match idp.clientID.
- id: railgrid
name: Railgrid
public: true # no client secret
redirectURIs:
- https://console.example.com/auth/callback
- http://localhost:8000
connectors:
- type: github
id: github
name: GitHub
config:
clientID: <github-client-id>
clientSecret: $GITHUB_CLIENT_SECRET # expanded from dex-secrets
redirectURI: https://auth.example.com/callback
# Optional: only members of this organization may sign in.
org: your-org
envFrom:
- secretRef:
name: dex-secrets
service:
type: ClusterIP
# Route auth.example.com yourself (Gateway API, cfgate) or enable the
# chart's Ingress for an ingress controller:
ingress:
enabled: falseFor SQLite instead, create a 1 GiB PersistentVolumeClaim named dex-data, set storage.type: sqlite3 with config.file: /var/dex/dex.db, mount the claim at /var/dex through volumes/volumeMounts, and keep replicaCount: 1.
Install Dex:
helm upgrade --install dex dex/dex --version 0.24.1 \
--namespace dex -f dex-values.yamlRailgrid runs chart 0.24.1. Verify the issuer through its public URL, because that is what the hub and kcp will use:
curl -s https://auth.example.com/.well-known/openid-configuration | jq -r .issuer
# https://auth.example.comDex answers 404 on /. A load balancer that health-checks / will mark it down; check /healthz instead (see Gateway API
).
2. Configure the hub for OIDC
Update your hub values: remove staticAuthTokens (or keep them; both methods can coexist) and add the idp section:
hub:
hubExternalURL: "https://console.example.com"
idp:
issuerURL: "https://auth.example.com"
clientID: "railgrid"There is no client secret: the hub, CLI, and console are PKCE public clients. That is why the Dex client above is marked public: true.
If your IdP serves a certificate signed by a private CA, put the PEM bundle in a Secret and reference it. The hub then verifies the issuer properly instead of you disabling TLS checks:
idp:
issuerURL: "https://auth.example.com"
clientID: "railgrid"
caSecretName: "idp-ca" # Secret with the PEM bundle
caSecretKey: "ca.crt" # key inside the SecretRoll out, with HUB_CHART_VERSION resolved as in Install the hub
:
helm upgrade --install railgrid-hub oci://ghcr.io/railgrid/charts/railgrid-hub \
--version "$HUB_CHART_VERSION" -f hub-values.yaml --namespace railgrid-system3. Configure kcp (external kcp only)
Every kcp component that authenticates users needs the same issuer and client, with the identity prefixes the hub hard-codes. On the FrontProxy, the RootShard, and every Shard:
auth:
oidc:
issuerURL: https://auth.example.com
clientID: railgrid
groupsClaim: groups
usernameClaim: email
usernamePrefix: "railgrid:"
groupsPrefix: "railgrid:"Any other prefix denies every request, because workspace RBAC is bound to railgrid:<email>. If the issuer’s certificate chains to a root the kcp image does not ship (a cloud-managed edge certificate, for example), set spec.caBundleSecretRef on the FrontProxy to a Secret holding that chain.
4. Log in
kubectl railgrid login --hub-url https://console.example.comThe browser opens the hub’s /auth/authorize endpoint, which redirects to Dex, which redirects to your identity backend. After you authorize, the CLI receives your kubeconfig over a one-time localhost callback. The kubeconfig uses an exec credential plugin that refreshes tokens automatically; see Login & Authentication
.
Identity connectors
Dex supports many backends. The most common configurations:
GitHub
- GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.
- Authorization callback URL:
https://auth.example.com/callback. An OAuth App has exactly one callback URL, so the Code provider’s “Connect with GitHub” feature needs its own app; see Self-host Code . - Add to Dex
connectors:
- type: github
id: github
name: GitHub
config:
clientID: <github-client-id>
clientSecret: $GITHUB_CLIENT_SECRET
redirectURI: https://auth.example.com/callback
# Optional: restrict to organization members
org: your-org- Create OAuth credentials in the Google Cloud Console .
- Add to Dex
connectors:
- type: google
id: google
name: Google
config:
clientID: <google-client-id>
clientSecret: $GOOGLE_CLIENT_SECRET
redirectURI: https://auth.example.com/callback
# Optional: restrict to a Workspace domain
hostedDomains:
- example.comLDAP
- type: ldap
id: ldap
name: LDAP
config:
host: ldap.example.com:636
insecureNoSSL: false
bindDN: cn=admin,dc=example,dc=com
bindPW: $LDAP_BIND_PASSWORD
userSearch:
baseDN: ou=users,dc=example,dc=com
filter: "(objectClass=person)"
username: uid
idAttr: uid
emailAttr: mail
nameAttr: cnKeep accounts across connector and issuer changes
A Railgrid account is found at sign-in by issuer + subject. Dex encodes the connector into the subject, so a person who signed in with GitHub and later signs in with Google is a different subject; moving to another issuer changes both. Without preparation, every returning user gets a fresh, empty account.
idp.linkVerifiedEmailIssuers lists issuers whose not-yet-known identities may be linked onto the one existing account with the same verified email:
idp:
linkVerifiedEmailIssuers:
- https://auth.example.comListing an issuer trusts it to have verified email ownership for every connector it fronts; the GitHub and Google connectors do. Linking only acts on identities no account holds yet, so it can stay on. kcp is unaffected either way: it identifies users as railgrid:<email>.
To add a connector: add it to Dex, set the hub value, and people who sign in through the new connector land on their existing account. To move to another issuer: register the public client there (redirects <hub>/auth/callback and http://localhost:8000, email and email_verified in the ID token), set idp.issuerURL and add the new issuer to the list, update the kcp OIDC blocks, and have everyone sign in again.
Troubleshooting
invalid issuer from the hub. config.issuer in Dex must match idp.issuerURL in the hub byte for byte. Same scheme, no trailing slash.
Callback URL mismatch. redirectURIs in Dex’s staticClients must include exactly <hubExternalURL>/auth/callback. Mismatched scheme or path is the usual culprit.
Hub restarts on start. It cannot fetch the issuer’s discovery document. Check that https://auth.example.com/.well-known/openid-configuration answers from inside the cluster, including DNS and TLS.
TLS errors on the issuer. If Dex’s certificate is signed by a private CA, set idp.caSecretName / idp.caSecretKey (see above), and spec.caBundleSecretRef on the kcp front proxy. As a last resort for throwaway dev setups, hub.devMode: true skips issuer TLS verification. Never in production.
unauthorized_client or secret prompts. The Dex client must be public: true with no secret. Railgrid does PKCE; a confidential client configuration will fail.
Signed in to the console, but every kcp request is forbidden. The kcp OIDC block differs from the hub’s: wrong issuer or client, or a missing railgrid: prefix.
Dex logs:
kubectl -n dex logs -l app.kubernetes.io/name=dexDiscovery sanity check:
curl -s https://auth.example.com/.well-known/openid-configuration | jq .