k8wiz
Back to Articles

Ready to test your cert-manager knowledge? Try our cert-manager quiz!

What cert-manager 1.21.3 and 1.20.5 change after the eMudhra CA compromise

How the October 2026 cert-manager and trust-manager patches remove trust in the compromised eMudhra CA, what else they fix and how to upgrade.

9 min read
By k8wiz Team
  • cert-manager
  • trust-manager
  • security
  • upgrade

In late October 2026 the cert-manager project shipped three security releases within a week. cert-manager v1.21.3 came out on October 23, trust-manager v0.26.0 on October 27 and cert-manager v1.20.5 on October 28. All three exist mainly for the same reason. An eMudhra CA certificate had signed intermediate certificates whose private keys were trivially recoverable, which led to an emergency distrust of that CA.

The release notes for both cert-manager versions end with the same line, "All users should upgrade." This article explains what each release changes, which parts of your cluster are affected, and how to upgrade without surprises. The v1.21.3 release also adds a new field to the Certificate CRD, which matters if you upgrade the controller and forget the CRDs.

Why a CA compromise reaches your cert-manager pods

cert-manager doesn't only issue certificates. Its controller is also a TLS client. It talks to your ACME directory, to Vault, to cloud DNS APIs for DNS-01 challenges, and it checks the certificates those servers present. Like most Go programs it validates them against the CA certificates baked into its container image.

That means the trust store inside the cert-manager images is part of your security boundary. If a root CA in that store is compromised, someone holding a key for one of its intermediates could impersonate any server the controller talks to. The fix has to happen in the image itself, because you can't change the trust store of a running distroless container.

The same logic applies one level up. Many clusters use trust-manager to build a CA bundle and distribute it to application pods as a ConfigMap or Secret. If that bundle includes the public CAs, every workload consuming it trusted the eMudhra root too.

What changes in cert-manager v1.21.3

The headline change is the trust store. The updated distroless base images no longer include the "emSign ECC Root CA - G3" root certificate. That is the eMudhra root covered by the distrust.

The release also moves to Go 1.26.10. That Go version makes crypto/x509 reject certificate chains that pass through the compromised intermediates. This is a second layer. Even if some other source of trust put the root back in front of the controller, chains through those specific intermediates would still fail verification.

Three other fixes ride along, and two of them are worth reading carefully.

The first concerns ACME Renewal Information (ARI, RFC 9773). With the alpha ACMEUseARI feature gate enabled, cert-manager asks the CA for a suggested renewal window and stores it in status.acme.ari on the Certificate. Before this release, that stored window wasn't tied to a specific certificate. After a reissue, the controller could keep acting on the window that belonged to the previous certificate, and the release notes describe it reissuing repeatedly until it hit the CA rate limit. v1.21.3 adds status.acme.ari.certID, so the controller knows which certificate the window belongs to and fetches fresh renewal information after each issuance.

The second fix closes a private key handling gap. During issuance, the keymanager controller stores a new private key in a temporary Secret and records its name in status.nextPrivateKeySecretName. Previously, other controllers would trust whatever Secret that field named. Now the requestmanager and issuing controllers only use that Secret if it carries the cert-manager.io/next-private-key label and is owned by the Certificate. The keymanager always creates its own Secret with a generated name, and it records a NotOwned Warning event when the field points at a Secret it doesn't own.

The third fix is in the Helm chart. The http_proxy, https_proxy and no_proxy values are now quoted when the chart renders the controller's environment variables. Before, a no_proxy value containing a wildcard such as *.example.com produced a broken manifest.

What changes in cert-manager v1.20.5

If you are still on the 1.20 line, v1.20.5 gives you the same trust fix in a different way. Its base images move to Debian 13, which drops both the "emSign ECC Root CA - G3" and "AAA Certificate Services" roots. It also builds with Go 1.26.10, so the intermediate check is the same as in v1.21.3.

The rest of v1.20.5 is dependency work. It updates golang.org/x/net and golang.org/x/crypto to clear vulnerabilities that govulncheck reported in v1.20.4, and bumps go-jose/v4 to v4.1.6 to fix a denial of service when parsing RSA JSON Web Keys. The ARI and private key ownership fixes from v1.21.3 are not part of this release.

One change only matters if you build cert-manager yourself or import it as a Go module. The go directive in its go.mod files is now 1.26, so you need Go 1.26 to build 1.20 from source. The released binaries were already built with Go 1.26, and the release notes say they keep the same GODEBUG defaults as v1.20.4.

Step 1: check what you are running

Start by finding the cert-manager version in each cluster. The image tag on the controller Deployment is the most direct answer.

kubectl get deployments -n cert-manager \
  -o custom-columns='NAME:.metadata.name,IMAGE:.spec.template.spec.containers[0].image'

