feat(egress-proxy): retarget remediation flow (PRD 0017 chunk 3) #30
@@ -169,14 +169,32 @@ def match_route(
|
|||||||
routes: typing.Sequence[Route],
|
routes: typing.Sequence[Route],
|
||||||
request_host: str,
|
request_host: str,
|
||||||
) -> Route | None:
|
) -> Route | None:
|
||||||
"""Return the first route whose `host` matches `request_host`.
|
"""Return the route whose `host` matches `request_host`.
|
||||||
|
|
||||||
Exact match in v1 — globs / wildcards are a follow-up (per PRD
|
Match precedence:
|
||||||
0017 open questions). Hostname comparison is case-insensitive
|
1. Exact (case-insensitive) match on the literal hostname.
|
||||||
because DNS names are case-insensitive."""
|
2. Wildcard match: a route whose host starts with `*.` is a
|
||||||
|
suffix pattern. `*.example.com` matches any host that
|
||||||
|
ends with `.example.com` (so `foo.example.com` and
|
||||||
|
`a.b.example.com`, but NOT the apex `example.com` or
|
||||||
|
`barexample.com`).
|
||||||
|
|
||||||
|
Exact match wins over wildcard so an operator can declare a
|
||||||
|
specific route on top of a broader wildcard (e.g. a
|
||||||
|
`*.github.com` bare-pass + an `api.github.com` route with
|
||||||
|
auth). DNS names are case-insensitive."""
|
||||||
target = request_host.lower()
|
target = request_host.lower()
|
||||||
|
# Pass 1: exact, literal hostname match.
|
||||||
for r in routes:
|
for r in routes:
|
||||||
if r.host.lower() == target:
|
host = r.host.lower()
|
||||||
|
if not host.startswith("*.") and host == target:
|
||||||
|
return r
|
||||||
|
# Pass 2: wildcard suffix match (`*.foo.com` → `.foo.com`).
|
||||||
|
for r in routes:
|
||||||
|
host = r.host.lower()
|
||||||
|
if host.startswith("*."):
|
||||||
|
suffix = host[1:] # keeps the leading `.`
|
||||||
|
if target.endswith(suffix) and target != suffix[1:]:
|
||||||
return r
|
return r
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,64 @@ class TestMatchRoute(unittest.TestCase):
|
|||||||
self.assertIsNone(match_route(self.ROUTES, "evil.api.github.com"))
|
self.assertIsNone(match_route(self.ROUTES, "evil.api.github.com"))
|
||||||
|
|
||||||
|
|
||||||
|
class TestMatchRouteWildcards(unittest.TestCase):
|
||||||
|
"""Wildcard host patterns: `*.foo.com` matches any host that
|
||||||
|
ends with `.foo.com` (subdomains, one level or more)."""
|
||||||
|
|
||||||
|
def test_wildcard_matches_direct_subdomain(self):
|
||||||
|
routes = (Route(host="*.example.com"),)
|
||||||
|
r = match_route(routes, "foo.example.com")
|
||||||
|
self.assertIsNotNone(r)
|
||||||
|
self.assertEqual("*.example.com", r.host)
|
||||||
|
|
||||||
|
def test_wildcard_matches_nested_subdomain(self):
|
||||||
|
routes = (Route(host="*.example.com"),)
|
||||||
|
self.assertIsNotNone(match_route(routes, "a.b.example.com"))
|
||||||
|
|
||||||
|
def test_wildcard_does_not_match_apex(self):
|
||||||
|
routes = (Route(host="*.example.com"),)
|
||||||
|
self.assertIsNone(match_route(routes, "example.com"))
|
||||||
|
|
||||||
|
def test_wildcard_does_not_match_overlapping_suffix(self):
|
||||||
|
# `*.example.com` shouldn't match `barexample.com` — the
|
||||||
|
# match requires `.` before the suffix.
|
||||||
|
routes = (Route(host="*.example.com"),)
|
||||||
|
self.assertIsNone(match_route(routes, "barexample.com"))
|
||||||
|
|
||||||
|
def test_wildcard_case_insensitive(self):
|
||||||
|
routes = (Route(host="*.example.com"),)
|
||||||
|
self.assertIsNotNone(match_route(routes, "FOO.Example.COM"))
|
||||||
|
|
||||||
|
def test_exact_match_wins_over_wildcard(self):
|
||||||
|
# A specific route declared alongside a broader wildcard
|
||||||
|
# should take precedence — operators stack a per-host
|
||||||
|
# config on top of a permissive wildcard this way.
|
||||||
|
routes = (
|
||||||
|
Route(host="*.github.com"),
|
||||||
|
Route(host="api.github.com", auth_scheme="Bearer",
|
||||||
|
token_env="EGRESS_PROXY_TOKEN_0"),
|
||||||
|
)
|
||||||
|
r = match_route(routes, "api.github.com")
|
||||||
|
self.assertIsNotNone(r)
|
||||||
|
self.assertEqual("api.github.com", r.host)
|
||||||
|
self.assertEqual("Bearer", r.auth_scheme)
|
||||||
|
|
||||||
|
def test_exact_wins_regardless_of_route_order(self):
|
||||||
|
# Same as above but with wildcard declared AFTER exact —
|
||||||
|
# exact wins because pass 1 finds it before pass 2 runs.
|
||||||
|
routes = (
|
||||||
|
Route(host="api.github.com", auth_scheme="Bearer",
|
||||||
|
token_env="EGRESS_PROXY_TOKEN_0"),
|
||||||
|
Route(host="*.github.com"),
|
||||||
|
)
|
||||||
|
r = match_route(routes, "api.github.com")
|
||||||
|
self.assertEqual("api.github.com", r.host)
|
||||||
|
|
||||||
|
def test_no_match_falls_through(self):
|
||||||
|
routes = (Route(host="*.example.com"),)
|
||||||
|
self.assertIsNone(match_route(routes, "elsewhere.org"))
|
||||||
|
|
||||||
|
|
||||||
# --- decide --------------------------------------------------------------
|
# --- decide --------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user