Skip to content

externalcas: make the certificate obtain timeout configurable - #57

Open
csgriggs wants to merge 2 commits into
esnet:mainfrom
csgriggs:fix/configurable-cert-obtain-timeout
Open

externalcas: make the certificate obtain timeout configurable#57
csgriggs wants to merge 2 commits into
esnet:mainfrom
csgriggs:fix/configurable-cert-obtain-timeout

Conversation

@csgriggs

@csgriggs csgriggs commented Sep 2, 2026

Copy link
Copy Markdown

Fixes #56. Follow-up to #22, which fixed the downstream half of the same root cause.

Problem

createLegoClient in externalcas/external.go never sets Certificate.Timeout, so the wait for the upstream CA to issue the certificate after /finalize is capped at lego's 30s default — even though RequestTimeout() already allows 2 minutes for the whole request. CAs whose post-finalization checks exceed 30s (CertiNext/InCommon has confirmed theirs can) make async finalization fail with certificate: time limit exceeded while the CA still issues the certificate, so every client retry mints another uncollected cert.

Change

  • externalcas/config.go: new optional cert_obtain_timeout (seconds) on authority.config, with an ObtainTimeout() time.Duration accessor. Default is 30s, so existing deployments are unchanged. Validate() rejects negative values and values at or above RequestTimeout(), so the outer context still bounds the request.
  • externalcas/external.go: clientConfig.Certificate.Timeout = cfg.ObtainTimeout().
  • externalcas/config_test.go: cases for the default, a configured value, and both validation errors.
  • README.md: documents the field in the config table.

Example:

"authority": {
  "type": "externalcas",
  "config": {
    "ca_url": "https://acme-us.certinext.io/v1/directory",
    "cert_obtain_timeout": 90,
    ...
  }
}

Testing

gofmt -l, go vet ./externalcas/, and go test ./... are clean. We'll be running this against CertiNext production and can report back on the observed finalize-to-issue times.

lego's Certificate.Timeout was never set, so the wait for the upstream CA to
issue a certificate after finalization was capped at lego's 30s default even
though RequestTimeout already allows 2 minutes. CAs whose post-finalization
checks exceed 30s (CertiNext/InCommon does) made async finalization fail with
"certificate: time limit exceeded" while the CA still issued the certificate,
so every client retry minted another uncollected cert.

Add cert_obtain_timeout (seconds, default 30 to preserve current behavior),
validated to be non-negative and below the request timeout, and wire it into
the lego client config. Follow-up to esnet#22, which fixed the downstream half.
cert_obtain_timeout is validated to stay below RequestTimeout(), which was
hardcoded to 2 minutes, so the lego wait could never exceed ~110s. Add
request_timeout (seconds, default 120 — unchanged behavior) so operators can
raise both together, e.g. cert_obtain_timeout=300 with request_timeout=330.

Motivation: CertiNext support has identified a CT log operator delaying
issuance of their 30-day product and asks proxies to wait 300 seconds for
finalization until it is resolved.
@csgriggs

csgriggs commented Sep 2, 2026

Copy link
Copy Markdown
Author

Pushed a second commit adding request_timeout (seconds, default 120 — unchanged behavior). The existing check that cert_obtain_timeout stays below the outer request context meant the lego wait could never exceed ~110 s; with both knobs an operator can set e.g. cert_obtain_timeout: 300, request_timeout: 330.

Why 300: CertiNext support told us the delay is on their side — "one of the CT log operators is not responding within the expected timeframe" for their 30-day product — and asked that ACME proxies raise the finalization wait to 300 seconds until it is resolved. With 90 s we still saw certificate: time limit exceeded several times a day. Same tests/vet/gofmt pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant