Skip to content
Railgrid Docs
Sections
Open console

Enter a topic to search documentation.

    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.

    TEXT
    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

    Terminal
    helm repo add dex https://charts.dexidp.io
    helm repo update
    kubectl create namespace dex

    Dex 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:

    Terminal
    kubectl -n dex create secret generic dex-secrets \
      --from-literal=GITHUB_CLIENT_SECRET="<github-client-secret>"

    Dex values (dex-values.yaml), Kubernetes storage:

    YAML
    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: false

    For 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:

    Terminal
    helm upgrade --install dex dex/dex --version 0.24.1 \
      --namespace dex -f dex-values.yaml

    Railgrid runs chart 0.24.1. Verify the issuer through its public URL, because that is what the hub and kcp will use:

    Terminal
    curl -s https://auth.example.com/.well-known/openid-configuration | jq -r .issuer
    # https://auth.example.com

    Dex 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:

    YAML
    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:

    YAML
    idp:
      issuerURL: "https://auth.example.com"
      clientID: "railgrid"
      caSecretName: "idp-ca"      # Secret with the PEM bundle
      caSecretKey: "ca.crt"       # key inside the Secret

    Roll out, with HUB_CHART_VERSION resolved as in Install the hub :

    Terminal
    helm upgrade --install railgrid-hub oci://ghcr.io/railgrid/charts/railgrid-hub \
      --version "$HUB_CHART_VERSION" -f hub-values.yaml --namespace railgrid-system

    3. 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:

    YAML
    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

    Terminal
    kubectl railgrid login --hub-url https://console.example.com

    The 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

    1. GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.
    2. 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 .
    3. Add to Dex connectors:
    YAML
    - 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

    Google

    1. Create OAuth credentials in the Google Cloud Console .
    2. Add to Dex connectors:
    YAML
    - 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.com

    LDAP

    YAML
    - 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: cn

    Keep 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:

    YAML
    idp:
      linkVerifiedEmailIssuers:
        - https://auth.example.com

    Listing 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:

    Terminal
    kubectl -n dex logs -l app.kubernetes.io/name=dex

    Discovery sanity check:

    Terminal
    curl -s https://auth.example.com/.well-known/openid-configuration | jq .