diff --git a/examples/route-proxy/challenge_test.go b/examples/route-proxy/challenge_test.go index 6919f24..3afd141 100644 --- a/examples/route-proxy/challenge_test.go +++ b/examples/route-proxy/challenge_test.go @@ -7,6 +7,8 @@ package main // three behaviours tokenOrRoute exists for. import ( + "context" + "fmt" "net/http" "net/http/httptest" "os" @@ -73,6 +75,27 @@ func TestASecondAuthorityIsProbedBeforeRouting(t *testing.T) { } } +func TestAnAuthorityWhosePolicyRefusesTheNameIsProbedPast(t *testing.T) { + // autocert checks the host policy before the token and answers 403 — the internal authority + // does this for every public name. A policy refusal is as much "not mine" as a missing token: + // the request must still reach plain routing, where the workload's own ACME client answers. + refusing := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache(t.TempDir()), + HostPolicy: func(ctx context.Context, host string) error { + return fmt.Errorf("no internal-only route for %q in this mesh", host) + }, + } + h := tokenOrRoute(routedTo(t, "the workload answered"), refusing) + + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest("GET", "http://mail.example/.well-known/acme-challenge/mailus-token", nil)) + + if rec.Code != http.StatusOK || rec.Body.String() != "the workload answered" { + t.Fatalf("a policy refusal must fall through to routing; got %d %q", rec.Code, rec.Body.String()) + } +} + func TestAnOrdinaryPathNeverTouchesTheChallengeMachinery(t *testing.T) { m := &autocert.Manager{Prompt: autocert.AcceptTOS, Cache: autocert.DirCache(t.TempDir())} h := tokenOrRoute(routedTo(t, "routed"), m) diff --git a/examples/route-proxy/main.go b/examples/route-proxy/main.go index 1e775f0..38d9308 100644 --- a/examples/route-proxy/main.go +++ b/examples/route-proxy/main.go @@ -458,8 +458,11 @@ func tokenOrRoute(routes http.Handler, managers ...*autocert.Manager) http.Handl for _, probe := range probes { buffered := &probedResponse{header: make(http.Header)} probe.ServeHTTP(buffered, r) - if buffered.status == http.StatusNotFound { - continue // not this manager's token + // Two shapes of "not mine": 404, a token this manager is not holding — and 403, a + // name its host policy would never certify at all (autocert checks the policy before + // the token, so the internal authority answers 403 for every public name). + if buffered.status == http.StatusNotFound || buffered.status == http.StatusForbidden { + continue } buffered.replayTo(w) return