scripts: region service gate, proxy-proof, proved against a real backhaul (nearest-region §17 step 6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+141
−0
internal/edge/servicegate_test.go+48 −0region_service_gate_test.go+50 −0scripts/region-service-gate.sh+43 −0
diff --git a/internal/edge/servicegate_test.go b/internal/edge/servicegate_test.go| new file mode 100644 |
| index 000000000..de7808661 |
| --- /dev/null |
| +++ b/internal/edge/servicegate_test.go |
| @@ -0,0 +1,48 @@ |
| +package edge |
| + |
| +import ( |
| + "context" |
| + "fmt" |
| + "net" |
| + "net/http" |
| + "net/http/httptest" |
| + "os" |
| + "os/exec" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// The gate script, run against this package's own backhaul path: a far |
| +// edge serving a remote-homed host passes; the same edge with no mesh key |
| +// mirrored answers unknown-host (edge.go:2699, :1428) and the gate FAILS. |
| +func TestServiceGateAgainstARealBackhaul(t *testing.T) { |
| + if _, err := exec.LookPath("curl"); err != nil { |
| + t.Skip("curl not installed") |
| + } |
| + const version = "abc1234" |
| + e, m, _ := hopEdge(t, "annie", "local.test") |
| + _, eip := fakeHomeEdge(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + w.Header().Set("X-Carlos-Version", version) |
| + fmt.Fprint(w, "ok") |
| + })) |
| + homedOn(t, m, "betty", eip, "example.com") |
| + gate := func() (string, error) { |
| + ts := httptest.NewTLSServer(e) |
| + defer ts.Close() |
| + h, p, _ := net.SplitHostPort(ts.Listener.Addr().String()) |
| + cmd := exec.Command("../../scripts/region-service-gate.sh", "example.com", version, h) |
| + cmd.Env = append(os.Environ(), "GATE_PORT="+p, "GATE_INSECURE=1", "GATE_SKIP_PUBLIC=1") |
| + out, err := cmd.CombinedOutput() |
| + return string(out), err |
| + } |
| + if out, err := gate(); err != nil { |
| + t.Fatalf("a working backhaul failed the gate: %v %s", err, out) |
| + } |
| + if err := m.s.Delete(context.Background(), meshKeyKey); err != nil { |
| + t.Fatal(err) |
| + } |
| + refreshHomeMap(t, m) |
| + if out, err := gate(); err == nil || !strings.Contains(out, "FAIL") { |
| + t.Fatalf("a backhaul with no mesh key passed the gate: %s", out) |
| + } |
| +} |
diff --git a/region_service_gate_test.go b/region_service_gate_test.go| new file mode 100644 |
| index 000000000..d31d970e1 |
| --- /dev/null |
| +++ b/region_service_gate_test.go |
| @@ -0,0 +1,50 @@ |
| +package main |
| + |
| +import ( |
| + "net" |
| + "net/http" |
| + "net/http/httptest" |
| + "os/exec" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// spec §17 step 6 (codex R4-3, plan-R1 4): the gate fails on a 404 and on a |
| +// version mismatch, and a proxy in the environment cannot route around the |
| +// edge it names. |
| +func TestRegionServiceGate(t *testing.T) { |
| + const version = "abc1234" |
| + ok := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + w.Header().Set("X-Carlos-Version", version) |
| + })) |
| + defer ok.Close() |
| + unknown := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + http.NotFound(w, r) |
| + })) |
| + defer unknown.Close() |
| + hostPort := func(s *httptest.Server) (string, string) { |
| + h, p, _ := net.SplitHostPort(s.Listener.Addr().String()) |
| + return h, p |
| + } |
| + run := func(srv *httptest.Server, want string, extraEnv ...string) (string, error) { |
| + h, p := hostPort(srv) |
| + cmd := exec.Command("./scripts/region-service-gate.sh", "probe.example", want, h) |
| + cmd.Env = append(cmd.Environ(), append([]string{"GATE_PORT=" + p, "GATE_INSECURE=1", "GATE_SKIP_PUBLIC=1"}, extraEnv...)...) |
| + out, err := cmd.CombinedOutput() |
| + return string(out), err |
| + } |
| + if out, err := run(ok, version); err != nil { |
| + t.Fatalf("a serving edge failed the gate: %v %s", err, out) |
| + } |
| + if out, err := run(unknown, version); err == nil || !strings.Contains(out, "FAIL") { |
| + t.Fatalf("an unknown-host 404 passed: %s", out) |
| + } |
| + if out, err := run(ok, "other"); err == nil || !strings.Contains(out, "FAIL") { |
| + t.Fatalf("a version mismatch passed: %s", out) |
| + } |
| + // A proxy that refuses everything: honoured, it would fail every check. |
| + dead := "http://127.0.0.1:9" |
| + if out, err := run(ok, version, "HTTPS_PROXY="+dead, "https_proxy="+dead, "ALL_PROXY="+dead, "all_proxy="+dead); err != nil { |
| + t.Fatalf("the gate went through a proxy: %v %s", err, out) |
| + } |
| +} |
diff --git a/scripts/region-service-gate.sh b/scripts/region-service-gate.sh| new file mode 100755 |
| index 000000000..ea6311f4a |
| --- /dev/null |
| +++ b/scripts/region-service-gate.sh |
| @@ -0,0 +1,43 @@ |
| +#!/usr/bin/env bash |
| +# scripts/region-service-gate.sh <probe-host> <expected-version> [<edge-eip> ...] |
| +# |
| +# Nearest-region spec §17 step 6: before CONSOLE_APP_REGIONS goes on, every |
| +# eligible region must serve a fresh probe instance through the REAL public |
| +# path — from outside through public DNS (skip with GATE_SKIP_PUBLIC=1), and |
| +# through every armed edge that is NOT the instance's box. Passes only on |
| +# 2xx with X-Carlos-Version equal to the deployed build. Proxies are |
| +# disabled and ~/.curlrc ignored, and each explicit-edge check verifies it |
| +# connected to the EIP it named, so no intermediary can stand in for an |
| +# edge. GATE_PORT and GATE_INSECURE exist for tests only. |
| +set -uo pipefail |
| +host="${1:?usage: region-service-gate.sh <probe-host> <expected-version> [<edge-eip> ...]}" |
| +want="${2:?usage: region-service-gate.sh <probe-host> <expected-version> [<edge-eip> ...]}" |
| +shift 2 |
| +port="${GATE_PORT:-443}" |
| +insecure="" |
| +[ "${GATE_INSECURE:-}" = 1 ] && insecure="-k" |
| +unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY all_proxy |
| +hdr="$(mktemp "${TMPDIR:-/tmp}/region-gate.XXXXXX")" |
| +trap 'rm -f "$hdr"' EXIT |
| +fail=0 |
| +check() { # <label> <expected-ip or ""> [curl args...] |
| + local label="$1" eip="$2"; shift 2 |
| + local ip status version |
| + # -q must be first: it stops curl reading ~/.curlrc. $insecure is |
| + # unquoted on purpose so that empty expands to nothing (bash 3.2 safe). |
| + ip="$(curl -q -s $insecure --noproxy '*' -D "$hdr" -o /dev/null -w '%{remote_ip}' --max-time 20 "$@" "https://$host:$port/")" || { |
| + echo "FAIL $label: request failed"; fail=1; return; } |
| + if [ -n "$eip" ] && [ "$ip" != "$eip" ]; then |
| + echo "FAIL $label: connected to $ip, not $eip"; fail=1; return |
| + fi |
| + status="$(awk 'toupper($1) ~ /^HTTP/ {s=$2} END {print s}' "$hdr")" |
| + version="$(awk -F': *' 'tolower($1)=="x-carlos-version" {gsub(/\r/,"",$2); print $2}' "$hdr")" |
| + if [[ "$status" != 2* ]]; then echo "FAIL $label: status $status"; fail=1; return; fi |
| + if [ "$version" != "$want" ]; then echo "FAIL $label: X-Carlos-Version '$version', want '$want'"; fail=1; return; fi |
| + echo "ok $label: $status $version" |
| +} |
| +[ "${GATE_SKIP_PUBLIC:-}" = 1 ] || check "public DNS" "" |
| +for eip in "$@"; do |
| + check "via edge $eip" "$eip" --resolve "$host:$port:$eip" |
| +done |
| +exit "$fail" |