route-proxy: a policy refusal is also not-my-token

autocert checks the host policy before the token and answers 403 — the
internal authority does this for every public name, so mail.novox.be's
challenge died on the internal manager's probe one commit after it
stopped dying on the public one's 404. Both shapes of refusal now fall
through to routing; a fifth test pins the 403 case with a refusing
policy.
This commit is contained in:
2026-09-26 14:26:01 +02:00
parent 345722a4fd
commit dad0a153ff
2 changed files with 28 additions and 2 deletions
+23
View File
@@ -7,6 +7,8 @@ package main
// three behaviours tokenOrRoute exists for. // three behaviours tokenOrRoute exists for.
import ( import (
"context"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "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) { func TestAnOrdinaryPathNeverTouchesTheChallengeMachinery(t *testing.T) {
m := &autocert.Manager{Prompt: autocert.AcceptTOS, Cache: autocert.DirCache(t.TempDir())} m := &autocert.Manager{Prompt: autocert.AcceptTOS, Cache: autocert.DirCache(t.TempDir())}
h := tokenOrRoute(routedTo(t, "routed"), m) h := tokenOrRoute(routedTo(t, "routed"), m)
+5 -2
View File
@@ -458,8 +458,11 @@ func tokenOrRoute(routes http.Handler, managers ...*autocert.Manager) http.Handl
for _, probe := range probes { for _, probe := range probes {
buffered := &probedResponse{header: make(http.Header)} buffered := &probedResponse{header: make(http.Header)}
probe.ServeHTTP(buffered, r) probe.ServeHTTP(buffered, r)
if buffered.status == http.StatusNotFound { // Two shapes of "not mine": 404, a token this manager is not holding — and 403, a
continue // not this manager's token // 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) buffered.replayTo(w)
return return