Anything below v1.21.3 on the 1.21 line, or below v1.20.5 on the 1.20 line, still trusts the eMudhra root. If you run an older minor version, the upgrade guidance from the project is to move one minor version at a time, choosing the latest patch of each.

If you also run trust-manager, check its default CA package. Every Bundle that uses the default CAs reports the package version in its status.

kubectl get bundles.trust.cert-manager.io \
  -o custom-columns='NAME:.metadata.name,DEFAULT_CA_VERSION:.status.defaultCAVersion'

Step 2: upgrade the CRDs before the controller

This step is easy to skip on a patch release, and v1.21.3 is one where skipping it bites. The new status.acme.ari.certID field only exists in the v1.21.3 CRDs.

With old CRDs and default settings, the API server prunes the unknown field and the controller degrades safely. With the alpha ServerSideApply feature gate turned on, status updates for ARI-enabled Certificates fail until the CRDs are upgraded. Upgrading CRDs first avoids both cases.

If you installed the CRDs with the Helm chart (--set crds.enabled=true), Helm upgrades them along with the chart and you can go straight to the next step. If you manage them separately, apply the CRDs for the target version first.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.3/cert-manager.crds.yaml

Step 3: upgrade cert-manager with Helm

The project publishes its charts as OCI artifacts on quay.io, and the upgrade docs use that registry. The --reset-then-reuse-values flag keeps the values you set at install time while picking up new chart defaults.

helm upgrade --reset-then-reuse-values --version v1.21.3 \
  cert-manager oci://quay.io/jetstack/charts/cert-manager \
  --namespace cert-manager

For the 1.20 line, swap the version for v1.20.5 (and use the matching CRD URL in step 2 if you manage CRDs separately). The legacy https://charts.jetstack.io repository still works, but the docs note that it gets new releases a few hours after the OCI registry.

Once the rollout finishes, check that the three Deployments run the new tag and that Certificates still report ready.

kubectl rollout status deployment/cert-manager -n cert-manager
kubectl get certificates --all-namespaces

If a Certificate that renews through an ACME server, Vault or a DNS provider starts failing after the upgrade with a certificate verification error, look at that server's chain. With the eMudhra root gone, a server whose certificate chains to it is no longer trusted by the controller. That is the intended result, and the fix belongs on the server side.

Step 4: patch trust-manager's default CA package

Upgrading cert-manager protects the controller. It does nothing for application pods that read a trust-manager Bundle built with useDefaultCAs: true. Those pods trust whatever the default CA package contains.

The trust-manager v0.25.0 chart installs trust-pkg-debian-trixie:20250419.2 by default, which still contains the compromised roots. The v0.26.0 chart installs trust-pkg-debian-trixie:20250419-deb13u1.1, which doesn't. The cleanest fix is to upgrade trust-manager.

helm upgrade --reset-then-reuse-values --version v0.26.0 \
  trust-manager oci://quay.io/jetstack/charts/trust-manager \
  --namespace cert-manager

If you can't upgrade trust-manager yet, you can update only the package image. The fixed tags are 20250419-deb13u1.0 or newer for Trixie and 20250419-deb12u2.0 or newer for Bookworm. Don't be confused by the 2025 date in the tag. It comes from Debian's ca-certificates package version. Dump your current values first, so you don't lose anything set at install time.

helm get values -n cert-manager trust-manager -o yaml > values.yaml

Then add or update the package image in values.yaml. The quotes around the tag matter, because an unquoted tag can be read as something other than a string.

defaultPackageImage:
  repository: quay.io/jetstack/trust-pkg-debian-trixie
  tag: "20250419-deb13u1.0"

Apply it while pinning the trust-manager version you already run, so you only change the package and not the controller.

TRUST_MANAGER_VER=$(helm list --filter "^trust-manager$" -n cert-manager -o json | jq -r ".[0].app_version")
echo "$TRUST_MANAGER_VER"
helm upgrade -f values.yaml -n cert-manager trust-manager \
  oci://quay.io/jetstack/charts/trust-manager --version "$TRUST_MANAGER_VER"

Run the Bundle status command from step 1 again afterwards. The reported defaultCAVersion should change once trust-manager has resynced the bundles. If you still run the old Bullseye-based cert-manager-package-debian image, note that it is deprecated and won't be patched, so move to Trixie.

What these releases don't cover

The cert-manager project can fix its own images and the bundles trust-manager builds. It can't fix the trust store inside your application images. A container that ships its own ca-certificates package keeps trusting the eMudhra root until you rebuild it on an updated base image.

trust-manager's docs suggest pointing such pods at a patched trust-manager bundle instead of their built-in store. That is more work up front, but the next time a CA is distrusted you update one package image instead of rebuilding every service. If this incident had you counting how many images you would need to rebuild, that is a good argument for making the move.

Test yourself

Ready to test your cert-manager knowledge? Try our cert-manager quiz!