edges file: merge the wildcard class on an in-file collision, cert halves from EIP-less files, role wildcard only on catch-all keys, deduped cap (#305 round 2)
A route's own catch-all row kept the key with Wildcard false, so the parent's PEM would load nowhere else — the same class drop the fold already merges, now merged at the publisher too. A file with no EIP still contributes cert halves (its dial halves become cert-only). Role wildcard on an exact key is refused (it would be a KnownHost admission with no dial). The delegated union dedupes (lower-cased) before the cap so a duplicate never costs a slot, and its two standing-condition lines are said once.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 files changed,
+91
−10
internal/edge/homemap.go+23 −2internal/edge/wildcard.go+31 −5internal/edge/wildcard_parents_test.go+25 −0internal/host/edgesfile.go+8 −1internal/host/edgesfile_test.go+4 −2
diff --git a/internal/edge/homemap.go b/internal/edge/homemap.go| index a8ed1c4ca..aa675bfd8 100644 |
| --- a/internal/edge/homemap.go |
| +++ b/internal/edge/homemap.go |
| @@ -503,11 +503,32 @@ func foldHomes(files map[string]edgesFileView, self string) map[string]homeEntry |
| hosts := make(map[string]homeEntry) |
| for _, k := range keys { |
| f := files[k] |
| - if f.Box == "" || f.Box == self || f.EIP == "" { |
| + if f.Box == "" || f.Box == self { |
| continue |
| } |
| for host, h := range f.Hosts { |
| - if host == "" || (h.Role != edgeRoleHome && h.Role != edgeRoleWildcard) { |
| + if host == "" { |
| + continue |
| + } |
| + switch h.Role { |
| + case edgeRoleHome: |
| + // A dial target needs an address. A file with no EIP (a box |
| + // with no discoverable public IP) still carries its cert |
| + // halves below; its dial halves are simply not dialable. |
| + if f.EIP == "" { |
| + if !h.Wildcard || !strings.HasPrefix(host, "*.") { |
| + continue |
| + } |
| + h.Role = edgeRoleWildcard |
| + } |
| + case edgeRoleWildcard: |
| + // The cert-only role is defined for marked catch-all keys |
| + // alone; on an exact key it would be a KnownHost admission |
| + // with no dial behind it — refused. |
| + if !h.Wildcard || !strings.HasPrefix(host, "*.") { |
| + continue |
| + } |
| + default: |
| continue |
| } |
| in := homeEntry{eip: f.EIP, box: f.Box, platform: h.Platform && !h.Alias && !h.Wildcard, alias: h.Alias, wildcard: h.Wildcard, dial: h.Role == edgeRoleHome} |
diff --git a/internal/edge/wildcard.go b/internal/edge/wildcard.go| index 8e0743bf7..eb78a74e9 100644 |
| --- a/internal/edge/wildcard.go |
| +++ b/internal/edge/wildcard.go |
| @@ -44,6 +44,7 @@ import ( |
| "sort" |
| "strings" |
| "sync" |
| + "sync/atomic" |
| "time" |
| "golang.org/x/crypto/acme" |
| @@ -144,19 +145,44 @@ const maxDelegatedParents = 32 |
| // rollout mistake worth a line — this box could win the issuer lease and |
| // would then fail every order for it against the customer's own zone. |
| func unionDelegated(local, remote []string, zone string) []string { |
| + seen := make(map[string]bool, len(local)+len(remote)) |
| out := make([]string, 0, len(local)+len(remote)) |
| - out = append(out, local...) |
| - if len(remote) > 0 && zone == "" { |
| - log.Printf("edge: %d delegated wildcard parent(s) published by other boxes but CARLOS_ACME_DELEGATION_ZONE is unset here — set it fleet-wide, or this box cannot issue for them if it becomes the issuer", len(remote)) |
| + add := func(d string) { |
| + d = strings.ToLower(strings.TrimSpace(d)) |
| + if d == "" || seen[d] { |
| + return |
| + } |
| + seen[d] = true |
| + out = append(out, d) |
| + } |
| + for _, d := range local { |
| + add(d) |
| + } |
| + nLocal := len(out) |
| + for _, d := range remote { |
| + add(d) |
| + } |
| + if len(out) > nLocal && zone == "" { |
| + delegationZoneUnsetOnce.Do(func() { |
| + log.Printf("edge: %d delegated wildcard parent(s) published by other boxes but CARLOS_ACME_DELEGATION_ZONE is unset here — set it fleet-wide, or this box cannot issue for them if it becomes the issuer", len(out)-nLocal) |
| + }) |
| } |
| - out = append(out, remote...) |
| if len(out) > maxDelegatedParents { |
| - log.Printf("edge: delegated wildcard parents capped at %d (%d present: %d local, %d remote) — the overflow gets no wildcard", maxDelegatedParents, len(out), len(local), len(remote)) |
| + if n := int32(len(out)); delegatedCapLogged.Swap(n) != n { |
| + log.Printf("edge: delegated wildcard parents capped at %d (%d distinct: %d local, %d remote) — the overflow gets no wildcard", maxDelegatedParents, len(out), nLocal, len(out)-nLocal) |
| + } |
| out = out[:maxDelegatedParents] |
| } |
| return out |
| } |
| +// Standing conditions are said once: an unset zone stays unset, and the cap |
| +// line repeats only when the count it reports changes. |
| +var ( |
| + delegationZoneUnsetOnce sync.Once |
| + delegatedCapLogged atomic.Int32 |
| +) |
| + |
| // 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 |
diff --git a/internal/edge/wildcard_parents_test.go b/internal/edge/wildcard_parents_test.go| index c5cecd5da..ddc3f3c54 100644 |
| --- a/internal/edge/wildcard_parents_test.go |
| +++ b/internal/edge/wildcard_parents_test.go |
| @@ -398,6 +398,21 @@ func TestHomeMapWildcardCollisionMergesClasses(t *testing.T) { |
| t.Fatalf("order %v: a cert-only wildcard entry was a dial target", order) |
| } |
| } |
| + // Role wildcard on an EXACT key is refused (no KnownHost admission |
| + // without a dial); a file with no EIP still contributes cert halves. |
| + s2 := meshTestStore(t) |
| + putJSON(t, s2, "control/edges/carol.json", `{"v":1,"box":"carol","eip":"","hosts":{"*.noeip.example":{"role":"home","wildcard":true},"victim.example":{"role":"wildcard"},"served.example":{"role":"home","platform":true}}}`) |
| + m2 := newHomeMap(s2, "self") |
| + refreshHomeMap(t, m2) |
| + if got := m2.WildcardParents(); !reflect.DeepEqual(got, []string{"noeip.example"}) { |
| + t.Fatalf("EIP-less file: WildcardParents = %v", got) |
| + } |
| + if m2.KnownHost("victim.example") || m2.KnownHost("served.example") { |
| + t.Fatal("an exact key from an EIP-less file or with role wildcard was admitted") |
| + } |
| + if _, _, ok := m2.Home("x.noeip.example"); ok { |
| + t.Fatal("an EIP-less file offered a dial target") |
| + } |
| // A remote parent of bad shape is not a parent. |
| s := meshTestStore(t) |
| putJSON(t, s, "control/edges/betty.json", `{"v":1,"box":"betty","eip":"2.2.2.2","hosts":{"*.nodot":{"role":"wildcard","wildcard":true},"*.*.double.example":{"role":"wildcard","wildcard":true},"*.ok.example":{"role":"wildcard","wildcard":true}}}`) |
| @@ -411,6 +426,8 @@ func TestHomeMapWildcardCollisionMergesClasses(t *testing.T) { |
| // The fleet's delegated set is capped where the per-box table is: local |
| // rows first, the overflow logged and dropped. |
| func TestUnionDelegatedCapsAndWarns(t *testing.T) { |
| + delegatedCapLogged.Store(0) // once-per-process throttles: isolate from -count>1 |
| + delegationZoneUnsetOnce = sync.Once{} |
| var buf strings.Builder |
| log.SetOutput(&buf) |
| t.Cleanup(func() { log.SetOutput(os.Stderr) }) |
| @@ -419,10 +436,14 @@ func TestUnionDelegatedCapsAndWarns(t *testing.T) { |
| for i := 0; i < 40; i++ { |
| remote = append(remote, fmt.Sprintf("r%02d.example", i)) |
| } |
| + remote = append(remote, "L1.example", "r00.example") // duplicates never cost a slot |
| got := unionDelegated(local, remote, "acme.apps.test") |
| if len(got) != maxDelegatedParents || got[0] != "l1.example" || got[1] != "l2.example" || got[2] != "r00.example" { |
| t.Fatalf("union = %d entries, head %v", len(got), got[:3]) |
| } |
| + if got := unionDelegated([]string{"a.example"}, []string{"A.example", "a.example", "b.example"}, "z"); !reflect.DeepEqual(got, []string{"a.example", "b.example"}) { |
| + t.Fatalf("dedupe = %v", got) |
| + } |
| if !strings.Contains(buf.String(), "capped at 32") { |
| t.Fatalf("overflow not logged:\n%s", buf.String()) |
| } |
| @@ -431,6 +452,10 @@ func TestUnionDelegatedCapsAndWarns(t *testing.T) { |
| t.Fatalf("no-zone case: %v / %s", got, buf.String()) |
| } |
| buf.Reset() |
| + unionDelegated(local, []string{"r.example"}, "") // a standing condition is said once |
| + if buf.Len() != 0 { |
| + t.Fatalf("zone-unset line repeated:\n%s", buf.String()) |
| + } |
| if got := unionDelegated(local, nil, ""); len(got) != 2 || buf.Len() != 0 { |
| t.Fatalf("local-only case logged or changed: %v / %s", got, buf.String()) |
| } |
diff --git a/internal/host/edgesfile.go b/internal/host/edgesfile.go| index 850847120..b44827d49 100644 |
| --- a/internal/host/edgesfile.go |
| +++ b/internal/host/edgesfile.go |
| @@ -224,7 +224,14 @@ func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry, |
| continue |
| } |
| key := "*." + wd.Host |
| - if _, taken := hosts[key]; taken { |
| + if prev, taken := hosts[key]; taken { |
| + // A real route owns the key (its catch-all row, published above): |
| + // the route keeps its role and class, and the parent's class is |
| + // ADDED — the reader's fold merges classes the same way, and a |
| + // parent whose PEM must load everywhere is a fact about the name, |
| + // not about which row got there first. |
| + prev.Wildcard = true |
| + hosts[key] = prev |
| continue |
| } |
| role := "wildcard" |
diff --git a/internal/host/edgesfile_test.go b/internal/host/edgesfile_test.go| index e477e740e..c19c1e219 100644 |
| --- a/internal/host/edgesfile_test.go |
| +++ b/internal/host/edgesfile_test.go |
| @@ -762,7 +762,9 @@ func TestEdgesFilePublishesDelegatedWildcardParents(t *testing.T) { |
| if _, ok := f.Hosts["*.*.odd.example"]; ok { |
| t.Error("a malformed wildcard_domains row was published") |
| } |
| - if h := f.Hosts["*.taken.example"]; !h.Platform || h.Wildcard { |
| - t.Errorf("a wildcard entry displaced a real route's key: %+v", h) |
| + // A real route owns the key: it keeps role and class, and the parent's |
| + // class is added — the reader must still load the PEM everywhere. |
| + if h := f.Hosts["*.taken.example"]; !h.Platform || !h.Wildcard || h.Role != "home" { |
| + t.Errorf("route-owned key = %+v; want platform AND wildcard, role home", h) |
| } |
| } |