// Alias reconcile pass — derives the registry's route_aliases table AND its // wildcard_domains and route_paths tables from the global claims recorded in // the bucket (#151). A claim contributes an ordinary alias row when it names // a Route (see the loop below); a Wildcard claim additionally contributes a // wildcard_domains row (a delegated-ACME wildcard-cert parent), a // Wildcard+Catchall claim also contributes a "*." route_aliases row // pointing at the same primary, and a claim's Paths map contributes one // route_paths row per prefix (path-based routing). See claims.go for the // claim shape and registry.go's RouteAlias/WildcardDomain/RoutePath for the // storage shapes; this file is the seam that turns one into the other. package domains import ( "context" "log" "slices" "sort" "strconv" "strings" "sync" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) // maxWildcardDomains caps wildcard_domains per box: every delegated parent // shares one ACME account's new-order budget, so an unbounded claim set // could starve platform issuance. Deterministic (claims come in list // order); drops are logged, never silent. const maxWildcardDomains = 32 // sidecarSuffix mirrors provision.SidecarSuffix, pinned here rather than // imported — the same reason claims.go's hostLabelRe gives: internal/provision // (its reconcile.go) already imports internal/domains for account/route // materialization, so importing provision back would be a cycle. // // A sidecar host is refused a route at the structural level (edge.go's // Reload skips Kind=="sidecar"), but that guarantee protects only the // SIDECAR row itself. This constant guards the other direction, at the ONE // place that can mint a route_aliases row from nothing: a claim naming a // reserved-suffix host as either end of an alias would insert that host // into the edge's routing map serving an ORDINARY route's upstream — which // Reload's Kind check never sees, because the row it reads is the alias's // primary, not a Kind=="sidecar" row at all. const sidecarSuffix = ".sidecar.internal" // maxInstanceAliases caps the alias rows instance catch-alls derive on one // box. Every alias row costs a serial DNS lookup in the box's health stamp // and about 140 bytes in its status snapshot, which readers skip whole // above 1 MiB — and a skipped box drops out of the deployment's route and // edge-address folds. The cap keeps a large app from carrying a box past // that. Deterministic (claims in list order, routes in host order), so a // rerun keeps the same names; the overflow is logged, never silent. const maxInstanceAliases = 1000 // instanceCatchallSaid holds the instance catch-alls whose refusal has // already been logged by this process, keyed by claim host and reason. The // pass runs every couple of seconds on every box, and a catch-all left // without its parent claim is a standing state, not an event. var instanceCatchallSaid sync.Map func sayInstanceCatchallOnce(host, reason string) { if _, said := instanceCatchallSaid.LoadOrStore(host+"\x00"+reason, true); !said { log.Printf("domains: instance catch-all %s: %s", host, reason) } } // deriveInstanceAliases adds the alias rows the instance catch-alls derive // (Claim.Instances): for each catch-all "*.", one row //