Security & Tenancy

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 doesn’t 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 ────────────────────┘

Prerequisites

  • A publicly reachable URL for Dex (or in-cluster access if you don’t expose it)
  • An identity backend (GitHub OAuth app, Google credentials, LDAP server, etc.)
  • The hub already deployed somewhere — we’ll switch it from static-token to OIDC

1. Deploy Dex

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

A small PVC for the SQLite database:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dex-data
  namespace: dex
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 1Gi

Dex values (dex-values.yaml):

config:
  issuer: https://idp.example.com

  storage:
    type: sqlite3
    config:
      file: /var/dex/dex.db

  web:
    http: 0.0.0.0:5556

  staticClients:
    - id: kedge
      name: Kedge
      public: true          # kedge is a PKCE public client — no client secret
      redirectURIs:
        - https://hub.example.com/auth/callback

  connectors:
    - type: github
      id: github
      name: GitHub
      config:
        clientID: <github-client-id>
        clientSecret: <github-client-secret>
        redirectURI: https://idp.example.com/callback
        # Optional — restrict to an org
        # org: your-org

volumes:
  - name: dex-data
    persistentVolumeClaim:
      claimName: dex-data

volumeMounts:
  - name: dex-data
    mountPath: /var/dex

service:
  type: ClusterIP

ingress:
  enabled: true
  className: "nginx"     # or cloudflare-tunnel, etc.
  hosts:
    - host: idp.example.com
      paths:
        - path: /
          pathType: Prefix

Install Dex:

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

Verify:

curl -s https://idp.example.com/.well-known/openid-configuration | head -5

2. Configure the hub for OIDC

Update your hub values — remove staticAuthTokens (or keep them; both methods can coexist), add the idp section:

hub:
  hubExternalURL: "https://hub.example.com"

idp:
  issuerURL: "https://idp.example.com"
  clientID: "kedge"

There is no client secret — the hub, CLI, and portal are PKCE public clients. That’s 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://idp.example.com"
  clientID: "kedge"
  caSecretName: "idp-ca"      # Secret with the PEM bundle
  caSecretKey: "tls.crt"      # key inside the Secret

Roll out:

helm upgrade --install kedge oci://ghcr.io/faroshq/charts/kedge-hub \
  -f values.yaml \
  --namespace kedge-system

3. Log in

kubectl kedge login --hub-url https://hub.example.com

The browser opens to the hub’s /auth/authorize endpoint, which redirects to Dex, which redirects to your identity backend (GitHub, Google, …). After you authorize, the CLI receives your kubeconfig over a one-time localhost callback. The kubeconfig uses an exec credential plugin (kedge get-token) 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://idp.example.com/callback
  3. Add to Dex connectors:
- type: github
  id: github
  name: GitHub
  config:
    clientID: <github-client-id>
    clientSecret: <github-client-secret>
    redirectURI: https://idp.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:
- type: google
  id: google
  name: Google
  config:
    clientID: <google-client-id>
    clientSecret: <google-client-secret>
    redirectURI: https://idp.example.com/callback
    # Optional — restrict to a Workspace domain
    hostedDomains:
      - example.com

LDAP

- type: ldap
  id: ldap
  name: LDAP
  config:
    host: ldap.example.com:636
    insecureNoSSL: false
    bindDN: cn=admin,dc=example,dc=com
    bindPW: admin-password
    userSearch:
      baseDN: ou=users,dc=example,dc=com
      filter: "(objectClass=person)"
      username: uid
      idAttr: uid
      emailAttr: mail
      nameAttr: cn

Troubleshooting

invalid issuer from the hubconfig.issuer in Dex must match idp.issuerURL in the hub byte-for-byte. Same scheme, no trailing slash.

Callback URL mismatchredirectURIs in Dex’s staticClients must be exactly <hubExternalURL>/auth/callback. Mismatched scheme or path is the usual culprit.

TLS errors on the issuer — If Dex’s certificate is signed by a private CA, set idp.caSecretName / idp.caSecretKey (see above). As a last resort for throwaway dev setups, hub.devMode: true skips issuer TLS verification — never in production.

unauthorized_client / secret prompts — The Dex client must be public: true with no secret. kedge does PKCE; a confidential client configuration will fail.

Dex logs:

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

Discovery sanity check:

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