The kubectl client secret CNCF wants you to stop distributing
Tomás Vega
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)