Access & identity

The kubectl client secret CNCF wants you to stop distributing

The kubectl client secret CNCF wants you to stop distributing

You know that OIDC "client secret" your platform team pastes into everyone's kubelogin config on onboarding day? The one nobody has rotated because rotating it means chasing every engineer's laptop? A new CNCF blog post has a name for that: not a secret. Just a shared static credential with more steps. The recommendation, phrased as gently as a project blog can phrase it, is to stop pretending and switch the kubectl OIDC integration to a public client with PKCE instead.

If you run a managed Kubernetes offering, this probably reads as obvious. If you run on-prem, it might sting a little.

The gap the post is filling

Managed cloud Kubernetes ships some form of IAM or SSO out of the box. Self-hosted clusters do not. The default on most on-prem installs is a static client certificate or a long-lived token, "issued once and rarely revisited," as the post puts it. Somebody eventually wires up an identity provider, an admin creates a client in Keycloak or an OIDC-compliant equivalent, and kubelogin gets bolted onto kubectl via the exec plugin. That much is normal.

The unusual claim is the shape of the client. Most guides walk you through creating a confidential client, because that is the default in a lot of IdPs. Confidential means "has a secret." Which sounds sturdier. It is the opposite.

Why a shared secret on N laptops is not a secret

The confidential model was designed for server-to-server flows. One backend, one secret, one place to rotate it. Fine.

kubectl is not that. kubectl runs on your machine, and your colleague's machine, and the machine your intern set up last Tuesday. If you want the OIDC handshake to work from any of them, the same "client secret" has to be present on all of them. The post's line is worth repeating verbatim: "A secret that has to be distributed to every client that uses it isn't functioning as a secret." Once it lives in dotfiles on dozens of laptops, rotating it means a coordinated push to all of them, which is why nobody rotates it.

PKCE, in one paragraph

The alternative the post advocates is a public client (no secret at all) with PKCE, the Proof Key for Code Exchange extension. Mechanically: the client generates a random value locally, sends a hash of it up front, and later proves possession of the original. If an attacker intercepts the authorization code mid-flight, they cannot redeem it without the original value they never saw. That is the specific attack a client secret was meant to block in a browser-less flow, and PKCE blocks it without the secret. So the secret has no job left to do.

Wiring it up

The post's concrete recipe is short. In Keycloak: turn client authentication off, require PKCE with method S256, restrict valid redirect URIs to loopback only (http://127.0.0.1:* and http://localhost:*), and hand out the standard OIDC scopes (openid, profile, email, groups). On the cluster, the API server needs the usual flags pointed at the IdP:

--oidc-issuer-url=https://<keycloak-host>/realms/<realm>
--oidc-client-id=<your-client-id>
--oidc-username-claim=preferred_username
--oidc-groups-claim=groups
--oidc-ca-file=/etc/kubernetes/pki/oidc-ca.crt

On the laptop side, kubectl calls the kubelogin exec plugin, which opens a loopback listener, drives the auth code flow through the browser, and hands back a token. No secret in ~/.kube/config to leak, screenshot, or forget to rotate.

Where this bites you

Two footguns worth flagging. First, the --oidc-ca-file bit is easy to skip when your Keycloak has a certificate the API server does not already trust. The post is polite about it; the reality is that OIDC authentication just fails, and the failure mode is, in its words, "a confusing one to debug." Turn it on before you turn users on. Second, group-based RBAC only works if the groups claim actually lands in the token. Configure the scope in the client and verify it round-trips end to end. Otherwise you have shipped a "single sign-on" experience that binds every human to the same anonymous identity.

The upside makes the plumbing worth it. Access follows group membership, not certificate files. Every audited API call carries the person who made it, not a shared robot. The kubeconfig you distribute becomes boring, which is what a kubeconfig is supposed to be.

The verdict

A public client with PKCE is the honest name for what your kubectl integration already is, whether you admit it or not. The post is essentially asking on-prem operators to stop cargo-culting a confidential-client setup from a tutorial that assumed a web app. Do the swap once. Rotate nothing forever.

Source: CNCF (cncf.io)

Related
Identity and access

Anthropic swaps per-developer Claude Code secrets for an OIDC gateway

Anthropic shipped a self-hosted Claude apps gateway that runs Claude Code on Amazon Bedrock and Google Cloud without per-developer cloud credentials, federating identity through Google Workspace, Microsoft Entra ID, Okta or any standards-compliant OIDC provider.

July 5, 2026
Kubernetes

Kubernetes 1.37 turns Storage Version Migration on by default

Storage Version Migration graduated to GA in Kubernetes v1.37, and the built-in StorageVersionMigration API and controller are enabled on every v1.37 cluster. Rewriting stored objects to a new schema version is now a first-class control loop that ships with core.

September 8, 2026
Security & supply chain

Rootless Kubernetes finally reaches beta, eight years after the first experiment

Kubernetes v1.37 promotes the KubeletInUserNamespace feature gate to beta. Kubelet, the container runtime, CNI plugins and kube-proxy can now all run as an unprivileged user on the host.

September 8, 2026

Turn this into your pipeline. Build it on Buddy.

Start free