Skip to content

ADR 0002: build on issuer-lib and pin it

  • Status: accepted

Context

An external issuer has to reconcile CertificateRequest resources correctly before it does anything with CMP. That means honoring cert-manager approval, initializing and patching status conditions with server-side apply, classifying failures into permanent and retryable, requeueing with backoff, emitting events, and doing the same again for a cluster-scoped issuer type. Every one of those is a place to be subtly wrong in ways that only show up under load or during upgrades.

github.com/cert-manager/issuer-lib implements exactly that layer, and it is maintained by the cert-manager project itself. Its README states that the API is still subject to change and advises using it for experimentation, so building on it is a deliberate trade rather than a default.

Decision

Use issuer-lib for the reconciliation layer and pin it to an exact version, currently v0.12.0. CMPIssuer and CMPClusterIssuer implement its Issuer interface, and enrollment is reached through its Sign and Check callbacks. The Kubernetes CertificateSigningRequest controller it also offers is deliberately disabled.

Unlike the CMP library in ADR 0001, issuer-lib is not isolated behind project-owned interfaces. Its types appear directly in the signer contract: the Sign and Check signatures, PermanentError, PendingError and IssuerError, CertificateRequestObject, and the Issuer interface our API types implement. Wrapping all of that would reimplement the library's contract in order to hide it, which buys nothing.

The CMP protocol layer carries none of it. internal/protocol has no issuer-lib import, so the part of this project that is worth reusing elsewhere is not bound to the reconciliation layer.

Consequences

An upgrade is a code change, not a version bump. Treat every issuer-lib upgrade as a behavioral change: review the changelog for contract changes, run the full check suite, and complete one live enrollment against each tested CMP server before accepting it.

Defects in the reconciliation layer are ours to detect and report. One is already known. In v0.12.0 the CertificateRequest helper decides IsDenied by looking for a Ready=False condition with reason Denied, rather than for cert-manager's Denied condition type, so a request that an approver has denied and nothing else has touched is treated as neither approved nor denied and is ignored. The library never records the terminal condition it intends to record for that request. cert-manager's own issuing controller acts on the Denied condition directly, so the Certificate still fails promptly and no CMP message is ever sent, which is why this is a reporting gap rather than a correctness or safety problem. Troubleshooting describes what an operator sees.

The alternative, reconciling CertificateRequest resources by hand, was rejected. It moves the approval gate, the status patching and the retry classification into this project, where they would get less review than they do upstream, and it would not have prevented the defect above from existing somewhere.