Evaluating the multitudes of cert-manager-webhook-desec

In June 2026 I proposed to create a deSEC-community GitHub org (which I went ahead and did, even though it’s just a placeholder for now) for maintaining community-driven DNS providers of various kinds (cert-manager, external-dns, terraform, opentofu, etc.).

My original motivation was that my fork of external-dns-desec-provider is ahead by around 5 bugfixes that aren’t being merged by the upstream maintainer, and I would like for people to discover the fixed version.

The time has come for me to bootstrap another Kubernetes cluster, and looking for the counterpart service, cert-manager-webhook-desec, I see that three independent projects exist that are not forks of one another:

  • kmorning’s (16 stars, 12 forks, ahead by 32 commits, behind by 124 commits, untouched 4 years, 1 stale pull request 4 years ago)
  • pr0ton11’s (13 stars, 0 forks, updated 2 months ago)
  • Luzifer’s (1 star, 1 fork, untouched 2 years)

12 forks” is a good sign: It means people find this and fork it to either bookmark, vendor, or try to fix it. A stale pull request from 2022 saying “Compatibility with kubernetes 1.26” is not a good sign: We’re at 1.36 now, and it makes me wonder if it works at all.

updated 2 months ago” is also a good sign. pr0ton11’s webhook is titled “An independently maintained deSEC DNS validation webhook for cert-manager” which indicates that they consciously went and made their own, which is a strong signal that kmorning’s is out-of-date and needs some love. Unfortunately, I rather found this webhook by accident. It’s named the same as the 12 forks, but it isn’t a fork.

Before I evaluate pr0ton11’s webhook, I will just quickly go over what the forks of kmorning’s are trying to solve.

kmorning’s webhook (most maintained fork: BlackDark’s)

Of kmorning’s forks, 5 are identical, 3 are behind, and 4 are ahead:

  • j-be’s fork is ahead by 74 commits
  • justuspfaue’s is ahead by 4 commits
  • 2143-Labs’s is ahead by 3 commits
  • grollinger’s is ahead by 9 commits:

From these there are second-order forks:

  • BlackDark’s fork is ahead of grollinger’s by 33 commits

Investigating what these forks try to solve:

  • kmorning’s code doesn’t build.
  • justuspfaue wanted an arm64 image
  • grollinger’s gets the test suite running again (incidentally, only on a feature branch, and ironically, BlackDark’s fork which builds on this accidentally disables the tests again)
  • 2143-Labs bumped versions, did GHCR releases, dropped arm64 (to avoid QEMU)
  • j-be rebases main so messes up the commit history, rewrites the webhook to make it testable, adds a distroless image, a kubeaudit-derived security context, dependabot, and some `duration: 43800h0m0s` hotfix to avoid ArgoCD perpetually reporting drift, and silently downgrades some dependencies. A lot of effort, but very messy.
  • BlackDark’s actually fixes bugs in the DNS logic:
    • deSEC’s bulk PUT /domains//rrsets/ returns 204 with an empty body, but request() unconditionally runs json.NewDecoder(resp.Body).Decode(target). So every successful write returns a response parsing error: EOF. both Present and CleanUp fail on writes that actually worked.
    • upstream’s subName := fqdn[:len(fqdn)-len(domain.Name)-1] panics on an apex-domain challenge (fqdn[:-1]) and silently corrupts the subname when the suffix isn’t on a label boundary. Replaced with a getSubName that errors explicitly.
    • loadConfig returned an empty config with nil error when the solver config was missing.
    • splits the live-API test behind //go:build integration from real table-driven unit tests
    • moves to FROM scratch + USER 65532 with --secure-port=8443
    • replaces QEMU with native Go cross-compilation
    • fixes the RBAC template: upstream’s resourceNames: {{ .Values.secretsNames }} with secretsNames undefined granted get/watch/list on every Secret in the cert-manager namespace.

Doing a very loose security audit, I incidentally uncovered 5 bugs of which one could have security implications (query string without escaping). So there’s a lot of work ahead of using even the best of these forks (BlackDark’s).


pr0ton11’s webhook

Since this webhook is more promising, I’d like to do a more thorough review of it.

But as part of that review, I’d also like to see if it comes short of kmorning’s in any way.

I’ll update this thread with that review, but will post now so I don’t lose what I’ve written so far.

The good parts

  • It doesn’t hand-roll the API client, which eliminates 3 bugs compared to kmorning’s.
  • Respects HTTP 429s from API (ahem, my external-dns webhook fork doesn’t do that yet)
  • Correctly handles multi-value RRSets
  • Is actively maintained

The bad parts

  • Its CI doesn’t run any tests besides `helm lint`.
  • Pins an old nrdcg/desec (v0.11.2) so it lacks the subname,omitempty → subname fix
  • GitHub Actions use floating tags instead of hash-pins (supply-chain security vector)
  • Doesn’t have recent, versioned tags (only latest)
  • Distroless image runs as root
  • Dockerfile lacks caching
  • (Arguable) pr0ton11 can be misread
  • Has a string-replacement bug messing up domains where the TLD occurs twice in the FQDN; since it lacks a test suite, one would have to wire that up to even confirm.
  • Helm chart leaves replicaCount undefined
  • Fetches domain.MinimumTTL and discards it in favor of a hardcoded 3600.

So it probably works!