ADR 0059 — ACME-Everywhere + SC-081v3 Compliance (Short-Lived Certs + ARI)¶
- Status: Accepted
- Date: 2026-05-15
- Deciders: André Luiz Gallon (Architect/Operator)
- Supersedes: —
- Related: ADR 0001 (TLS 1.3 baseline), ADR 0007 (Public-Internet Realism), ADR 0017 (Backup/DR), ADR 0054 (OCTOPUS PQC-Everywhere), ADR 0055 (Admin Console + Marketplace), ADR 0056 (Customer Auto-Provisioning)
- References:
- CA/Browser Forum Ballot SC-081v3 ("Introduce Schedule of Reducing Validity and Data Reuse Periods"), approved 2025-04-11 (25-0-5)
- RFC 8555 — Automatic Certificate Management Environment (ACME), Mar 2019
- RFC 9773 — ACME Renewal Information (ARI) Extension, Jun 2025
Context¶
A indústria de certificados TLS públicos está caminhando rapidamente para lifetimes muito curtos. A ballot SC-081v3 do CA/Browser Forum, aprovada em 11 de abril de 2025 por 25 votos a favor, 0 contra e 5 abstenções, institui um cronograma agressivo de redução do validity máximo:
| Fase | Início | Max validity TLS | Max validity SAN data reuse |
|---|---|---|---|
| 0 (status quo) | hoje | 398 dias | 398 dias |
| 1 | 2026-03-15 ⚠️ já em vigor | 200 dias | 200 dias |
| 2 | 2027-03-15 | 100 dias | 100 dias |
| 3 | 2029-03-15 | 47 dias | 10 dias |
Implicações de negócio + operacionais:
- Renovação manual é operacionalmente inviável. Em fase 3 (47 dias) um humano operando manualmente fica preso em loop de renovação semanal sem capacidade para fazer outra coisa.
- Outage por cert expirado custa caro. Em CONNECT.Art ou STUN-coord, um cert expirado derruba o serviço de federation para todo customer conectado ao cell — afeta TODOS os clientes daquele cell ao mesmo tempo.
- CA mass-revocation já aconteceu (Entrust 2024, GoDaddy 2025). Quando uma CA precisa revogar em massa, clientes ARI-aware renovam ANTES da revogação efetivar — clientes ARI-blind tomam outage.
- Compliance audits cada vez mais exigentes. SOC 2 CC6.1 (logical access) + ISO 27001 A.10.1 (cryptographic controls) já pedem evidence de automation. Auditor de 2027+ vai exigir ARI.
- PQC migration (ADR 0054) só viabiliza com cert rotation frequente. Re-emitir cert com algoritmo novo (ML-KEM/ML-DSA) precisa de pipeline ACME automatizado.
Estado atual do projeto (auditoria 2026-05-15):
| Componente | Cert source atual | Internet-facing? | SC-081v3 ready? |
|---|---|---|---|
webserver/Caddyfile (DUT lab) |
Caddy tls internal self-signed |
❌ Lab-only | ✅ N/A — não é Internet |
platform/pki/persona-pki.yaml |
Multi-CA mock (intencional) | ❌ Lab fabric | ✅ Design — não é Internet |
platform/pki/ca-art-bto.yaml (CA.Art) |
cert-manager + CA interna | ❌ Lab fabric | ✅ Manter |
| GATEWAY.Art | mTLS file-mount, BYO ou CA.Art interno | ✅ Cloud uplink + operator-facing | ❌ GAP |
| RELAY.Art | mTLS interno, file-mount | ⚠️ Pode WAN/MPLS | ❌ GAP |
| CONNECT.Art (OCTOPUS cloud) | OCTOPUS_CONNECT_CERT_FILE static |
✅ Cloud Internet | ❌ GAP |
| STUN-coord (OCTOPUS cloud) | OCTOPUS_CERT_FILE static |
✅ Cloud Internet | ❌ GAP |
Admin Console (admin.tlsstress.art) |
TBD (Wave-1 PR-OCTOPUS-4) | ✅ Cloud | ❌ GAP |
Customer App (app.tlsstress.art) |
TBD | ✅ Cloud | ❌ GAP |
Marketing/institucional (tlsstress.art apex + www.tlsstress.art) |
Hosted (verificar) | ✅ Public | ⚠️ Verificar |
Conclusão: zero módulo Internet-facing está SC-081v3-ready hoje. Precisamos de uma wave dedicada de compliance ACME antes que fase 2 (100d) entre em vigor em 2027-03-15 — idealmente concluída em 2026-Q3 para dar folga operacional.
Decisão¶
TLSStress.Art e OCTOPUS adotam ACME-Everywhere como padrão arquitetural para todo certificado TLS público. As diretivas LOCKED desta ADR:
D1 — Toda conexão Internet-facing usa cert público confiável¶
Qualquer endpoint TLS que aceita conexão proveniente da Internet pública (direta ou via WAN/MPLS/Metro-Ethernet sem mTLS L7 prévia) DEVE apresentar um cert emitido por uma CA pública confiável incluída nos root stores dos browsers padrão (Chrome / Firefox / Safari / Edge).
Lista de módulos abrangidos por esta diretiva:
- CONNECT.Art — connect-${cell}.tlsstress.art
- STUN-coord — stun-${cell}.tlsstress.art
- GATEWAY.Art — operator-facing FQDN, pattern gw-${slot}.${customer}.gw.tlsstress.art
- RELAY.Art — quando exposto WAN: relay.${customer}.tlsstress.art
- Admin Console — admin.tlsstress.art
- Customer App — app.tlsstress.art
- Provisioning webhook — provisioning.tlsstress.art
- Marketing/institucional — tlsstress.art (apex) + www.tlsstress.art
Módulos lab-internal explicitamente FORA do escopo desta ADR (mantêm PKI
interna intencional):
- webserver/Caddyfile (DUT) — Caddy internal CA
- Personas (*.persona.tlsstress.art) — multi-CA mock por design (ADR 0007)
- CA.Art bench-internal — PKI interna ZTP-prem (ADR 0048)
- OOBI Bearer / Hello mesh — PSK + mTLS interno
D2 — Issuer: Let's Encrypt como CA primária¶
Let's Encrypt (ISRG) é a CA pública primária. Critérios:
- ACME RFC 8555 nativo
- ARI RFC 9773 em produção (primeira CA a shippar)
- Free → zero blocker financeiro para 47-day cadence
- Rate limit 50 certs/account/week com escape (per-account scaling)
- Profile tlsserver short-lived (90 days hoje, 47 days plano)
- Suporte universal em cert-manager / lego / acmez
Backup CA (failover quando Let's Encrypt rate-limit / outage): plano
ZeroSSL como standby (também ACME-compliant). Implementado via
cert-manager multi-issuer fallback. CA secundária é runbook-only no
MVP — feature gate em ADR 0059-A futura.
D3 — Challenge: DNS-01 obrigatório, HTTP-01 vedado em produção¶
DNS-01 é o único challenge type permitido para certs públicos TLSStress.Art em produção. Razões:
- Suporta wildcard (necessário para per-cell
*.connect.tlsstress.art) - Não exige expor :80 HTTP — superfície de ataque menor
- Funciona dentro de VPN — GATEWAY.Art on-prem em rede privada ainda valida via DNS público
- Trabalha com CDN/anycast — Cloudflare Magic Transit não precisa bypass para validação
Provedores DNS suportados no MVP:
- AWS Route53 — para zona tlsstress.art primária
- Cloudflare — para zona delegada *.gw.tlsstress.art (customer fleets)
Solver implementation: cert-manager Webhook (oficial Route53 +
Cloudflare). On-prem standalone: lego v5 DNS provider plugin.
HTTP-01 fica permitido APENAS em dev/staging via Pebble. Tentativa de configurar HTTP-01 issuer em produção falha CI gate.
D4 — ARI (RFC 9773) feature gate ON desde dia 1¶
ARI é GA no cert-manager v1.18 — não é mais um feature gate. As gates
UseCertificateRequestApprovedCondition e ACMEAuthorizationLifetime não
existem na v1.18 e fazem o controller crashar (k8s component-base rejeita gate
desconhecida com erro FATAL → CrashLoopBackOff); NUNCA configure essas gates.
A única gate válida é ServerSideApply=true (ver platform/pki/cert-manager-helm-values.yaml).
Todo client ACME embedded (binário standalone) DEVE poll renewalInfo
endpoint ao iniciar e a cada Retry-After (RFC 9773 §4.3.4).
Benefícios: - Mass-revocation safety — se Let's Encrypt revogar cert por CT log misissuance, ARI nos avisa em horas, não em semanas - Renewal smoothing — evita thundering herd quando fleet inteiro renova na mesma janela
D5 — Cert-manager para K8s, mholt/acmez para binários standalone¶
| Ambiente | Tooling | Por quê |
|---|---|---|
| K8s (OCTOPUS cloud, customer fleet K8s) | cert-manager v1.17+ | Padrão de mercado, CRD-based, ARI |
| Binários Go standalone | mholt/acmez |
Pure Go, leve, ARI, mesma família do Caddy certmagic |
| Caddy embedded | certmagic (já incluso) |
Caddy autogestão |
| CLI debugging | lego v5 |
DNS provider catalog amplo |
Não usamos vancluever/acme (community fork pessoal, menos battle-tested).
D6 — Hot-reload sem restart¶
Todo módulo Go que serve TLS público DEVE implementar hot-reload de
cert via fsnotify no path de mount do cert + atomic swap de
tls.Config:
// pkg/cert-hotreload — high-level API
import "github.com/nollagluiz/AI_forSE/pkg/cert-hotreload"
reloader, err := certreload.New(certreload.Config{
CertFile: "/etc/tls/tls.crt",
KeyFile: "/etc/tls/tls.key",
ClientCAFile: "/etc/tls/ca.crt", // optional for mTLS
OnReload: func(newCert *tls.Certificate) {
slog.Info("cert reloaded", "not_after", newCert.Leaf.NotAfter)
},
})
// Server usa reloader.GetCertificate em tls.Config:
tlsConfig := &tls.Config{
GetCertificate: reloader.GetCertificate,
// ...
}
Garantias: - Zero downtime — conexões existentes não dropam; novas pegam novo cert - Atomic — mutex-guarded swap; nunca lê cert meio-rotacionado - Crash-safe — se novo cert falha parse, mantém o antigo + alerta - Throttled — máximo 1 reload / 5s para evitar storm
D7 — Monitoramento + alertas Prometheus¶
Cada módulo TLS público expõe métricas Prometheus de cert lifetime:
| Métrica | Tipo | Descrição |
|---|---|---|
tlsstress_tls_cert_not_after_seconds{module, sni} |
Gauge | Unix timestamp do NotAfter do leaf cert |
tlsstress_tls_cert_not_before_seconds{module, sni} |
Gauge | Unix timestamp do NotBefore |
tlsstress_tls_cert_serial_hash{module, sni} |
Gauge | Hash truncado do serial (cardinality-safe) |
tlsstress_tls_cert_reload_total{module, result} |
Counter | Total de hot-reloads (result=success/parse_fail/file_missing) |
tlsstress_tls_cert_reload_age_seconds{module} |
Gauge | Seconds since last successful reload |
tlsstress_acme_renewal_total{issuer, result} |
Counter | Renewals executados por issuer (result=success/auth_fail/finalize_fail/rate_limit) |
tlsstress_acme_ari_suggested_window_start{cert} |
Gauge | ARI suggested renewal window start (epoch) |
tlsstress_acme_ari_suggested_window_end{cert} |
Gauge | ARI suggested renewal window end (epoch) |
Alerting rules (Prometheus):
# 14d warning — give ops a week of slack
- alert: TLSCertExpiringWarning
expr: tlsstress_tls_cert_not_after_seconds - time() < 14 * 86400
for: 30m
labels: { severity: warning }
annotations:
summary: "TLS cert {{ $labels.sni }} expires in less than 14 days"
# 7d critical — page on-call business hours
- alert: TLSCertExpiringCritical
expr: tlsstress_tls_cert_not_after_seconds - time() < 7 * 86400
for: 10m
labels: { severity: critical }
# 2d page — PagerDuty 24/7
- alert: TLSCertExpiringPage
expr: tlsstress_tls_cert_not_after_seconds - time() < 2 * 86400
for: 5m
labels: { severity: page, pagerduty: "true" }
# Renewal failure cascade (3+ failures in 1h)
- alert: ACMERenewalFailing
expr: increase(tlsstress_acme_renewal_total{result=~"auth_fail|finalize_fail"}[1h]) >= 3
for: 15m
labels: { severity: critical }
# Reload health (cert file present but reload not happening)
- alert: TLSCertReloadStale
expr: tlsstress_tls_cert_reload_age_seconds > 90 * 86400
for: 1h
labels: { severity: warning }
annotations:
description: "Module {{ $labels.module }} has not reloaded cert in 90+ days"
Adicionalmente, ssl_exporter + blackbox_exporter deployados como
canary external-vantage: probam endpoints públicos do exterior (não da
mesma K8s) para detectar problemas que cert-manager interno não vê
(DNS misconfig, edge LB cert mismatch, etc.).
D8 — Per-service certs (no wildcard)¶
Cada serviço cloud OCTOPUS recebe SEU próprio cert por cell. Pattern:
connect-us-east-1-a.tlsstress.art → CONNECT.Art cell us-east-1-a
connect-eu-west-1-a.tlsstress.art → CONNECT.Art cell eu-west-1-a
stun-us-east-1-a.tlsstress.art → STUN-coord cell us-east-1-a
admin.tlsstress.art → Admin Console (single global)
app.tlsstress.art → Customer App (single global)
provisioning.tlsstress.art → Stripe webhook receiver
Razões para per-service: 1. Blast radius isolado — compromise de 1 chave não derruba todos 2. Per-cell rotation — failover regional não precisa global cert rotate 3. Audit clarity — cada cert vinculado a serviço específico no audit chain
GATEWAY.Art on-prem usa subzona delegada por customer:
gw-1.acme-corp.gw.tlsstress.art → GATEWAY.Art slot 250
gw-2.acme-corp.gw.tlsstress.art → GATEWAY.Art slot 251 (HA standby)
gw-1.bigco.gw.tlsstress.art → GATEWAY.Art slot 250 outro customer
A zona gw.tlsstress.art é delegada via NS records para um ACME-DNS
provider customer-side. Customer cert-manager / lego edita apenas
sua subzona ${customer-id}.gw.tlsstress.art.
D9 — BYO cert opcional para GATEWAY.Art (regulated escape hatch)¶
Clientes regulados (defesa, telecom, governo) podem exigir PKI própria ou Cisco Satellite Root CA específica. GATEWAY.Art SUPORTA modo BYO via Dashboard:
Dashboard → /admin/gateways/${slot}/cert-source
○ ACME-managed (Let's Encrypt) ← default
● Operator-supplied (BYO)
└─ Upload PEM (cert + key + chain)
└─ Validação: serial check, NotAfter > 30d, SAN match FQDN
└─ Renewal: operator responsável; alertas idem D7
BYO mode aceita warning explícito ("operator owns renewal"); audit chain registra opt-out + responsável.
D10 — Sigstore-signed manifests para cert-manager Issuer YAMLs¶
ClusterIssuer YAMLs que provisionam ACME accounts são consideradas infrastructure-as-code crítica. Devem ser: - Versionadas em git - Assinadas com Sigstore cosign na pipeline CI (já existe — extend) - Revisadas via PR (não kubectl apply ad-hoc em produção)
Wave de implementação¶
PR-ACME-1 (este PR) — ADR + docs:
- docs/ADR/0059-acme-everywhere-sc081v3-compliance.md (este arquivo)
- pkg/octopus/docs/08-security/acme-ari-compliance.md (reference deep)
- pkg/octopus/docs/08-security/cert-management.md (operational)
- pkg/octopus/docs/05-operations/runbooks/cert-renewal-failure.md (on-call)
- README index update
PR-ACME-2 — cert-manager ClusterIssuer + DNS-01:
- platform/pki/acme-letsencrypt-prod.yaml
- platform/pki/acme-letsencrypt-staging.yaml
- platform/pki/dns-01-route53.yaml
- platform/pki/dns-01-cloudflare.yaml
- ARI feature gate enabled
- Sigstore signing pipeline
PR-ACME-3 — Certificate CRs para cloud OCTOPUS: - CONNECT.Art per-cell - STUN-coord per-cell - Admin Console + Customer App - Provisioning webhook - Existing tests + smoke
PR-ACME-4 — pkg/cert-hotreload library Go:
- fsnotify-based, atomic swap, throttled
- 100% unit test coverage
- Benchmark < 1ms swap latency
PR-ACME-5 — Wire hot-reload em todos os módulos relevantes
PR-ACME-6 — Prometheus metrics em todos os módulos
PR-ACME-7 — ssl_exporter + blackbox_exporter + Grafana dashboard + Alertmanager rules
PR-ACME-8 — pkg/acme-client (lego wrapper) para standalone
PR-ACME-9 — BYO cert source operator escape hatch (BTO-1 D3 ext)
PR-ACME-10 — DNS subzone delegation + runbook ops
PR-ACME-11 — 47-day forward-compat e2e test (Pebble + ARI sim)
PR-ACME-12 — PR reviewer checklist + Dashboard /admin/security/certs
Estimativa total: 12 PRs, ~6.300 LoC + 1.300 docs, 6-8 semanas sequencial OR 4-5 com paralelismo. Compliance SC-081v3 fase 2 (100d, 2027-03-15) requer wave completa antes de 2027-Q1.
Alternativas consideradas¶
| Alternativa | Por que rejeitada |
|---|---|
| Manter cert-manager interno + ignorar SC-081v3 | Conexões Internet-facing browser-untrusted → quebra customer journey, marketing site → não viável |
| Comprar certs DigiCert/Sectigo/GlobalSign 1-year manuais | Em fase 3 (47d) significa $X * fleet_size * 8 renewals/year → custos altos + manual = impossível |
| HTTP-01 challenge | Não suporta wildcard; exige :80 público em todos módulos; surface attack maior; falha atrás de WAF/CDN |
| TLS-ALPN-01 challenge | Exige conexão TLS direta ao módulo — incompatível com terminação em LB / CDN |
| vancluever/acme em vez de lego/acmez | Community fork pessoal, menos battle-tested, smaller DNS provider catalog |
| Cert público apenas em cloud, BYO em on-prem | GATEWAY.Art operator-facing precisa ser browser-trusted para Dashboard funcionar — não viável BYO universal |
| Self-hosted ACME CA (step-ca) | Não é publicamente confiável → browser warning → não resolve o problema do operador |
Wildcard global *.tlsstress.art |
Blast radius gigante; compromise de 1 chave → todos serviços; rejeitado por D8 |
Consequências¶
Positivas¶
- SC-081v3 compliance alcançado antes da fase 2 (100d, 2027-03)
- Mass-revocation safety via ARI — robusto contra próximo Entrust/GoDaddy event
- Zero manual cert work para operadores customer-side — Dashboard "just works"
- Audit-ready — SOC 2 CC6.1 + ISO 27001 A.10.1 evidence pronta
- PQC migration enablement — ADR 0054 viabiliza com rotation cadence semanal
- Cost zero — Let's Encrypt grátis, escala sem custos por cert
- Browser-trusted universalmente — sem warnings para customers
- Reuse de infra K8s existente — cert-manager já está em platform/pki/
Negativas¶
- Wave de 12 PRs ~6 semanas — capacity-impactante; mas necessário
- Operator BYO mode adiciona complexidade ao GATEWAY.Art Dashboard
- DNS-01 dependência de Route53/Cloudflare credentials — secret management precisa hardening adicional
- Rate limit Let's Encrypt (50 certs/account/week) — mitigado por
multi-account (
acme-account-pool) ou backup ZeroSSL para spikes - 47-day cadence requer disciplina — incidents que tomem cert offline por > 5 dias = cert expira; runbook D7 mitiga
- External vantage probing custa $/mês — ssl_exporter + blackbox_exporter
- monitoring infrastructure ~$50-100/mês
Neutras¶
- ADR 0007 (Public-Internet Realism) continua válida — persona PKI multi-CA mock é design distinto, não afetado por SC-081v3
- ADR 0048 (ZTP-prem) continua válida — internal CA.Art mantém-se como camada bench-internal; ACME-Everywhere é a camada Internet-facing
- ADR 0054 (PQC-Everywhere) se beneficia — rotation frequente viabiliza algoritmo upgrade rolling
Compliance mapping¶
| Standard | Requirement | How this ADR satisfies |
|---|---|---|
| CA/B Forum SC-081v3 | Phased validity reduction 200/100/47d | D1+D2+D6+D7 garantem fleet renova dentro do prazo |
| RFC 8555 §6 | JWS-signed ACME requests | cert-manager + lego/acmez implementam corretamente |
| RFC 8555 §7.4 | Order finalization with CSR | Default em ambas tools |
| RFC 8555 §8.3 | DNS-01 challenge | D3 mandatório |
| RFC 8555 §9 | Rate limiting Retry-After | cert-manager honra; ours pool accounts |
| RFC 9773 §3 | ARI renewalInfo endpoint poll | D4 mandatório |
| RFC 9773 §4.3.4 | Retry-After on renewalInfo | D4 implementado em pkg/acme-client |
| SOC 2 CC6.1 | Logical access automation | Cert-manager + ARI = evidence ready |
| ISO 27001 A.10.1 | Cryptographic controls | Documented in cert-management.md |
| PCI DSS 4.0 4.2.1.1 | Inventory of trusted keys | Prometheus + audit chain provide |
| NIST SP 800-57 §8.4 | Key lifetimes documented | Per-service in cert-management.md |
| GDPR Art. 32 | State-of-the-art crypto | TLS 1.3 + ACME-automated + ARI |
Riscos + Mitigações¶
| Risco | Probabilidade | Impacto | Mitigação |
|---|---|---|---|
| Let's Encrypt outage > 24h durante renewal window | Baixa | Alto | Backup ZeroSSL issuer em standby (Wave-ACME-2.5) |
| ARI poll storm sobrecarrega CA | Muito baixa | Médio | Jitter ARI poll por cell; cap acme-account-pool size |
| DNS provider credential leak | Baixa | Crítico | Sealed Secrets + rotation 90d; audit log de access |
| Cert-manager bug em ARI feature gate | Baixa | Alto | Staging cell pinned a cert-manager N-1; only roll forward after staging valida 30d |
| Customer DNS subzone delegation falha | Média | Médio | Setup wizard + runbook D10; pre-flight check no Dashboard onboarding |
| Operator opta-out BYO sem entender lifecycle | Média | Alto | Warning + double-confirm modal; PagerDuty notification when BYO cert < 14d |
Open items / future work¶
- ADR 0059-A (Q3 2026) — Backup ACME CA failover policy (ZeroSSL, Google Trust Services) — agora documented como runbook only
- ADR 0059-B (Q4 2026) — PQC ACME profiles (ml-dsa-65 leaf signatures) quando CAs públicas suportarem (provavelmente 2027+)
- ADR 0059-C (Q1 2027) — Multi-account account pool para Let's Encrypt rate limit escape (fleet > 1000 customers)
- Patent angle: arquitetura DNS subzone delegation + ACME per-customer via GATEWAY.Art pode integrar Patent Family F (mobile + edge ops) como claim adicional. Avaliar com IP counsel.
Memory + cross-references¶
Adicionar entrada em MEMORY.md (auto-memory) sob seção FOUNDATIONAL
RULES:
⭐⭐⭐ ACME-Everywhere + SC-081v3 Compliance LOCKED — Toda conexão TLS Internet-facing usa cert público Let's Encrypt via cert-manager + DNS-01 + ARI feature gate. Per-service certs (no wildcard). Hot-reload obrigatório. Monitoring Prometheus 14d/7d/2d. GATEWAY.Art BYO escape hatch. ADR 0059. Wave de 12 PRs até 2026-Q3.