package edge // The steering probe: the small 200 that decides whether DNS steering's // rung-2 withdrawal is real or decorative (spec: // docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md, Part 4). // // The converger (internal/steering/route53.go) binds every latency record it // writes to a Route53 health check that probes the edge's EIP over HTTPS // with SNI edge. and GET /. That check is the ONLY thing that // withdraws a dead edge's answers, so it has to pass on a live box — and on // an unmodified edge it could not, twice over: // // - TLS: edge. is one label under the apps domain, and every // wildcard this edge holds is a per-ACCOUNT parent one label DEEPER // (*..). No cert covered the name, so the probe died // at the handshake and every record looked dead. The fix is in // wildcard.go: with steering armed, the apps domain itself joins the // wildcard parent set, so *. covers exactly this name, // issued over DNS-01. The narrow alternative — a per-host exception for // the single name — was rejected on three counts (the full argument is // at the arming site in wildcard.go): it would have to breach the // exact-registry Allowed rule (PR #180) in TWO places, issuerHostPolicy // AND the mirror's m.allowed bound on the live challenge read-through; // with the name resolving to every armed edge, the CA reaches an // arbitrary one, so issuance would hang on the cross-box challenge // mirror; and every attempt is paid for in failed health checks and in // the shared ACME account's failed-validation budget. NOT because an // order could not complete — autocert orders under context.Background() // with a 5-minute budget (autocert.go:274), so a checker that hangs up // on the handshake does not cancel the order it triggered. // // - HTTP: the hostname is not a registry route, so it fell through to // unknownHost — a 404, or on a -fallback edge (every flagship box) a // 302, which Route53 counts as HEALTHY. A false-healthy check is worse // than no check: it never withdraws anything, and nothing in the journal // says so. Hence the answer below, placed AHEAD of every other serving // path. (Since #174 the name, one label under the apps domain, would get // the pending page's 503 instead, which Route53 scores unhealthy on // every edge: wrong the other way. The probe still answers first.) // // Inert unless the deployment steers: steeringProbeHostFromEnv returns "" // unless CARLOS_STEERING_ZONE_ID is set AND usable — the same read, with the // same validation, that the converger performs — so on every box today the // probe is one comparison against an empty string and nothing else changes. // PLATFORM_APPS_DOMAIN alone is NOT the gate: both production deployments // set it, and gating there would answer for a hostname no zone steers and // order a wildcard nobody asked for. import ( "io" "log" "net/http" "os" "strings" "github.com/carlosframework/platform/internal/steering" ) // steeringProbeBody is what the health check reads. Deliberately tiny and // deliberately not a status page: it says the edge answered, nothing about // what it serves or who it serves it for. const steeringProbeBody = "carlos steering probe ok\n" // steeringProbeHostFromEnv derives this deployment's steering hostname, or "" // when the deployment does not steer. Read once, at Edge construction (the // gzipEnabled precedent), because a hostname that changes under a running // edge is a redeploy, not a reload. func steeringProbeHostFromEnv() string { if steering.SteeringZoneID() == "" { return "" } domain := strings.ToLower(strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(os.Getenv("PLATFORM_APPS_DOMAIN")), "."))) if domain == "" { // Armed but with nothing to build the name from. The converger says // the same thing on its own pass (desiredState mints no steering // hostname without an apps domain); saying it here too is what tells // an operator reading the EDGE's journal why no probe is answered. log.Printf("edge: steering zone is set but PLATFORM_APPS_DOMAIN is not — no steering hostname to answer; health checks on this box will fail until it is set") return "" } host := "edge." + domain log.Printf("edge: steering probe armed for %s (GET / answers 200; every other path on that host routes normally)", host) return host } // steeringProbe answers the deployment's Route53 health check, reporting // whether it did. // // Narrow on purpose, in all three axes: // // - the HOST must be the steering hostname exactly (case-insensitively, // trailing dot tolerated — DNS names are case-insensitive and a client // may send either form); // - the PATH must be exactly "/" — the health check's own path // (healthCheckPath, internal/steering/route53.go). Anything else on that // host carries on to the ordinary routing path, so this is a reserved // ANSWER, not a reserved hostname; // - the METHOD must be GET or HEAD. Nothing here mutates, so anything else // falls through rather than being handled. // // Callers place it before every other branch — including the fallback // redirect, which would otherwise 302 the check into false health. func (e *Edge) steeringProbe(w http.ResponseWriter, r *http.Request, host string) bool { if e.steeringHost == "" { return false } if !strings.EqualFold(strings.TrimSuffix(host, "."), e.steeringHost) { return false } if r.URL.Path != "/" || (r.Method != http.MethodGet && r.Method != http.MethodHead) { return false } w.Header().Set("Content-Type", "text/plain; charset=utf-8") // A health check must never be answered from anyone's cache: the whole // point of the probe is that it reflects THIS box, right now. w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusOK) if r.Method == http.MethodGet { _, _ = io.WriteString(w, steeringProbeBody) } return true }