carlos / platform Public

Merge pull request #168 from carlosframework/edge-delegated-wildcards

edge: delegated-challenge wildcard certs (customer domains, zero customer DNS knowledge)
Paul Campbell b56213fdc6723d7e6e5aafeed7f9b0d4e0f0eb7f parent e52d8ecparent 7154424
4 files changed, +250 −37
  • internal/edge/edge.go +2 −2
  • internal/edge/wildcard.go +111 −24
  • internal/edge/wildcard_test.go +134 −11
  • internal/host/agent.go +3 −0
diff --git a/internal/edge/edge.go b/internal/edge/edge.go
index 219e8697e..16cbeaae6 100644
--- a/internal/edge/edge.go
+++ b/internal/edge/edge.go
@@ -1333,8 +1333,8 @@ func RunWithActivatorReady(args []string, act Activator, cache *Cache, ready fun
e.OnReload(w.refresh)
w.refresh()
go w.run()
- log.Printf("edge: wildcard certs on (static %v, apps domain %q)",
- splitDomains(os.Getenv("CARLOS_WILDCARD_DOMAINS")), os.Getenv("PLATFORM_APPS_DOMAIN"))
+ log.Printf("edge: wildcard certs on (static %v, apps domain %q, delegation zone %q)",
+ splitDomains(os.Getenv("CARLOS_WILDCARD_DOMAINS")), os.Getenv("PLATFORM_APPS_DOMAIN"), w.delegationZone)
}
// The non-ACME :80 handler. Known hosts get the classic redirect to https
diff --git a/internal/edge/wildcard.go b/internal/edge/wildcard.go
index 251c8e0fc..32ce6e5e1 100644
--- a/internal/edge/wildcard.go
+++ b/internal/edge/wildcard.go
@@ -16,8 +16,11 @@ package edge
// instance hosts are one label deep).
//
// Canary hosts (<name>.<app>.<acct>.…, one label too deep) and custom
-// domains stay per-host HTTP-01. The wildcard only changes how a handshake
-// GETS a certificate: routing, HostPolicy and the :80 handler are
+// domains stay per-host HTTP-01 — except a custom domain approved as a
+// delegated wildcard parent (spec §4), which gets its own *.<parent>
+// wildcard via a challenge TXT published in our own zone. The wildcard
+// only changes how a handshake GETS a certificate: routing, HostPolicy and
+// the :80 handler are
// untouched, and every failure (DNS provider outage, ACME outage,
// unreadable cache) degrades to exactly the per-host behaviour — never a
// dead edge.
@@ -45,6 +48,8 @@ import (
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
+
+ "github.com/carlosframework/platform/internal/provision"
)
const letsEncryptDirectory = "https://acme-v02.api.letsencrypt.org/directory"
@@ -65,6 +70,19 @@ type wildcardManager struct {
appsDomain string
hosts func() []string // live route hosts (Edge.currentHosts)
+ // delegated lists customer domains approved for delegated-challenge
+ // wildcard issuance (spec §4: claim-derived; nil = none). Their
+ // challenge TXTs publish into delegationZone — our own zone — at
+ // _acme-challenge.<parent>.<delegationZone>, which the customer has
+ // CNAMEd _acme-challenge.<parent> to. The platform never touches,
+ // or knows, the customer's DNS provider.
+ delegated func() []string
+ delegationZone string
+
+ // delegatedSet is refresh()'s snapshot of delegated(), read by
+ // challengeFQDN under mu (issuance runs on run()'s goroutine).
+ delegatedSet map[string]bool
+
mu sync.Mutex
parents []string // last computed parent set (sorted)
certs map[string]*tls.Certificate
@@ -81,26 +99,31 @@ type wildcardManager struct {
wake chan struct{}
}
-func newWildcardManager(staticDomains []string, appsDomain string, hosts func() []string, cache autocert.Cache, dns dnsProvider) *wildcardManager {
+func newWildcardManager(staticDomains []string, appsDomain string, hosts func() []string, delegated func() []string, delegationZone string, cache autocert.Cache, dns dnsProvider) *wildcardManager {
return &wildcardManager{
- cache: cache,
- dns: dns,
- directory: envOr("CARLOS_ACME_DIRECTORY", letsEncryptDirectory),
- staticDomains: staticDomains,
- appsDomain: strings.ToLower(strings.TrimSpace(appsDomain)),
- hosts: hosts,
- certs: map[string]*tls.Certificate{},
- nextAttempt: map[string]time.Time{},
- wake: make(chan struct{}, 1),
+ cache: cache,
+ dns: dns,
+ directory: envOr("CARLOS_ACME_DIRECTORY", letsEncryptDirectory),
+ staticDomains: staticDomains,
+ appsDomain: strings.ToLower(strings.TrimSpace(appsDomain)),
+ hosts: hosts,
+ delegated: delegated,
+ delegationZone: strings.ToLower(strings.TrimSpace(delegationZone)),
+ certs: map[string]*tls.Certificate{},
+ nextAttempt: map[string]time.Time{},
+ delegatedSet: map[string]bool{},
+ wake: make(chan struct{}, 1),
}
}
-// parentsFor computes the wildcard parent set: the static domains plus
+// parentsFor computes the wildcard parent set: staticAndDelegated (the
+// static CARLOS_WILDCARD_DOMAINS plus any delegated parents refresh() has
+// already vetted — both arrive pre-merged through this one parameter) union
// <acct>.<appsDomain> for every route host exactly two labels under
// appsDomain (<app>.<acct>.<appsDomain>). Canary hosts (three labels), the
// apex, and foreign domains contribute nothing. Sorted and deduped so the
// set is deterministic for logs and tests.
-func parentsFor(static []string, appsDomain string, hosts []string) []string {
+func parentsFor(staticAndDelegated []string, appsDomain string, hosts []string) []string {
seen := map[string]bool{}
var out []string
add := func(p string) {
@@ -109,7 +132,7 @@ func parentsFor(static []string, appsDomain string, hosts []string) []string {
out = append(out, p)
}
}
- for _, d := range static {
+ for _, d := range staticAndDelegated {
add(strings.ToLower(d))
}
if appsDomain != "" {
@@ -129,17 +152,48 @@ func parentsFor(static []string, appsDomain string, hosts []string) []string {
}
// refresh recomputes the parent set from the live route table (called from
-// the reload hook on every route swap, and once at startup). If any parent
-// lacks a usable cert it wakes run() — that is what makes a NEW account's
-// wildcard start issuing the moment its first route lands, instead of on
-// the next 12h tick. A wake cannot bypass a failing parent's backoff: run()
-// still skips any parent whose nextAttempt hasn't passed, so a wake only
-// advances parents that aren't already backing off.
+// the reload hook on every route swap, and once at startup), folding in
+// delegated parents (spec §4) alongside the static and per-account ones.
+// Platform parents always win: a delegated entry that collides with a
+// static domain, or falls under the apps domain, is dropped here — before
+// it reaches parentsFor's union or delegatedSet — because letting it
+// through would redirect a PLATFORM parent's challenge to a delegation
+// target where no CNAME exists, silently degrading its wildcard renewal to
+// per-host. If any parent lacks a usable cert it wakes run() — that is what
+// makes a NEW account's wildcard start issuing the moment its first route
+// lands, instead of on the next 12h tick. A wake cannot bypass a failing
+// parent's backoff: run() still skips any parent whose nextAttempt hasn't
+// passed, so a wake only advances parents that aren't already backing off.
func (w *wildcardManager) refresh() {
- parents := parentsFor(w.staticDomains, w.appsDomain, w.hosts())
+ var delegated []string
+ if w.delegated != nil {
+ static := make(map[string]bool, len(w.staticDomains))
+ for _, d := range w.staticDomains {
+ static[strings.ToLower(strings.TrimSpace(d))] = true
+ }
+ appsSuffix := ""
+ if w.appsDomain != "" {
+ appsSuffix = "." + w.appsDomain
+ }
+ for _, d := range w.delegated() {
+ if d = strings.ToLower(strings.TrimSpace(d)); d == "" {
+ continue
+ }
+ if static[d] || (appsSuffix != "" && strings.HasSuffix(d, appsSuffix)) {
+ continue
+ }
+ delegated = append(delegated, d)
+ }
+ }
+ parents := parentsFor(append(append([]string(nil), w.staticDomains...), delegated...), w.appsDomain, w.hosts())
now := time.Now()
w.mu.Lock()
w.parents = parents
+ set := make(map[string]bool, len(delegated))
+ for _, d := range delegated {
+ set[d] = true
+ }
+ w.delegatedSet = set
need := false
for _, p := range parents {
c := w.certs[p]
@@ -244,6 +298,27 @@ func coversParent(cert *tls.Certificate, parent string) bool {
return ok && pub.Equal(cert.Leaf.PublicKey)
}
+// challengeFQDN returns where parent's DNS-01 TXT is published: the
+// delegation target inside our own zone for delegated parents, or
+// directly at _acme-challenge.<parent> otherwise. With no delegation
+// zone configured the direct form is used for everyone — the
+// misconfigured state degrades to the historical behavior and per-host
+// issuance keeps covering (obtain will fail on the CA's lookup, log,
+// and back off — never a dead edge).
+func (w *wildcardManager) challengeFQDN(parent string) string {
+ if w.delegationZone != "" && w.isDelegated(parent) {
+ return "_acme-challenge." + parent + "." + w.delegationZone
+ }
+ return "_acme-challenge." + parent
+}
+
+// isDelegated reports whether parent is in the delegated set.
+func (w *wildcardManager) isDelegated(parent string) bool {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ return w.delegatedSet[parent]
+}
+
// wildcardPEMKey is the cache key for a parent's wildcard cert+key PEM —
// Eleven's file name, so a dir cache written by that lineage reads with no
// migration, and the keys sit additively beside autocert's per-host entries.
@@ -448,7 +523,7 @@ func (w *wildcardManager) solveDNS01(ctx context.Context, client *acme.Client, p
return err
}
- challengeFQDN := "_acme-challenge." + parent
+ challengeFQDN := w.challengeFQDN(parent)
cleanup, err := w.dns.present(ctx, challengeFQDN, txt)
if err != nil {
return fmt.Errorf("publish challenge TXT: %v", err)
@@ -532,7 +607,19 @@ func newWildcardFromEnv(e *Edge, cache autocert.Cache) *wildcardManager {
log.Printf("edge: wildcard certs off (%s) — per-host issuance continues", reason)
return nil
}
- return newWildcardManager(static, appsDomain, e.currentHosts, cache, dns)
+ // Delegated parents (spec §4): the SOURCE is claim-derived and lands
+ // with the claim-derivation pass (Plan C); until then the zone is
+ // configured but the set is empty — inert by construction.
+ rawZone := os.Getenv("CARLOS_ACME_DELEGATION_ZONE")
+ zone := strings.ToLower(strings.TrimSpace(rawZone))
+ if zone != "" && !provision.ValidHostShape(zone) {
+ // Degrade, don't die: the edge must still boot on a bad zone value.
+ // Delegation goes off; per-host and direct (own-zone) DNS-01 keep
+ // covering everyone else.
+ log.Printf("edge: CARLOS_ACME_DELEGATION_ZONE %q invalid — delegation off, per-host and direct DNS-01 continue", rawZone)
+ zone = ""
+ }
+ return newWildcardManager(static, appsDomain, e.currentHosts, nil, zone, cache, dns)
}
// wrapGetCertificate serves the wildcard for covered names and hands
diff --git a/internal/edge/wildcard_test.go b/internal/edge/wildcard_test.go
index 4925d6918..8ad08d544 100644
--- a/internal/edge/wildcard_test.go
+++ b/internal/edge/wildcard_test.go
@@ -73,7 +73,7 @@ func TestParentsFor(t *testing.T) {
func TestCertForMatchesExactlyOneLabel(t *testing.T) {
w := newWildcardManager([]string{"go.tito.io"}, "oncarlos.com",
func() []string { return []string{"kass.abs.oncarlos.com"} },
- autocert.DirCache(t.TempDir()), nil)
+ nil, "", autocert.DirCache(t.TempDir()), nil)
w.refresh()
cert, _ := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour))
w.mu.Lock()
@@ -124,7 +124,7 @@ func TestWildcardPEMRoundTripAndRenewDecision(t *testing.T) {
t.Fatal("parseWildcardPEM(junk): want error")
}
- w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, autocert.DirCache(t.TempDir()), nil)
+ w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", autocert.DirCache(t.TempDir()), nil)
if !w.needsObtain("abs.oncarlos.com", time.Now()) {
t.Fatal("no cert loaded: needsObtain should be true")
}
@@ -152,7 +152,7 @@ func TestLoadCachedAndRefreshWake(t *testing.T) {
}
w := newWildcardManager(nil, "oncarlos.com",
- func() []string { return []string{"kass.abs.oncarlos.com"} }, cache, nil)
+ func() []string { return []string{"kass.abs.oncarlos.com"} }, nil, "", cache, nil)
if !w.loadCached(context.Background(), "abs.oncarlos.com") {
t.Fatal("loadCached: want true for cached parent")
}
@@ -169,7 +169,7 @@ func TestLoadCachedAndRefreshWake(t *testing.T) {
default:
}
w2 := newWildcardManager(nil, "oncarlos.com",
- func() []string { return []string{"kass.abs.oncarlos.com"} }, autocert.DirCache(t.TempDir()), nil)
+ func() []string { return []string{"kass.abs.oncarlos.com"} }, nil, "", autocert.DirCache(t.TempDir()), nil)
w2.refresh()
select {
case <-w2.wake:
@@ -190,7 +190,7 @@ func TestLoadCachedRejectsWrongCoverage(t *testing.T) {
if err := cache.Put(context.Background(), wildcardPEMKey("abs.oncarlos.com"), rawWrongParent); err != nil {
t.Fatal(err)
}
- w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, cache, nil)
+ w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache, nil)
if w.loadCached(context.Background(), "abs.oncarlos.com") {
t.Fatal("loadCached: want false for cert covering the wrong parent")
}
@@ -215,7 +215,7 @@ func TestLoadCachedRejectsWrongCoverage(t *testing.T) {
if err := cache2.Put(context.Background(), wildcardPEMKey("abs.oncarlos.com"), rawMismatch); err != nil {
t.Fatal(err)
}
- w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, cache2, nil)
+ w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache2, nil)
if w2.loadCached(context.Background(), "abs.oncarlos.com") {
t.Fatal("loadCached: want false for key/leaf mismatch")
}
@@ -248,12 +248,12 @@ func (f *fakeDNS) present(_ context.Context, fqdn, value string) (func(context.C
func TestWildcardAccountKeyStableAcrossManagers(t *testing.T) {
dir := t.TempDir()
cache := autocert.DirCache(dir)
- w1 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, cache, &fakeDNS{})
+ w1 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache, &fakeDNS{})
k1, err := w1.accountKey(context.Background())
if err != nil {
t.Fatal(err)
}
- w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, cache, &fakeDNS{})
+ w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache, &fakeDNS{})
k2, err := w2.accountKey(context.Background())
if err != nil {
t.Fatal(err)
@@ -274,7 +274,7 @@ func (f faultyCache) Get(context.Context, string) ([]byte, error) {
func TestAccountKeyBackendFaultDoesNotMint(t *testing.T) {
w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil },
- faultyCache{autocert.DirCache(t.TempDir())}, &fakeDNS{})
+ nil, "", faultyCache{autocert.DirCache(t.TempDir())}, &fakeDNS{})
if _, err := w.accountKey(context.Background()); err == nil {
t.Fatal("backend fault: want error, got a (freshly minted) key")
}
@@ -286,7 +286,7 @@ func TestAccountKeyBackendFaultDoesNotMint(t *testing.T) {
// (delete) clears the entry entirely.
func TestPerParentBackoff(t *testing.T) {
w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil },
- autocert.DirCache(t.TempDir()), &fakeDNS{})
+ nil, "", autocert.DirCache(t.TempDir()), &fakeDNS{})
if w.backingOff("abs.oncarlos.com", time.Now()) {
t.Fatal("no nextAttempt recorded yet: backingOff should be false")
@@ -321,7 +321,7 @@ func TestPerParentBackoff(t *testing.T) {
func TestWrappedGetCertificateFallsThrough(t *testing.T) {
w := newWildcardManager(nil, "oncarlos.com",
func() []string { return []string{"kass.abs.oncarlos.com"} },
- autocert.DirCache(t.TempDir()), &fakeDNS{})
+ nil, "", autocert.DirCache(t.TempDir()), &fakeDNS{})
w.refresh()
cert, _ := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour))
w.mu.Lock()
@@ -418,3 +418,126 @@ func TestCurrentHostsPlatformOnly(t *testing.T) {
t.Fatalf("currentHosts = %v, want %v", got, want)
}
}
+
+func TestDelegatedParentsJoinTheSet(t *testing.T) {
+ w := newWildcardManager(nil, "oncarlos.com", func() []string {
+ return []string{"app.bab.oncarlos.com"}
+ }, func() []string {
+ return []string{"woodstar.app"}
+ }, "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{})
+ w.refresh()
+ w.mu.Lock()
+ parents := append([]string(nil), w.parents...)
+ w.mu.Unlock()
+ want := []string{"bab.oncarlos.com", "woodstar.app"}
+ if !reflect.DeepEqual(parents, want) {
+ t.Fatalf("parents = %v, want %v", parents, want)
+ }
+}
+
+func TestChallengeFQDNDelegation(t *testing.T) {
+ w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil },
+ func() []string { return []string{"woodstar.app"} },
+ "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{})
+ w.refresh()
+ // A delegated parent's TXT publishes into OUR zone, at the target
+ // the customer's CNAME points to.
+ if got, want := w.challengeFQDN("woodstar.app"), "_acme-challenge.woodstar.app.acme.oncarlos.com"; got != want {
+ t.Errorf("delegated: %q, want %q", got, want)
+ }
+ // A platform-zone parent publishes directly, exactly as before.
+ if got, want := w.challengeFQDN("bab.oncarlos.com"), "_acme-challenge.bab.oncarlos.com"; got != want {
+ t.Errorf("own-zone: %q, want %q", got, want)
+ }
+}
+
+func TestChallengeFQDNWithoutZoneNeverDelegates(t *testing.T) {
+ // No delegation zone configured: even a listed delegated parent
+ // publishes directly (misconfiguration degrades to the historical
+ // behavior, which per-host issuance then covers — never a dead edge).
+ w := newWildcardManager(nil, "", func() []string { return nil },
+ func() []string { return []string{"woodstar.app"} },
+ "", autocert.DirCache(t.TempDir()), &fakeDNS{})
+ w.refresh()
+ if got, want := w.challengeFQDN("woodstar.app"), "_acme-challenge.woodstar.app"; got != want {
+ t.Errorf("%q, want %q", got, want)
+ }
+}
+
+func TestNilDelegatedSourceIsFine(t *testing.T) {
+ // Every existing construction site passes nil — the zero-source
+ // manager must behave exactly as before this feature existed.
+ w := newWildcardManager([]string{"go.tito.io"}, "", func() []string { return nil },
+ nil, "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{})
+ w.refresh()
+ w.mu.Lock()
+ parents := append([]string(nil), w.parents...)
+ w.mu.Unlock()
+ if !reflect.DeepEqual(parents, []string{"go.tito.io"}) {
+ t.Fatalf("parents = %v, want [go.tito.io]", parents)
+ }
+ if got, want := w.challengeFQDN("go.tito.io"), "_acme-challenge.go.tito.io"; got != want {
+ t.Errorf("%q, want %q", got, want)
+ }
+}
+
+// TestPlatformParentsWinOverDelegated exercises the precedence rule refresh()
+// enforces: a delegated entry that collides with a static domain or falls
+// under the apps domain is dropped before it can hijack a PLATFORM parent's
+// challenge — that parent's TXT must still publish directly, never into the
+// delegation zone where no customer CNAME exists.
+func TestPlatformParentsWinOverDelegated(t *testing.T) {
+ w := newWildcardManager([]string{"go.tito.io"}, "oncarlos.com",
+ func() []string { return []string{"app.bab.oncarlos.com"} },
+ func() []string { return []string{"go.tito.io", "bab.oncarlos.com", "woodstar.app"} },
+ "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{})
+ w.refresh()
+
+ // Colliding entries lose: both the static domain and the dynamic
+ // per-account parent publish their challenge directly, exactly as if
+ // they had never appeared in the delegated source.
+ if got, want := w.challengeFQDN("go.tito.io"), "_acme-challenge.go.tito.io"; got != want {
+ t.Errorf("static collision: challengeFQDN = %q, want %q", got, want)
+ }
+ if got, want := w.challengeFQDN("bab.oncarlos.com"), "_acme-challenge.bab.oncarlos.com"; got != want {
+ t.Errorf("apps-domain collision: challengeFQDN = %q, want %q", got, want)
+ }
+ // A genuinely foreign delegated parent still gets the delegated form.
+ if got, want := w.challengeFQDN("woodstar.app"), "_acme-challenge.woodstar.app.acme.oncarlos.com"; got != want {
+ t.Errorf("non-colliding delegated: challengeFQDN = %q, want %q", got, want)
+ }
+}
+
+func TestNewWildcardFromEnvDelegationZone(t *testing.T) {
+ t.Setenv("CARLOS_WILDCARD_DOMAINS", "go.tito.io")
+ t.Setenv("CARLOS_DNS_PROVIDER", "dnsimple")
+ t.Setenv("CARLOS_DNSIMPLE_TOKEN", "test-token")
+ t.Setenv("CARLOS_ACME_DELEGATION_ZONE", " Acme.OnCarlos.Com ")
+ e := &Edge{}
+ w := newWildcardFromEnv(e, autocert.DirCache(t.TempDir()))
+ if w == nil {
+ t.Fatal("manager should be on")
+ }
+ if w.delegationZone != "acme.oncarlos.com" {
+ t.Errorf("delegationZone = %q, want lowercased+trimmed acme.oncarlos.com", w.delegationZone)
+ }
+}
+
+// TestNewWildcardFromEnvInvalidDelegationZoneDegrades covers "validate at
+// boot, degrade don't die": a malshaped CARLOS_ACME_DELEGATION_ZONE must
+// never stop the edge from booting wildcards — it just turns delegation off
+// while per-host and direct (own-zone) DNS-01 keep covering everyone else.
+func TestNewWildcardFromEnvInvalidDelegationZoneDegrades(t *testing.T) {
+ t.Setenv("CARLOS_WILDCARD_DOMAINS", "go.tito.io")
+ t.Setenv("CARLOS_DNS_PROVIDER", "dnsimple")
+ t.Setenv("CARLOS_DNSIMPLE_TOKEN", "test-token")
+ t.Setenv("CARLOS_ACME_DELEGATION_ZONE", "*.bad zone")
+ e := &Edge{}
+ w := newWildcardFromEnv(e, autocert.DirCache(t.TempDir()))
+ if w == nil {
+ t.Fatal("invalid delegation zone must not stop the edge from booting")
+ }
+ if w.delegationZone != "" {
+ t.Errorf("delegationZone = %q, want empty (delegation off)", w.delegationZone)
+ }
+}
diff --git a/internal/host/agent.go b/internal/host/agent.go
index 23fb23f62..4b6c310f8 100644
--- a/internal/host/agent.go
+++ b/internal/host/agent.go
@@ -111,6 +111,9 @@
// wildcard certs: unset/dnsimple needs the token (SSM → unit env),
// route53 rides the box IAM role; nothing set = wildcards off,
// per-host HTTP-01 only
+// - CARLOS_ACME_DELEGATION_ZONE — our own zone that delegated wildcard
+// parents' challenge TXTs publish into (spec §4); unset or an invalid
+// shape = delegation off, per-host and direct DNS-01 continue
// - PLATFORM_EDGE_IPS — comma-separated edge IPs custom-domain DNS checks
// compare against. Default: this box's public IPv4 from IMDS
// (best-effort; if neither is knowable, DNS stamping stands down