fix(egress-proxy-addon): wildcard hosts also match the apex
`*.example.com` now matches `example.com` itself in addition to
every subdomain. RFC 6125 TLS-wildcard semantics excluded the
apex; an allowlist's natural reading of `*.example.com` is "all
of example.com" — and the pipelock mirror already strips
`*.example.com` to `example.com`, so without the apex match the
two layers disagreed (pipelock allowed the apex, egress-proxy
blocked it).
Behavior:
- `*.example.com` matches `example.com` (apex)
- `*.example.com` matches `foo.example.com` (subdomain)
- `*.example.com` matches `a.b.example.com` (nested)
- `*.example.com` does NOT match `barexample.com` (label
boundary required)
Test renamed: `test_wildcard_does_not_match_apex` →
`test_wildcard_matches_apex`. 395 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -174,10 +174,15 @@ def match_route(
|
|||||||
Match precedence:
|
Match precedence:
|
||||||
1. Exact (case-insensitive) match on the literal hostname.
|
1. Exact (case-insensitive) match on the literal hostname.
|
||||||
2. Wildcard match: a route whose host starts with `*.` is a
|
2. Wildcard match: a route whose host starts with `*.` is a
|
||||||
suffix pattern. `*.example.com` matches any host that
|
suffix pattern that covers the apex AND every subdomain.
|
||||||
ends with `.example.com` (so `foo.example.com` and
|
`*.example.com` matches `example.com`, `foo.example.com`,
|
||||||
`a.b.example.com`, but NOT the apex `example.com` or
|
and `a.b.example.com`, but NOT `barexample.com` (the
|
||||||
`barexample.com`).
|
label boundary `.` is required when matching a
|
||||||
|
subdomain). This is intentionally more permissive than
|
||||||
|
RFC 6125 TLS-wildcard semantics — an allowlist's natural
|
||||||
|
reading of `*.example.com` is "all of example.com",
|
||||||
|
apex included, and matches what the pipelock mirror does
|
||||||
|
(strips `*.example.com` → `example.com`).
|
||||||
|
|
||||||
Exact match wins over wildcard so an operator can declare a
|
Exact match wins over wildcard so an operator can declare a
|
||||||
specific route on top of a broader wildcard (e.g. a
|
specific route on top of a broader wildcard (e.g. a
|
||||||
@@ -189,12 +194,12 @@ def match_route(
|
|||||||
host = r.host.lower()
|
host = r.host.lower()
|
||||||
if not host.startswith("*.") and host == target:
|
if not host.startswith("*.") and host == target:
|
||||||
return r
|
return r
|
||||||
# Pass 2: wildcard suffix match (`*.foo.com` → `.foo.com`).
|
# Pass 2: wildcard match — apex + every subdomain.
|
||||||
for r in routes:
|
for r in routes:
|
||||||
host = r.host.lower()
|
host = r.host.lower()
|
||||||
if host.startswith("*."):
|
if host.startswith("*."):
|
||||||
suffix = host[1:] # keeps the leading `.`
|
suffix = host[2:] # strip the `*.`
|
||||||
if target.endswith(suffix) and target != suffix[1:]:
|
if target == suffix or target.endswith("." + suffix):
|
||||||
return r
|
return r
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -162,9 +162,13 @@ class TestMatchRouteWildcards(unittest.TestCase):
|
|||||||
routes = (Route(host="*.example.com"),)
|
routes = (Route(host="*.example.com"),)
|
||||||
self.assertIsNotNone(match_route(routes, "a.b.example.com"))
|
self.assertIsNotNone(match_route(routes, "a.b.example.com"))
|
||||||
|
|
||||||
def test_wildcard_does_not_match_apex(self):
|
def test_wildcard_matches_apex(self):
|
||||||
|
# Allowlist semantics: `*.example.com` covers
|
||||||
|
# `example.com` itself + every subdomain. Matches what
|
||||||
|
# the pipelock mirror does (strips `*.example.com` →
|
||||||
|
# `example.com`) so the two layers agree.
|
||||||
routes = (Route(host="*.example.com"),)
|
routes = (Route(host="*.example.com"),)
|
||||||
self.assertIsNone(match_route(routes, "example.com"))
|
self.assertIsNotNone(match_route(routes, "example.com"))
|
||||||
|
|
||||||
def test_wildcard_does_not_match_overlapping_suffix(self):
|
def test_wildcard_does_not_match_overlapping_suffix(self):
|
||||||
# `*.example.com` shouldn't match `barexample.com` — the
|
# `*.example.com` shouldn't match `barexample.com` — the
|
||||||
|
|||||||
Reference in New Issue
Block a user