OIDC Authentication
KSail provides native OIDC (OpenID Connect) authentication support. When configured, KSail:
- Injects OIDC flags into the Kubernetes API server at cluster creation time (per-distribution).
- Acts as an exec credential plugin (
ksail cluster oidc get-token) — replacing the need for kubelogin. - Auto-configures kubeconfig with an OIDC user and context after cluster creation.
Prerequisites
Section titled “Prerequisites”- KSail CLI installed
- Docker running
- An OIDC provider (e.g., Dex) — either already deployed or planned for deployment via GitOps
Quick Start
Section titled “Quick Start”-
Initialize a cluster with OIDC
Terminal window ksail project init \--oidc-issuer-url https://dex.example.com \--oidc-client-id kubectl \--oidc-extra-scope email \--oidc-extra-scope groupsOr add OIDC configuration to an existing
ksail.yaml:spec:cluster:oidc:issuerURL: https://dex.example.comclientID: kubectlextraScopes:- email- profile- groups -
Create the cluster
Terminal window ksail cluster createKSail automatically:
- Configures the API server with
--oidc-*flags - Adds an
oidc-<cluster-name>user to your kubeconfig (exec-based, pointing toksail cluster oidc get-token) - Adds an
oidc@<cluster-name>context
- Configures the API server with
-
Deploy your OIDC provider
Deploy your OIDC provider (e.g., Dex) as a workload. Once it’s running, the OIDC context becomes usable:
Terminal window kubectl --context oidc@<cluster-name> get podsOn first use,
ksail cluster oidc get-tokenopens your browser for authentication.
Configuration Reference
Section titled “Configuration Reference”All OIDC settings live under spec.cluster.oidc in ksail.yaml. See the Declarative Configuration reference for the full schema.
| Field | Type | Default | Description |
|---|---|---|---|
issuerURL |
string |
— | OIDC provider issuer URL. Setting this enables OIDC. |
clientID |
string |
— | OIDC client ID for kubectl authentication. |
extraScopes |
[]string |
— | Additional OIDC scopes beyond openid. |
usernameClaim |
string |
email |
JWT claim for Kubernetes username. |
usernamePrefix |
string |
oidc: |
Prefix prepended to OIDC usernames. |
groupsClaim |
string |
groups |
JWT claim for Kubernetes group membership. |
groupsPrefix |
string |
oidc: |
Prefix prepended to OIDC group names. |
caFile |
string |
— | Path to CA certificate for self-signed OIDC providers. |
CLI Flags
Section titled “CLI Flags”OIDC can also be configured via CLI flags on ksail project init, ksail cluster create, and ksail cluster update:
--oidc-issuer-url OIDC provider issuer URL--oidc-client-id OIDC client ID--oidc-extra-scope Additional OIDC scopes (repeatable)--oidc-username-claim JWT claim for Kubernetes username--oidc-username-prefix Prefix for OIDC usernames--oidc-groups-claim JWT claim for Kubernetes groups--oidc-groups-prefix Prefix for OIDC groups--oidc-ca-file Path to CA certificateHow It Works
Section titled “How It Works”API Server Configuration
Section titled “API Server Configuration”KSail injects the following flags into the Kubernetes API server based on spec.cluster.oidc:
--oidc-issuer-url=<issuerURL>--oidc-client-id=<clientID>--oidc-username-claim=<usernameClaim>--oidc-username-prefix=<usernamePrefix>--oidc-groups-claim=<groupsClaim>--oidc-groups-prefix=<groupsPrefix>--oidc-ca-file=<caFile> # only when caFile is setEach distribution handles this differently:
| Distribution | Mechanism |
|---|---|
| Vanilla (Kind) | kubeadm ClusterConfiguration patch with apiServer.extraArgs |
| K3s (K3d) | --kube-apiserver-arg=--oidc-* flags |
| Talos | cluster.apiServer.extraArgs machine config patch |
| VCluster | controlPlane.distro.k8s.apiServer.extraArgs Helm values |
Exec Credential Plugin
Section titled “Exec Credential Plugin”ksail cluster oidc get-token implements the Kubernetes exec credential plugin protocol. The authentication flow:
- Check token cache (
~/.ksail/oidc/cache/) — return if still valid - If expired, attempt token refresh using the stored refresh token
- If no valid token, start authorization code flow with PKCE:
- Start a local HTTP callback server
- Open browser to the OIDC authorize endpoint
- User authenticates (e.g., via GitHub, Google, LDAP — depends on provider configuration)
- Receive callback with authorization code
- Exchange code for tokens
- Cache tokens and output
ExecCredentialJSON to stdout
Kubeconfig Layout
Section titled “Kubeconfig Layout”After ksail cluster create, your kubeconfig contains both admin and OIDC entries:
# Admin user (certificate-based, always works)users:- name: <cluster-name> user: client-certificate-data: ...
# OIDC user (exec-based, requires OIDC provider to be running)- name: oidc-<cluster-name> user: exec: apiVersion: client.authentication.k8s.io/v1 command: ksail args: - cluster - oidc - get-token - --issuer-url=https://dex.example.com - --client-id=kubectl - --extra-scope=email - --extra-scope=groups interactiveMode: IfAvailable
contexts:# Admin context (default)- name: kind-<cluster-name> context: cluster: kind-<cluster-name> user: <cluster-name>
# OIDC context- name: oidc@<cluster-name> context: cluster: kind-<cluster-name> user: oidc-<cluster-name>The admin context remains the default. Switch to the OIDC context explicitly:
kubectl --context oidc@<cluster-name> get pods# orkubectl config use-context oidc@<cluster-name>Chicken-and-Egg: OIDC Provider Deployment
Section titled “Chicken-and-Egg: OIDC Provider Deployment”The API server OIDC flags reference an OIDC provider that is deployed after cluster creation. This is expected:
- The admin context (certificate-based) always works, regardless of OIDC provider state.
- The OIDC context becomes usable once the OIDC provider is running.
- Deploy your OIDC provider via GitOps (
ksail workload apply, Flux, or ArgoCD).
Self-Signed OIDC Providers (Local Development)
Section titled “Self-Signed OIDC Providers (Local Development)”For local development with self-signed certificates (e.g., mkcert):
spec: cluster: oidc: issuerURL: https://dex.platform.lan clientID: kubectl caFile: /path/to/rootCA.pem extraScopes: - email - groupsThe caFile field specifies a host path to the CA certificate. KSail automatically handles mounting it into the cluster:
- Kind/Vanilla: Mounted as a read-only
extraMountinto all nodes at/etc/kubernetes/pki/oidc-ca.crt - K3d/K3s: Mounted as a volume into server nodes at
/etc/kubernetes/pki/oidc-ca.crt - Talos: CA content is embedded via
machine.filesonto the node filesystem at/etc/kubernetes/pki/oidc-ca.crt - VCluster: Mounted via a
hostPathvolume from the host Kubernetes node into the VCluster control plane pod
The CA file path is also passed to ksail cluster oidc get-token via kubeconfig exec args for client-side token verification.
RBAC Integration
Section titled “RBAC Integration”OIDC usernames and groups are available in Kubernetes RBAC. With the default prefixes:
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: oidc-adminroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-adminsubjects: # Bind to a specific OIDC user - kind: User name: "oidc:user@example.com" # Or bind to an OIDC group - kind: Group name: "oidc:platform-admins"Multi-Cluster OIDC (Federation)
Section titled “Multi-Cluster OIDC (Federation)”The OIDC story extends naturally to multiple clusters: authenticate once, then operate across every cluster that trusts the same identity provider — without managing per-cluster credentials.
Federation Model: Shared-Issuer Trust
Section titled “Federation Model: Shared-Issuer Trust”KSail’s federation model is shared-issuer trust. Each cluster is configured independently, but when several clusters point at the same OIDC provider, they all accept tokens from a single identity:
- Every cluster’s API server is configured with the same
--oidc-issuer-urland--oidc-client-id, so each one independently verifies and trusts tokens minted by that provider. - Usernames and groups are derived from the same JWT claims, so a single OIDC identity (and its group memberships) maps to the same Kubernetes subject on every federated cluster — RBAC bindings are portable across the fleet.
There is no central control plane to manage: trust is established declaratively, per cluster, by pointing at a shared issuer.
Single Sign-On Across Clusters
Section titled “Single Sign-On Across Clusters”Because tokens come from one provider, you authenticate once and the session is reused across all federated clusters. This works today with no extra configuration: ksail cluster oidc get-token caches tokens in ~/.ksail/oidc/cache/ keyed by the issuer URL, client ID, and scopes — not by cluster name. The first kubectl call against any federated cluster opens the browser; every subsequent call against any cluster sharing that OIDC configuration reuses the cached token (and silently refreshes it when it expires).
Workflow
Section titled “Workflow”-
Configure every cluster with the same OIDC provider
Use identical
issuerURL,clientID, andextraScopesin each cluster’sksail.yaml:# cluster-a/ksail.yaml and cluster-b/ksail.yamlspec:cluster:oidc:issuerURL: https://dex.example.comclientID: kubectlextraScopes:- email- groups -
Apply portable RBAC
Bind the same OIDC user or group on each cluster (see RBAC Integration). Because the username/group claims resolve identically everywhere, one set of bindings works fleet-wide.
-
Authenticate once, use everywhere
Terminal window kubectl --context oidc@cluster-a get pods # opens the browser on first usekubectl --context oidc@cluster-b get pods # reuses the cached token — no second login
The model so far covers identity federation — a single human identity, authenticated once and authorized independently on each cluster via portable RBAC. The complementary half is cross-cluster service-account projection: letting a workload in one cluster present a verifiable identity that another cluster (or an external service) trusts, without per-cluster static credentials.
Cross-Cluster Service-Account Projection
Section titled “Cross-Cluster Service-Account Projection”Because KSail delegates here, no new ksail.yaml field is required — the existing OIDC configuration plus each distribution’s service-account issuer already supply the trust anchors. Pick the mechanism that matches your trust requirements:
Option A — Projected service-account tokens (lightweight, no mesh)
Section titled “Option A — Projected service-account tokens (lightweight, no mesh)”This extends the shared-issuer model from user tokens to workload tokens. Every cluster’s API server is itself an OIDC issuer for the service-account tokens it mints (--service-account-issuer + a discoverable JWKS), so the same “verify against a trusted issuer” pattern applies:
-
Project an audience-bound token in the source cluster
Mount a projected service-account token whose
audiencenames the receiving party. The token is a short-lived JWT signed by the source cluster’s service-account issuer, with claims identifying the namespace and service account:volumes:- name: federated-tokenprojected:sources:- serviceAccountToken:audience: https://api.cluster-b.example.com # the receiverexpirationSeconds: 3600path: token -
Trust the source cluster’s issuer at the receiver
Configure the receiving cluster (or external service) to validate tokens against the source cluster’s service-account OIDC discovery document (
<issuer>/.well-known/openid-configuration→ JWKS). On the receiver this is the same API-server OIDC configuration KSail already understands, pointed at the source issuer; on a non-Kubernetes verifier it is standard OIDC discovery. -
Authorize the projected identity
Bind RBAC (or the external service’s policy) to the token’s subject —
system:serviceaccount:<namespace>:<name>— exactly as with portable RBAC.
The source cluster’s issuer URL must be resolvable by the verifier (a stable, externally reachable discovery endpoint), and audiences should be receiver-specific so a token cannot be replayed against an unintended cluster.
Option B — SPIFFE/SPIRE federation (mesh-grade, mutual)
Section titled “Option B — SPIFFE/SPIRE federation (mesh-grade, mutual)”For mutual workload authentication across a fleet, use SPIFFE/SPIRE. Each cluster runs a SPIRE server owning a trust domain; clusters federate by exchanging trust bundles over the SPIFFE Federation endpoint API, after which a workload’s SVID — spiffe://<trust-domain>/ns/<namespace>/sa/<service-account> — is verifiable in every federated trust domain.
Choosing between them
Section titled “Choosing between them”| Option A — Projected tokens | Option B — SPIFFE/SPIRE | |
|---|---|---|
| Trust direction | One-way (workload → receiver) | Mutual (mTLS) |
| Extra infrastructure | None (uses the cluster’s SA issuer) | SPIRE servers + federation (may already exist via Cilium) |
| Best for | A workload calling a specific cluster/service | Service-to-service mesh across the fleet |
Both keep the no-central-control-plane property: trust is declared per cluster against a shared/federated issuer. If a future need arises for KSail to manage this trust declaratively (e.g. a spec.cluster.federation surface), that can be added incrementally on top of these mechanisms — see the OIDC federation roadmap issue.
Related
Section titled “Related”- Declarative Configuration — full
ksail.yamlschema reference - Secret Management — encrypting secrets with SOPS
- Kubernetes OIDC documentation — upstream reference