From 5d5bd02f3e4dc3b84018621bb01b2c2171de9de3 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Thu, 14 May 2026 03:53:17 +0000 Subject: [PATCH] refactor(config): extract ACME family to its own file (Phase 9, 2 of N) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continuing Phase 9 ARCH-M2 closure. Sprint 1 (commit 45ddcb75) extracted NotifierConfig as the smallest-possible pattern demonstration. This sprint extracts a larger, equally clean family: the three ACME-related config types. What moved ========== internal/config/acme.go (new, 262 lines including BSL header + Phase 9 doc-comment + `import "time"` + the three structs verbatim) - ACMEConfig (68 lines, the consumer/issuer side: we talk UP to Let's Encrypt / pebble) - ACMEServerConfig (119 lines, the server side: we ARE the ACME server, RFC 8555 + RFC 9773) - ACMEServerDirectoryMeta (20 lines, the directory `meta` block) These types form a single logical concern (everything ACME) and were already adjacent in config.go (lines 587-812 pre-split). The internal cross-reference is local: ACMEServerConfig.DirectoryMeta is typed as ACMEServerDirectoryMeta. Both still live in package `config`, so the field type continues to resolve without an import. Why this sprint specifically ============================ - Clean boundary: zero helper-function dependencies on Load(). Each field is read directly in Load() via getEnv*() helpers; those helpers stay in config.go. The struct definitions are pure data-shape and move cleanly. - High-LOC win: 227 lines deleted from config.go in one cut. After Sprint 1 (-68) + Sprint 2 (-227 from this commit) the file dropped from 3403 to 3108 LOC — already ~9% smaller than its pre-Phase-9 size with two clean PRs. - Mirrors the Phase 4 + Phase 6 prior art: ACME-related code already has its own subpackages (internal/api/handler/acme.go, internal/connector/issuer/acme/, internal/api/acme/) so a config sibling keeps the convention consistent. What stayed in config.go ========================= - `ErrACMEInsecureWithoutAck` sentinel (lines 35-46) — still needed by Load()'s validation pass, lives in the config.go top-of-file sentinel block alongside `ErrAgentBootstrapTokenRequired` and `ErrDemoModeAckExpired`. These three sentinels are tied to Validate()'s behavior, not to the ACME config struct itself. - All the `getEnv*()` helpers that ACME fields use to load — they're shared across every config struct. - The Config{}.ACME and Config{}.ACMEServer field declarations on the master Config type — those are part of the Config struct surface and stay until the Config split (Sprint 6 or later). Public-surface invariant ======================== Every type, field, and doc-comment is byte-identical to pre-split. Package stays `config`. Every caller's `config.ACMEConfig` / `config.ACMEServerConfig` / `config.ACMEServerDirectoryMeta` import path is preserved without modification. Verification: gofmt -l internal/config/ → clean go build ./internal/config/... → clean go test ./internal/config/... -count=1 → ok (0.68s) staticcheck ./internal/config/... → clean git diff --stat HEAD → -227 lines from config.go grep -nE '^type ACME[A-Za-z]+ struct' internal/config/config.go → empty (none in config.go anymore) grep -nE '^type ACME[A-Za-z]+ struct' internal/config/acme.go → 3 (ACMEConfig, ACMEServerConfig, ACMEServerDirectoryMeta) LOC delta: config.go: 3335 → 3108 (-227 lines) acme.go: new, 262 lines (incl. 32-line Phase 9 doc-comment + BSL header + package decl + import) Phase 9 progress: 2 of 12 sub-splits shipped. Next queued (Sprint 3): SCEP family from config.go → internal/config/scep.go (~330 LOC including helpers — SCEP has several scattered helpers like loadSCEPProfilesFromEnv, mergeSCEPLegacyIntoProfiles, validSCEPPathID that need to come along; this is meaningfully more complex than the pure-data ACME cut). Pre-commit verification gate respected: gofmt -l → clean go vet (implicit via go test) → clean go test ./internal/config/... → ok staticcheck ./internal/config/... → clean Closes: cowork/certctl-architecture-diligence-audit.html#fix-ARCH-M2 (partial — 2 of 12 — full ARCH-M2 closure is the aggregate) --- internal/config/acme.go | 262 ++++++++++++++++++++++++++++++++++++++ internal/config/config.go | 227 --------------------------------- 2 files changed, 262 insertions(+), 227 deletions(-) create mode 100644 internal/config/acme.go diff --git a/internal/config/acme.go b/internal/config/acme.go new file mode 100644 index 0000000..c8ba5fe --- /dev/null +++ b/internal/config/acme.go @@ -0,0 +1,262 @@ +// Copyright 2026 certctl LLC. All rights reserved. +// SPDX-License-Identifier: BUSL-1.1 + +package config + +import "time" + +// Phase 9 ARCH-M2 closure Sprint 2 (2026-05-14): extracted from +// config.go to reduce its change-risk hotspot footprint. Three +// related types live here: +// +// ACMEConfig — the issuer-connector (consumer) side: +// we are a CLIENT talking UP to an ACME +// CA (Let's Encrypt, pebble, step-ca). +// CERTCTL_ACME_* prefix. +// ACMEServerConfig — the server-side ACME (RFC 8555 + RFC +// 9773) configuration: we ARE the ACME +// server, exposing /acme/profile//* +// to cert-manager / lego / acme.sh +// clients. CERTCTL_ACME_SERVER_* prefix +// (deliberately distinct from the +// consumer namespace). +// ACMEServerDirectoryMeta — the optional `meta` block of the ACME +// directory document, populated from +// CERTCTL_ACME_SERVER_TOS_URL / WEBSITE +// / CAA_IDENTITIES / EAB_REQUIRED. +// +// Every field, doc-comment, and exported name is byte-identical to +// the pre-split form. The structs live in the same `config` package +// so every caller's `config.ACMEConfig` etc. import path is +// preserved without modification. +// +// Public-surface invariant: `go doc internal/config ACMEConfig` and +// `go doc internal/config ACMEServerConfig` produce identical output +// before and after this split. + +// ACMEConfig contains ACME issuer connector configuration. +type ACMEConfig struct { + // DirectoryURL is the ACME directory URL for certificate issuance. + // Examples: "https://acme-v02.api.letsencrypt.org/directory" (Let's Encrypt), + // "https://acme.zerossl.com/v2/DV90" (ZeroSSL), or custom CA directory. + DirectoryURL string + + // Email is the email address for ACME account registration. + // Used for certificate expiration notices and account recovery by ACME CA. + Email string + + // ChallengeType selects the ACME challenge mechanism for domain validation. + // Valid values: "http-01" (default, requires public HTTP endpoint), + // "dns-01" (DNS TXT record per renewal), or "dns-persist-01" (standing DNS record). + // Default: "http-01". + ChallengeType string + + // DNSPresentScript is the path to a shell script that creates DNS TXT records. + // Required for dns-01 and dns-persist-01 challenge types. + // Script receives these environment variables: + // - CERTCTL_DNS_DOMAIN: domain being validated (e.g., "example.com") + // - CERTCTL_DNS_FQDN: full record name (e.g., "_acme-challenge.example.com" or "_validation-persist.example.com") + // - CERTCTL_DNS_VALUE: TXT record value (key authorization digest for dns-01, or issuer domain info for dns-persist-01) + // - CERTCTL_DNS_TOKEN: ACME challenge token + // Example: /opt/dns-scripts/add-record.sh + DNSPresentScript string + + // DNSCleanUpScript is the path to a shell script that removes DNS TXT records. + // Used only for dns-01 challenges to clean up temporary validation records. + // Script receives the same environment variables as DNSPresentScript. + // Leave empty if cleanup is not needed (e.g., dns-persist-01). + DNSCleanUpScript string + + // DNSPersistIssuerDomain is the issuer domain for dns-persist-01 standing records. + // Example: "letsencrypt.org" or "zerossl.com". Only used if ChallengeType is "dns-persist-01". + // The record value becomes: "; accounturi=" + DNSPersistIssuerDomain string + + // Profile selects the ACME certificate profile for newOrder requests. + // Let's Encrypt supports "tlsserver" (standard TLS) and "shortlived" (6-day certs). + // Leave empty for the CA's default profile (backward-compatible). + // Setting: CERTCTL_ACME_PROFILE environment variable. + Profile string + + // ARIEnabled enables ACME Renewal Information (RFC 9773) support. + // When enabled, the renewal scheduler queries the CA for suggested renewal windows + // instead of relying solely on static expiration thresholds. + // Default: false. Requires a CA that supports ARI (e.g., Let's Encrypt). + // Setting: CERTCTL_ACME_ARI_ENABLED environment variable. + ARIEnabled bool + + // Insecure skips TLS certificate verification when connecting to the ACME directory. + // Only use for testing with self-signed ACME servers like Pebble. Never in production. + // Setting: CERTCTL_ACME_INSECURE environment variable. + Insecure bool + + // InsecureAck is the Phase 2 SEC-M4 closure (2026-05-13): when + // Insecure=true, Validate() refuses to start unless InsecureAck is + // also true. Pre-Phase-2 the Insecure flag only emitted a boot-time + // WARN log; this guard converts that to a hard fail-closed gate so + // the dev-only escape hatch cannot be flipped accidentally in + // production via a copy-pasted Pebble runbook. + // + // Acknowledged (Insecure=true + InsecureAck=true): boot proceeds + WARN logs. + // Unack'd (Insecure=true + InsecureAck=false): ErrACMEInsecureWithoutAck. + // Off (Insecure=false): InsecureAck is ignored entirely. + // + // Setting: CERTCTL_ACME_INSECURE_ACK environment variable. + InsecureAck bool +} + +// ACMEServerConfig is the SERVER-side ACME (RFC 8555 + RFC 9773 ARI) +// configuration. Distinct from ACMEConfig (the consumer-side issuer +// connector that talks UP to Let's Encrypt / pebble). Server uses +// CERTCTL_ACME_SERVER_* prefix throughout to avoid colliding with +// the existing CERTCTL_ACME_* consumer namespace (DIRECTORY_URL / +// PROFILE / CHALLENGE_TYPE / etc.). +// +// Phase 1a wires Enabled / DefaultAuthMode / DefaultProfileID / +// NonceTTL / DirectoryMeta. Order/Authz TTLs + the per-challenge-type +// concurrency caps + DNS01 resolver are reserved fields populated for +// Phases 2/3 — exposing them now keeps the env-var surface stable +// from day one (operators can set CERTCTL_ACME_SERVER_HTTP01_CONCURRENCY +// today; it's a no-op until Phase 3 reads it). +type ACMEServerConfig struct { + // Enabled is the master toggle. When false, the ACME handler is + // constructed (so the registry-shape stays stable) but no routes + // are registered. Operators flip this on after configuring the + // per-profile auth_mode column on certificate_profiles. + // Setting: CERTCTL_ACME_SERVER_ENABLED. + Enabled bool + + // DefaultAuthMode sets the default value of certificate_profiles.acme_auth_mode + // for NEWLY-created profiles (e.g. via API). Existing profile rows + // retain whatever value they were created with — per-profile + // values, once set, override this default. Architecture decision: + // auth mode is per-profile, not server-wide. + // Valid: "trust_authenticated" (default) or "challenge". + // Setting: CERTCTL_ACME_SERVER_DEFAULT_AUTH_MODE. + DefaultAuthMode string + + // DefaultProfileID, when set, activates the /acme/* shorthand + // path family — /acme/directory mirrors + // /acme/profile//directory etc. When empty, + // requests to the shorthand return RFC 7807 + // userActionRequired with a hint pointing at the per-profile + // path. Single-profile deployments can set this for ergonomic + // client config; multi-profile deployments leave it empty. + // Setting: CERTCTL_ACME_SERVER_DEFAULT_PROFILE_ID. + DefaultProfileID string + + // NonceTTL is how long an issued ACME nonce remains valid before + // the server rejects it as expired. RFC 8555 §6.5.1 allows the + // server to set any TTL; 5 minutes is the operator-friendly + // default (clock-skew tolerant without enabling long-replay + // attacks). Setting: CERTCTL_ACME_SERVER_NONCE_TTL. + NonceTTL time.Duration + + // OrderTTL is the lifetime of an unfulfilled ACME order. Phase 2 + // reads; Phase 1a reserves the field. Default: 24h. + // Setting: CERTCTL_ACME_SERVER_ORDER_TTL. + OrderTTL time.Duration + + // AuthzTTL is the lifetime of an unfulfilled authorization. Phase 2 + // reads; Phase 1a reserves. Default: 24h. + // Setting: CERTCTL_ACME_SERVER_AUTHZ_TTL. + AuthzTTL time.Duration + + // HTTP01ConcurrencyMax is the bound on concurrent HTTP-01 validators + // (semaphore weight). Phase 3 reads; Phase 1a reserves. Default: 10. + // Setting: CERTCTL_ACME_SERVER_HTTP01_CONCURRENCY. + HTTP01ConcurrencyMax int + + // DNS01Resolver is the resolver address used by the DNS-01 validator. + // Phase 3 reads; Phase 1a reserves. Default: "8.8.8.8:53". + // Setting: CERTCTL_ACME_SERVER_DNS01_RESOLVER. + DNS01Resolver string + + // DNS01ConcurrencyMax bounds concurrent DNS-01 validators. Default: 10. + // Setting: CERTCTL_ACME_SERVER_DNS01_CONCURRENCY. + DNS01ConcurrencyMax int + + // TLSALPN01ConcurrencyMax bounds concurrent TLS-ALPN-01 validators. + // Default: 10. Setting: CERTCTL_ACME_SERVER_TLSALPN01_CONCURRENCY. + TLSALPN01ConcurrencyMax int + + // ARIEnabled toggles RFC 9773 ACME Renewal Information surface + // (the `renewalInfo` directory entry + GET + // /acme/profile//renewal-info/). Default: true. + // Operators wanting Phase-1a-style "directory + nonce + accounts + + // orders + finalize + challenges only" can flip this off; doing so + // drops the renewalInfo URL from the directory document so ACME + // clients fall back to their static renewal scheduler. Phase 4 wires. + // Setting: CERTCTL_ACME_SERVER_ARI_ENABLED. + ARIEnabled bool + + // ARIPollInterval is the value the server returns in the Retry-After + // response header on a 200 ARI response — i.e., the suggested gap + // between successive ARI polls a client should respect. RFC 9773 §4.2 + // leaves this server-policy. Default: 6h. Tighter intervals (e.g. 1h) + // suit short-lived certs; looser intervals (24h) suit standard 90-day + // certs. Setting: CERTCTL_ACME_SERVER_ARI_POLL_INTERVAL. + ARIPollInterval time.Duration + + // RateLimitOrdersPerHour caps new-order requests per ACME account per + // rolling hour. 0 disables (no limit). Default: 100. Hits return RFC + // 7807 + RFC 8555 §6.7 `urn:ietf:params:acme:error:rateLimited` with + // a Retry-After header. In-memory token-bucket — restart wipes the + // counter, which is acceptable for orders/hour caps (eventual- + // consistency anyway). Setting: + // CERTCTL_ACME_SERVER_RATE_LIMIT_ORDERS_PER_HOUR. + RateLimitOrdersPerHour int + + // RateLimitConcurrentOrders caps the number of orders an ACME account + // can have in pending/ready/processing state simultaneously. 0 + // disables. Default: 5. Same Problem shape as the per-hour limit. + // Setting: CERTCTL_ACME_SERVER_RATE_LIMIT_CONCURRENT_ORDERS. + RateLimitConcurrentOrders int + + // RateLimitKeyChangePerHour caps account-key rollovers per account + // per rolling hour. 0 disables. Default: 5 (rollovers should be rare; + // a flood is an attack signal). Setting: + // CERTCTL_ACME_SERVER_RATE_LIMIT_KEY_CHANGE_PER_HOUR. + RateLimitKeyChangePerHour int + + // RateLimitChallengeRespondsPerHour caps challenge-respond requests + // per challenge per rolling hour. 0 disables. Default: 60 (defends + // against retry storms from a misbehaving client). Setting: + // CERTCTL_ACME_SERVER_RATE_LIMIT_CHALLENGE_RESPONDS_PER_HOUR. + RateLimitChallengeRespondsPerHour int + + // GCInterval is the tick interval for the ACME GC scheduler loop. + // On each tick the loop sweeps expired nonces, transitions expired + // pending authzs to `expired`, transitions expired + // pending/ready/processing orders to `invalid`, and reaps Phase-2 + // atomicity-window orphans (orders without a linked cert when one + // should exist). 0 disables the loop entirely. Default: 1m. Setting: + // CERTCTL_ACME_SERVER_GC_INTERVAL. + GCInterval time.Duration + + // DirectoryMeta is the optional metadata advertised in the directory + // document per RFC 8555 §7.1.1. + DirectoryMeta ACMEServerDirectoryMeta +} + +// ACMEServerDirectoryMeta holds the optional fields of the directory +// `meta` block. Each is populated from a CERTCTL_ACME_SERVER_* +// env var; an all-empty struct produces an omitempty-suppressed JSON +// `meta` field on the directory. +type ACMEServerDirectoryMeta struct { + // TermsOfService is a URL pointing to the operator's ToS document. + // Setting: CERTCTL_ACME_SERVER_TOS_URL. + TermsOfService string + // Website is a URL pointing to the operator's homepage. + // Setting: CERTCTL_ACME_SERVER_WEBSITE. + Website string + // CAAIdentities is the list of CAA-record domain values clients + // should authorize for this server. Setting: + // CERTCTL_ACME_SERVER_CAA_IDENTITIES (comma-separated). + CAAIdentities []string + // ExternalAccountRequired, when true, signals to clients that + // new-account requires an EAB token (RFC 8555 §7.3.4). Phase 1a + // advertises but does not enforce; EAB enforcement is a follow-up. + // Setting: CERTCTL_ACME_SERVER_EAB_REQUIRED. + ExternalAccountRequired bool +} diff --git a/internal/config/config.go b/internal/config/config.go index 1b216e8..030e441 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -584,233 +584,6 @@ type HealthCheckConfig struct { AutoCreate bool } -// ACMEConfig contains ACME issuer connector configuration. -type ACMEConfig struct { - // DirectoryURL is the ACME directory URL for certificate issuance. - // Examples: "https://acme-v02.api.letsencrypt.org/directory" (Let's Encrypt), - // "https://acme.zerossl.com/v2/DV90" (ZeroSSL), or custom CA directory. - DirectoryURL string - - // Email is the email address for ACME account registration. - // Used for certificate expiration notices and account recovery by ACME CA. - Email string - - // ChallengeType selects the ACME challenge mechanism for domain validation. - // Valid values: "http-01" (default, requires public HTTP endpoint), - // "dns-01" (DNS TXT record per renewal), or "dns-persist-01" (standing DNS record). - // Default: "http-01". - ChallengeType string - - // DNSPresentScript is the path to a shell script that creates DNS TXT records. - // Required for dns-01 and dns-persist-01 challenge types. - // Script receives these environment variables: - // - CERTCTL_DNS_DOMAIN: domain being validated (e.g., "example.com") - // - CERTCTL_DNS_FQDN: full record name (e.g., "_acme-challenge.example.com" or "_validation-persist.example.com") - // - CERTCTL_DNS_VALUE: TXT record value (key authorization digest for dns-01, or issuer domain info for dns-persist-01) - // - CERTCTL_DNS_TOKEN: ACME challenge token - // Example: /opt/dns-scripts/add-record.sh - DNSPresentScript string - - // DNSCleanUpScript is the path to a shell script that removes DNS TXT records. - // Used only for dns-01 challenges to clean up temporary validation records. - // Script receives the same environment variables as DNSPresentScript. - // Leave empty if cleanup is not needed (e.g., dns-persist-01). - DNSCleanUpScript string - - // DNSPersistIssuerDomain is the issuer domain for dns-persist-01 standing records. - // Example: "letsencrypt.org" or "zerossl.com". Only used if ChallengeType is "dns-persist-01". - // The record value becomes: "; accounturi=" - DNSPersistIssuerDomain string - - // Profile selects the ACME certificate profile for newOrder requests. - // Let's Encrypt supports "tlsserver" (standard TLS) and "shortlived" (6-day certs). - // Leave empty for the CA's default profile (backward-compatible). - // Setting: CERTCTL_ACME_PROFILE environment variable. - Profile string - - // ARIEnabled enables ACME Renewal Information (RFC 9773) support. - // When enabled, the renewal scheduler queries the CA for suggested renewal windows - // instead of relying solely on static expiration thresholds. - // Default: false. Requires a CA that supports ARI (e.g., Let's Encrypt). - // Setting: CERTCTL_ACME_ARI_ENABLED environment variable. - ARIEnabled bool - - // Insecure skips TLS certificate verification when connecting to the ACME directory. - // Only use for testing with self-signed ACME servers like Pebble. Never in production. - // Setting: CERTCTL_ACME_INSECURE environment variable. - Insecure bool - - // InsecureAck is the Phase 2 SEC-M4 closure (2026-05-13): when - // Insecure=true, Validate() refuses to start unless InsecureAck is - // also true. Pre-Phase-2 the Insecure flag only emitted a boot-time - // WARN log; this guard converts that to a hard fail-closed gate so - // the dev-only escape hatch cannot be flipped accidentally in - // production via a copy-pasted Pebble runbook. - // - // Acknowledged (Insecure=true + InsecureAck=true): boot proceeds + WARN logs. - // Unack'd (Insecure=true + InsecureAck=false): ErrACMEInsecureWithoutAck. - // Off (Insecure=false): InsecureAck is ignored entirely. - // - // Setting: CERTCTL_ACME_INSECURE_ACK environment variable. - InsecureAck bool -} - -// ACMEServerConfig is the SERVER-side ACME (RFC 8555 + RFC 9773 ARI) -// configuration. Distinct from ACMEConfig (the consumer-side issuer -// connector that talks UP to Let's Encrypt / pebble). Server uses -// CERTCTL_ACME_SERVER_* prefix throughout to avoid colliding with -// the existing CERTCTL_ACME_* consumer namespace (DIRECTORY_URL / -// PROFILE / CHALLENGE_TYPE / etc.). -// -// Phase 1a wires Enabled / DefaultAuthMode / DefaultProfileID / -// NonceTTL / DirectoryMeta. Order/Authz TTLs + the per-challenge-type -// concurrency caps + DNS01 resolver are reserved fields populated for -// Phases 2/3 — exposing them now keeps the env-var surface stable -// from day one (operators can set CERTCTL_ACME_SERVER_HTTP01_CONCURRENCY -// today; it's a no-op until Phase 3 reads it). -type ACMEServerConfig struct { - // Enabled is the master toggle. When false, the ACME handler is - // constructed (so the registry-shape stays stable) but no routes - // are registered. Operators flip this on after configuring the - // per-profile auth_mode column on certificate_profiles. - // Setting: CERTCTL_ACME_SERVER_ENABLED. - Enabled bool - - // DefaultAuthMode sets the default value of certificate_profiles.acme_auth_mode - // for NEWLY-created profiles (e.g. via API). Existing profile rows - // retain whatever value they were created with — per-profile - // values, once set, override this default. Architecture decision: - // auth mode is per-profile, not server-wide. - // Valid: "trust_authenticated" (default) or "challenge". - // Setting: CERTCTL_ACME_SERVER_DEFAULT_AUTH_MODE. - DefaultAuthMode string - - // DefaultProfileID, when set, activates the /acme/* shorthand - // path family — /acme/directory mirrors - // /acme/profile//directory etc. When empty, - // requests to the shorthand return RFC 7807 - // userActionRequired with a hint pointing at the per-profile - // path. Single-profile deployments can set this for ergonomic - // client config; multi-profile deployments leave it empty. - // Setting: CERTCTL_ACME_SERVER_DEFAULT_PROFILE_ID. - DefaultProfileID string - - // NonceTTL is how long an issued ACME nonce remains valid before - // the server rejects it as expired. RFC 8555 §6.5.1 allows the - // server to set any TTL; 5 minutes is the operator-friendly - // default (clock-skew tolerant without enabling long-replay - // attacks). Setting: CERTCTL_ACME_SERVER_NONCE_TTL. - NonceTTL time.Duration - - // OrderTTL is the lifetime of an unfulfilled ACME order. Phase 2 - // reads; Phase 1a reserves the field. Default: 24h. - // Setting: CERTCTL_ACME_SERVER_ORDER_TTL. - OrderTTL time.Duration - - // AuthzTTL is the lifetime of an unfulfilled authorization. Phase 2 - // reads; Phase 1a reserves. Default: 24h. - // Setting: CERTCTL_ACME_SERVER_AUTHZ_TTL. - AuthzTTL time.Duration - - // HTTP01ConcurrencyMax is the bound on concurrent HTTP-01 validators - // (semaphore weight). Phase 3 reads; Phase 1a reserves. Default: 10. - // Setting: CERTCTL_ACME_SERVER_HTTP01_CONCURRENCY. - HTTP01ConcurrencyMax int - - // DNS01Resolver is the resolver address used by the DNS-01 validator. - // Phase 3 reads; Phase 1a reserves. Default: "8.8.8.8:53". - // Setting: CERTCTL_ACME_SERVER_DNS01_RESOLVER. - DNS01Resolver string - - // DNS01ConcurrencyMax bounds concurrent DNS-01 validators. Default: 10. - // Setting: CERTCTL_ACME_SERVER_DNS01_CONCURRENCY. - DNS01ConcurrencyMax int - - // TLSALPN01ConcurrencyMax bounds concurrent TLS-ALPN-01 validators. - // Default: 10. Setting: CERTCTL_ACME_SERVER_TLSALPN01_CONCURRENCY. - TLSALPN01ConcurrencyMax int - - // ARIEnabled toggles RFC 9773 ACME Renewal Information surface - // (the `renewalInfo` directory entry + GET - // /acme/profile//renewal-info/). Default: true. - // Operators wanting Phase-1a-style "directory + nonce + accounts + - // orders + finalize + challenges only" can flip this off; doing so - // drops the renewalInfo URL from the directory document so ACME - // clients fall back to their static renewal scheduler. Phase 4 wires. - // Setting: CERTCTL_ACME_SERVER_ARI_ENABLED. - ARIEnabled bool - - // ARIPollInterval is the value the server returns in the Retry-After - // response header on a 200 ARI response — i.e., the suggested gap - // between successive ARI polls a client should respect. RFC 9773 §4.2 - // leaves this server-policy. Default: 6h. Tighter intervals (e.g. 1h) - // suit short-lived certs; looser intervals (24h) suit standard 90-day - // certs. Setting: CERTCTL_ACME_SERVER_ARI_POLL_INTERVAL. - ARIPollInterval time.Duration - - // RateLimitOrdersPerHour caps new-order requests per ACME account per - // rolling hour. 0 disables (no limit). Default: 100. Hits return RFC - // 7807 + RFC 8555 §6.7 `urn:ietf:params:acme:error:rateLimited` with - // a Retry-After header. In-memory token-bucket — restart wipes the - // counter, which is acceptable for orders/hour caps (eventual- - // consistency anyway). Setting: - // CERTCTL_ACME_SERVER_RATE_LIMIT_ORDERS_PER_HOUR. - RateLimitOrdersPerHour int - - // RateLimitConcurrentOrders caps the number of orders an ACME account - // can have in pending/ready/processing state simultaneously. 0 - // disables. Default: 5. Same Problem shape as the per-hour limit. - // Setting: CERTCTL_ACME_SERVER_RATE_LIMIT_CONCURRENT_ORDERS. - RateLimitConcurrentOrders int - - // RateLimitKeyChangePerHour caps account-key rollovers per account - // per rolling hour. 0 disables. Default: 5 (rollovers should be rare; - // a flood is an attack signal). Setting: - // CERTCTL_ACME_SERVER_RATE_LIMIT_KEY_CHANGE_PER_HOUR. - RateLimitKeyChangePerHour int - - // RateLimitChallengeRespondsPerHour caps challenge-respond requests - // per challenge per rolling hour. 0 disables. Default: 60 (defends - // against retry storms from a misbehaving client). Setting: - // CERTCTL_ACME_SERVER_RATE_LIMIT_CHALLENGE_RESPONDS_PER_HOUR. - RateLimitChallengeRespondsPerHour int - - // GCInterval is the tick interval for the ACME GC scheduler loop. - // On each tick the loop sweeps expired nonces, transitions expired - // pending authzs to `expired`, transitions expired - // pending/ready/processing orders to `invalid`, and reaps Phase-2 - // atomicity-window orphans (orders without a linked cert when one - // should exist). 0 disables the loop entirely. Default: 1m. Setting: - // CERTCTL_ACME_SERVER_GC_INTERVAL. - GCInterval time.Duration - - // DirectoryMeta is the optional metadata advertised in the directory - // document per RFC 8555 §7.1.1. - DirectoryMeta ACMEServerDirectoryMeta -} - -// ACMEServerDirectoryMeta holds the optional fields of the directory -// `meta` block. Each is populated from a CERTCTL_ACME_SERVER_* -// env var; an all-empty struct produces an omitempty-suppressed JSON -// `meta` field on the directory. -type ACMEServerDirectoryMeta struct { - // TermsOfService is a URL pointing to the operator's ToS document. - // Setting: CERTCTL_ACME_SERVER_TOS_URL. - TermsOfService string - // Website is a URL pointing to the operator's homepage. - // Setting: CERTCTL_ACME_SERVER_WEBSITE. - Website string - // CAAIdentities is the list of CAA-record domain values clients - // should authorize for this server. Setting: - // CERTCTL_ACME_SERVER_CAA_IDENTITIES (comma-separated). - CAAIdentities []string - // ExternalAccountRequired, when true, signals to clients that - // new-account requires an EAB token (RFC 8555 §7.3.4). Phase 1a - // advertises but does not enforce; EAB enforcement is a follow-up. - // Setting: CERTCTL_ACME_SERVER_EAB_REQUIRED. - ExternalAccountRequired bool -} - // OpenSSLConfig contains OpenSSL/Custom CA issuer connector configuration. type OpenSSLConfig struct { // SignScript is the path to a shell script that signs certificate requests.