edge: e2e — a non-home edge answers the validation, and the name mints
The automated shadow of the dev proof that failed as #277: one bucket, two edges, and the host in exactly ONE box's registry. Edge B — the box DNS points at, homing nothing for the name — answers the CA out of the bucket, edge A mints, and the certificate reaches B's mirror and serves a real handshake. The tls-alpn-01 half is proved at the same seam the unit tests use, since the fake CA offers http-01 only, together with its #180 twin: a label under the mirrored catch-all is still refused a token cert. Without the widening this test fails the way production did — "no viable challenge type found" for the domain, after the CA burned an authorization against the box that refused it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+124
−0
internal/edge/certsync_e2e_test.go+124 −0
diff --git a/internal/edge/certsync_e2e_test.go b/internal/edge/certsync_e2e_test.go| index e7f538956..7eef19508 100644 |
| --- a/internal/edge/certsync_e2e_test.go |
| +++ b/internal/edge/certsync_e2e_test.go |
| @@ -1155,3 +1155,127 @@ func TestCertSyncE2EConcurrentRefreshIsRaceFree(t *testing.T) { |
| t.Fatalf("account key seeding raced: %q, %v", got, err) |
| } |
| } |
| + |
| +// ---- #277: the non-home edge answers, and the home-boxed name mints --------- |
| + |
| +// e2eRemoteHomedHost is homed on edge A and absent from edge B's registry — |
| +// the shape the original scenario deliberately did not have (there, B carried |
| +// every host in its own route table). |
| +const e2eRemoteHomedHost = "remote.bab.example.test" |
| + |
| +// The automated shadow of the dev proof that failed as #277: a member points |
| +// a custom domain's A record at edge B, which homes nothing for that name. B |
| +// backhauls to A, A (the issuer) sees the hello and orders, and the CA then |
| +// validates against B — the box whose registry has never heard of the host. |
| +// Before the widening, B refused the challenge, every authorization failed, |
| +// and the name's Let's Encrypt budget went in minutes. After it, B answers |
| +// out of the bucket, A mints, and the certificate reaches B's mirror. |
| +// |
| +// The backhaul leg itself is not re-proved here (homemap_test.go owns it); |
| +// what this pins is that everything CERT-side works with the host in ONE |
| +// box's registry and the other box's home map. |
| +func TestNonHomeEdgeAnswersValidationForRemoteHomedHost(t *testing.T) { |
| + e2eQuietLogs(t) |
| + bucket := t.TempDir() |
| + ca := newFakeACME(t) |
| + |
| + // Edge B: no routes at all for the name — this is the box DNS points at. |
| + edgeB := newE2EEdge(t, bucket, false, ca.directoryURL()) |
| + ca.validateAgainst(edgeB.serveHTTP(t)) |
| + if edgeB.edge.Allowed(e2eRemoteHomedHost) { |
| + t.Fatal("test bug: edge B must not carry the host in its own registry") |
| + } |
| + |
| + // ...but it mirrors the deployment's edges files, in which edge A homes |
| + // the name exactly. That, and only that, is what admits the validation. |
| + homes := newHomeMap(release.NewDirStore(bucket), "edge-b") |
| + homedOn(t, homes, "edge-a", "203.0.113.10", e2eRemoteHomedHost, "*.other.example.test") |
| + edgeB.edge.homes.Store(homes) |
| + |
| + // Edge A: the housekeeper, and the box that homes the route. Its own :80 |
| + // chain is served purely so "the CA never reached the issuer" is an |
| + // assertion rather than an assumption. |
| + edgeA := newE2EEdge(t, bucket, true, ca.directoryURL(), e2eRemoteHomedHost) |
| + edgeA.serveHTTP(t) |
| + |
| + lnB := edgeB.serveTLS(t) |
| + if _, err := e2eDial(lnB, e2eRemoteHomedHost, ca.rootPool()); err == nil { |
| + t.Fatal("edge B answered a handshake for a certificate that does not exist yet") |
| + } |
| + |
| + // The mint, on edge A — exactly what a backhauled hello triggers there. |
| + minted, err := edgeA.getCert(ecdsaHello(e2eRemoteHomedHost)) |
| + if err != nil { |
| + t.Fatalf("the issuer failed to mint a host it homes: %v", err) |
| + } |
| + ca.checkFaults(t) |
| + if minted.Leaf == nil || minted.Leaf.VerifyHostname(e2eRemoteHomedHost) != nil { |
| + t.Fatalf("minted cert does not cover %s", e2eRemoteHomedHost) |
| + } |
| + |
| + t.Run("the validation was answered by the box that homes nothing", func(t *testing.T) { |
| + v, ok := ca.lastValidation() |
| + if !ok { |
| + t.Fatal("the CA never validated anything") |
| + } |
| + if !v.ok || v.host != e2eRemoteHomedHost { |
| + t.Fatalf("validation of %q ok=%v — the body B served was not the key authorization", v.host, v.ok) |
| + } |
| + if n := edgeB.hits.Load(); n == 0 { |
| + t.Fatal("edge B's :80 chain was never reached") |
| + } |
| + if n := edgeA.hits.Load(); n != 0 { |
| + t.Fatalf("edge A's :80 chain served %d request(s) — the CA must never have reached the issuer", n) |
| + } |
| + // It came out of the bucket, live: B never polled, and its own |
| + // autocert handler is shut for this name by the issuer gate. |
| + if n := len(edgeB.mirror.snapshot().entries); n != 0 { |
| + t.Fatalf("edge B's mirror holds %d entry/entries — the read-through is not what was tested", n) |
| + } |
| + if err := edgeB.edge.issuerHostPolicy()(context.Background(), e2eRemoteHomedHost); !errors.Is(err, errNotIssuer) { |
| + t.Fatalf("edge B's HostPolicy = %v, want errNotIssuer — a non-issuer orders nothing, however it is named", err) |
| + } |
| + want := certKey(v.token + httpTokenSuffix) |
| + reads, _ := edgeB.store.snapshot() |
| + if !slices.Contains(reads, want) { |
| + t.Fatalf("edge B read %v, want a live read of %q", reads, want) |
| + } |
| + }) |
| + |
| + t.Run("the tls-alpn-01 twin is admitted for the same name", func(t *testing.T) { |
| + // The fake CA offers http-01 only (its doc says why), so the ALPN half |
| + // is proved at the same seam the unit tests use: a token cert in the |
| + // bucket, an acme-tls/1 hello at edge B for a name only edge A homes. |
| + // Written through a THIRD handle on the bucket, standing in for the |
| + // issuer's publishingCache: edge B's own handle must stay write-free, |
| + // which the last subtest asserts. |
| + putBucket(t, release.NewDirStore(bucket), e2eRemoteHomedHost+alpnTokenSuffix, |
| + hostCertPEM(t, e2eRemoteHomedHost, time.Now().Add(24*time.Hour))) |
| + if c := edgeB.mirror.alpnTokenCert(context.Background(), e2eRemoteHomedHost); c == nil { |
| + t.Fatal("edge B refused a tls-alpn-01 validation for a host edge A homes") |
| + } |
| + // And the label under the mirrored catch-all is still refused (#180). |
| + if c := edgeB.mirror.alpnTokenCert(context.Background(), "invented.other.example.test"); c != nil { |
| + t.Fatal("a label under a mirrored catch-all was served a token cert") |
| + } |
| + }) |
| + |
| + t.Run("the minted cert reaches the non-home edge", func(t *testing.T) { |
| + if err := edgeB.mirror.Refresh(context.Background()); err != nil { |
| + t.Fatalf("one refresh must be enough: %v", err) |
| + } |
| + state, err := e2eDial(lnB, e2eRemoteHomedHost, ca.rootPool()) |
| + if err != nil { |
| + t.Fatalf("edge B must serve the certificate edge A minted: %v", err) |
| + } |
| + if len(state.PeerCertificates) == 0 || !bytes.Equal(state.PeerCertificates[0].Raw, minted.Leaf.Raw) { |
| + t.Fatal("edge B served a certificate that is not the one edge A minted") |
| + } |
| + _, wrote := edgeB.store.snapshot() |
| + if len(wrote) != 0 { |
| + t.Fatalf("a gated edge wrote %v to the control bucket — only the issuer may", wrote) |
| + } |
| + }) |
| + |
| + ca.checkFaults(t) |
| +} |