Skip to main content

Pod Certificates Are GA in Kubernetes 1.37: Per-Tenant TLS Without the cert-manager Glue

9 min readDora NodaDora Noda
Share
On this page

Every time your workload authenticates with its service account token, it hands a copy of its entire identity to the peer on the other side of the connection. That peer can now be you — to anyone, until the token expires. Time, audience, and object bindings narrow the window, but the shape of the problem never changes: bearer tokens are photocopies of your ID that you leave at every door you knock on.

Kubernetes 1.37 "Garhwal," released August 26, 2026, graduates the fix to stable: Pod Certificates and Cluster Trust Bundles, covered in depth in the August 28 Kubernetes blog post by SIG Auth's Taahir Ahmed. Together they build X.509 certificate issuance for TLS and mTLS directly into core Kubernetes — proof-of-possession identity where the private key is generated on the node and never leaves it. For a self-hosted PaaS issuing per-tenant TLS, this is the moment workload identity stops being glue you maintain and starts being an API you consume. Here is the mechanism, the before-and-after against cert-manager, and the four-item adoption checklist.

How it works: from pod spec to mTLS in six steps

A workload opts in with two projected volume sources in its pod spec — podCertificate for its own identity and clusterTrustBundle for the trust anchors it verifies peers against:

yaml
volumes:
  - name: workload-cert
    projected:
      sources:
        - podCertificate:
            signerName: example.com/my-signer
            keyType: Ed25519
  - name: trust-anchors
    projected:
      sources:
        - clusterTrustBundle:
            signerName: example.com/my-signer

