# 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.

Published: 2026-11-12
Author: k8wiz Team
URL: https://k8wiz.com/articles/cert-manager-1-21-3-emudhra-ca-distrust-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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```yaml
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.

```bash
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.
