Routing and TLS disagree on wildcard depth, so a multi-label name is served the self-signed fallback #56
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
From the v2.0.0 security audit.
host_matches(src/proxy.rs:76) matches a wildcard at any depth —prefix.ends_with('.')is satisfied bydeep.sub., and the repo's own test asserts*.example.commatchesdeep.sub.example.com.l4::match_routeuses the same rule.The TLS side is single-label:
SniResolver::lookupbuilds exactly one wildcard candidate from the first dot, andcert_manager::covering_wildcardis single-label too. That side is the RFC 6125 one — a wildcard covers exactly one label — which is why Let's Encrypt genuinely will not covera.b.oates.wswith*.oates.ws.Consequence
A service with
routing_hosts = ["*.oates.ws"]. A request fora.b.oates.wsroutes to it. The resolver's candidates area.b.oates.ws(absent) and*.b.oates.ws(absent), so nothing matches, no tombstone is hit, andlookupfalls through to the self-signededge-router.localfallback — which is on by default.That is a name covered by a route host being served the fallback, which the #38 invariant says must not happen. It is not a wrong trusted identity, so a verifying client fails; the harm is that a non-verifying client proceeds and the operator sees the route "working" on a bogus identity instead of a refused handshake.
Also note the doc comment on
host_matchesclaims the any-depth behaviour mirrors "wildcard-cert SAN semantics". It does not — that is precisely the single-label rule.Fix
Make
host_matchessingle-label, so routing agrees with what a certificate can actually cover. Breaking for anyone relying on multi-label wildcard routing, and v2.0.0 is already breaking — but it was raised after the forwarded-header fixes landed, so it is a decision rather than something to slip in.The alternative, making the TLS side multi-label, is wrong: there is no certificate to find, so it would only move the failure.
Needs a test asserting the three consumers agree, since the current divergence is asserted by a test.
Fixed in
5362126.host_matchesnow covers exactly one label, so routing agrees with what a certificate can cover.The multi-label assertion in
host_matches_wildcard_is_explicitwas flipped, which this issue named as part of the fix. Added.example.com(an empty label) as a negative case while there.The test that matters is new and cross-module:
routing_and_certificate_coverage_agree_on_wildcard_depthassertsproxy::host_matchesandcert_manager::covering_wildcardgive the same answer for a covered name, a two-label name and the apex. The previous divergence was asserted by a test rather than merely unnoticed, so pinning the agreement is the part that stops it recurring.l4::match_routeshareshost_matches, so SNI matching narrowed with it —test_wildcard_does_not_match_bare_apexstill passes.Breaking: a route relying on multi-label wildcard matching stops matching; write the intermediate wildcard (
*.b.oates.ws) or an exact host. Needs a release-notes line.354 tests green, clippy clean, fmt clean in
rust:1.98.0-bookworm.