From there the issuance flow runs chronologically:

  1. Pod lands on a node. Kubelet scans the pod spec for podCertificate and clusterTrustBundle sources.
  2. Per certificate source, kubelet generates a fresh private key per keyType, then creates a PodCertificateRequest addressed to the named signer. The signer controller decides whether to issue, fills in status.certificateChain, and sets status.beginRefreshAt to tell kubelet when to start refreshing. Kubelet writes the key and chain to the container filesystem.
  3. Per trust-bundle source, kubelet collects every ClusterTrustBundle matching the signer name and label selector, unifies their certificates, reorders them stably (so apps can't accidentally depend on ordering), and writes the result to the named path.
  4. The app starts and reads keys, certificates, and trust anchors as plain files.
  5. Kubelet keeps the files current as the selected trust bundles change — the app picks updates up with inotify or polling.
  6. Past beginRefreshAt, kubelet repeats step 2 and rewrites the key and chain. Rotation is built in; handling it is the application's job.

Three design details matter for operators. First, kubelet can write the private key and certificate chain to a single file (a credential bundle), so the app subscribes to one path instead of carefully avoiding read-mid-rotation races across two files. Second, security checks live in the apiserver, not in each signer: the node-restriction admission plugin enforces node isolation, so a compromised node cannot request certificates for pods that aren't scheduled to it. Third, lifetimes are short by policy — any signer eventually shipped in core Kubernetes will cap certificates at 24 hours, and third-party signers at 91 days. This is short-lived identity, not a cert you install once and forget.

Before and after: Certificate sprawl vs. an API you consume

The cert-manager way of doing per-service TLS works, but every platform team knows its shape: a Certificate custom resource per service, each producing a Secret holding tls.key and tls.crt, each Secret mounted into pods that must somehow learn when renewal rewrites it. Trust distribution is a separate DIY project — typically a controller cloning CA data into every namespace as a ConfigMap, or CA certificates baked into base images. It all functions. It is all glue.

Concerncert-manager glue (today)Pod Certificates + CTB (1.37 GA)
Identity per workloadCertificate CR per service → Secret sprawl grows with service countpodCertificate volume source; kubelet mints a key and requests issuance per pod
Secret-distribution raceSecret must exist and be populated before the pod mounts it; renewal rewrites need app reload wiringKey and chain are written to the container filesystem before the app starts; refresh rewrites the same paths
Trust anchorsDIY: cloned ConfigMaps, baked-in CA bundles, or trust-managerClusterTrustBundle: cluster-scoped, signer-published, mounted as one auto-updated file
RotationrotationPolicy + renewal window per Certificate; app must detect Secret changebeginRefreshAt-driven refresh managed by kubelet; app watches one file (credential bundle)
Blast radius of a nodeSecret contents readable by anyone with Secret accessNode-restriction admission: a node can only request certs for its own pods
Trust modelBearer-style Secret handling throughoutProof-of-possession: private key generated on-node, never transmitted

The deepest row in that table is the last one. A service account JWT or a Secret-mounted key is useful to anyone who copies it; a Pod Certificate's private key never leaves the node, so a stolen certificate chain without the key proves nothing. That is the property that makes mTLS between tenant workloads actually mean something, and it is the property no amount of rotationPolicy tuning can retrofit onto bearer credentials.

The adoption checklist: four things to verify before you delete the glue

GA means the API is stable. It does not mean you can delete cert-manager on Monday. Four checks stand between "stable API" and "workload identity as an API we consume."

1. Version floor: apiserver AND kubelet on 1.37. Pod Certificates are a kubelet-driven feature — the kubelet generates keys, creates PodCertificateRequest objects, and writes the files. An upgraded apiserver with old kubelets gives you the API types without the machinery that fills them.

The KEP's own skew notes call this out explicitly: a pod requesting a podCertificate volume that lands on a kubelet that doesn't understand it will simply start without any certificates mounted. For a Cluster-API-managed fleet, that means the node-image rollout is part of the adoption, not an afterthought — every node pool needs the 1.37 kubelet before any tenant workload can rely on the volume source existing.

2. Signer choice: Kubernetes ships the mailbox, not the post office. The GA announcement comes with a conspicuous absence: no signer ships in core Kubernetes yet. A PodCertificateRequest addressed to a signer nobody implements sits unanswered.

Today you install a third-party signer — the reference implementation is Tinycert by the feature's author, which demonstrates both a service-DNS-SANs signer and a SPIFFE-compatible signer — or you write your own controller against the documented signer contract: watch requests, verify, fill status.certificateChain and status.beginRefreshAt, and publish your CA as a ClusterTrustBundle. Budget for the signer as a platform component with its own CA rotation story, not as a YAML snippet.

3. Rotation behavior: short lifetimes only work if apps watch the file. The 24-hour core cap and 91-day third-party cap are ceilings, not suggestions — signers are expected to issue short-lived certificates and drive refresh through beginRefreshAt. Your side of the contract is making sure tenant workloads actually pick up rotations: document the credential-bundle single-file pattern and provide an inotify/poll-and-reload helper for the frameworks your tenants use.

Workloads that read the cert once at startup and never again will break at first refresh — deterministically, a few hours after deploy. A platform offering Pod Certificates as default identity needs a conformance story here, even a simple "your container must reload TLS state on file change" contract test, or the support queue will discover the gap tenant by tenant.

4. SPIFFE compatibility: the agent-workload question. If your roadmap includes AI-agent sandboxes that call each other (and everything else) over mTLS, identity format matters beyond one cluster. Tinycert already ships a SPIFFE-compatible signer issuing namespace-and-service-account identities usable as client and server certificates, alongside a Go helper for the SPIFFE Filesystem Delivery draft standard.

The honest comparison is layers: Pod Certificates and SPIFFE/SPIRE both deliver short-lived X.509 with rotation and trust distribution, but SPIFFE is a cross-platform identity framework with federation, attestation plugins, and a workload API, while Pod Certificates are a Kubernetes-native issuance API. For sandboxes that never leave your fleet, a SPIFFE-compatible signer on Pod Certificates may be the whole solution. For identity that must federate past the cluster boundary, plan for SPIRE with Pod Certificates as one issuance path, not the entire identity plane.

What GA doesn't mean (yet)

Three expectations to calibrate before the migration doc gets written:

  • Edge and public TLS still belong to cert-manager. Pod Certificates identify workloads to each other; they don't prove domain control to Let's Encrypt. Nothing in 1.37 changes the DNS-01/HTTP-01 pipeline that puts a trusted cert on a tenant subdomain.
  • Trust-bundle updates are poll-based. Kubelet rewrites the file, but nothing pushes a signal into your process — inotify or polling is load-bearing, not optional.
  • The signer ecosystem is day one. Tinycert is a reference, not a production CA. Production signers with HSM backing, multi-replica HA, and audited issuance policy are work the ecosystem (and possibly your platform team) still has to do.

Keep cert-manager for the edge, adopt Pod Certificates for east-west identity, and revisit the split as in-core signers land.

What this unlocks for a self-hosted PaaS

Per-tenant mTLS stops being a per-service craft project. Instead of N Certificate resources, N Secrets, a trust-distribution controller, and a reload-convention wiki page, the platform offers two volume sources and a signer: every tenant workload gets a short-lived, auto-rotated identity and a current trust bundle by mounting two paths. The marginal cost of "this service speaks mTLS" drops from a ticket queue to a YAML stanza — which is exactly the cost curve a multi-tenant platform needs before it can make encrypted, authenticated east-west traffic the default rather than the upsell.

The longer arc is agent workloads. Sandboxes that spawn, call tools, and die within minutes are the worst case for bearer tokens (more copies, more doors) and the best case for short-lived proof-of-possession identity minted at schedule time. A fleet where every sandbox arrives with a SPIFFE-compatible certificate and a fresh trust bundle is a fleet where agent-to-agent mTLS is a mount, not a project.

Kubernetes 1.37 turns workload identity from something your platform builds into something it configures. The API is stable, the rotation semantics are sound, and the remaining work — signers, app-side reload discipline, SPIFFE alignment — is enumerated, not open-ended. That is what a real GA looks like: not "everything is done," but "everything left has a name and an owner."

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex