[management] Keep the ingress address contract and the ownership fallback

Two follow-ups to canonicalizing the proxy address.

netip accepts a zoned literal where net.ParseIP did not, so "fe80::1%eth0"
started passing validation and would have been stored as a cluster key. An
address scoped to one host's interface cannot name a cluster others reach,
so zones are rejected, as before.

Making the ownership query exact also left the bootstrap check without a
fallback for rows written before canonicalization: a foreign cluster
stored as "BYOP.Account2.Example.com" no longer matches the normalized
address, reads as never declared, and the pin it should refuse goes
through. Split the two callers instead of choosing between them.
IsClusterAddressConflicting stays exact for the per-connect path that
needs the index; HasProxyOutsideAccountAtHost folds case for the
bootstrap, which runs once per account and is the only thing standing
between it and pinning its immutable endpoint to somebody else's cluster.
The settings delete guard already made that trade for the same reason.

Restores the foreign-casing case that went with the exact query, and adds
the zone cases to the ingress test.
This commit is contained in:
mlsmaycon
2026-09-03 12:11:05 +00:00
parent 61e1742885
commit ba3bc8c56f
7 changed files with 70 additions and 6 deletions
@@ -888,7 +888,13 @@ func canonicalProxyAddress(addr string) (string, bool) {
if addr == "" {
return "", false
}
// A zoned literal like "fe80::1%eth0" is scoped to one host's interface,
// so it cannot identify a cluster others reach; net.ParseIP rejected it
// before and netip must not start accepting it.
if ip, err := netip.ParseAddr(addr); err == nil {
if ip.Zone() != "" {
return "", false
}
return ip.String(), true
}
// Folded before punycode conversion, not just after: idna maps the ASCII
@@ -51,6 +51,10 @@ func TestCanonicalProxyAddress(t *testing.T) {
{name: "mixed case ipv6 canonicalised", addr: "2001:DB8::1", canonical: "2001:db8::1", ok: true},
{name: "empty string rejected", addr: "", ok: false},
{name: "space rejected", addr: "eu proxy.example.com", ok: false},
// Scoped to one host's interface, so it cannot name a cluster others
// reach; net.ParseIP rejected these and netip must not accept them.
{name: "zoned ipv6 rejected", addr: "fe80::1%eth0", ok: false},
{name: "unzoned link-local ipv6 accepted", addr: "fe80::1", canonical: "fe80::1", ok: true},
}
for _, tt := range tests {