Say how to allow a refused private target address (closes #398)
check / check (push) Waiting to run
check / check (push) Waiting to run
Adding or editing an http or slack target whose address is private or reserved was refused with no hint that the refusal is deliberate or that it can be lifted. The refusal now adds that such addresses are refused by default and that the server's ALLOWED_EGRESS_CIDRS setting allows named networks, naming the README section "Allowing egress to your own network". Metadata refusals do not get it. The default blocklist's public addresses move to a list of their own, still checked after the allowlist, and are refused as cloud metadata addresses. The private-and-reserved error is exported as ErrBlockedPrivateOrReservedIP so the handler can tell them apart. Model: opus-5-5
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -23,6 +24,10 @@ const (
|
||||
metadataIP = "169.254.169.254"
|
||||
metadataURL = "http://" + metadataIP + "/latest/meta-data/"
|
||||
|
||||
// linkLocalIPv4 is the IPv4 link-local block, which holds
|
||||
// metadataIP.
|
||||
linkLocalIPv4 = "169.254.0.0/16"
|
||||
|
||||
// loopbackHookURL is a target on this host: blocked by
|
||||
// default, reachable only once an operator allowlists
|
||||
// loopback.
|
||||
@@ -237,7 +242,7 @@ func linkLocalRefusedCases() []metadataAlwaysRefusedCase {
|
||||
},
|
||||
{
|
||||
name: "whole link-local block",
|
||||
allow: "169.254.0.0/16",
|
||||
allow: linkLocalIPv4,
|
||||
target: metadataURL,
|
||||
},
|
||||
{
|
||||
@@ -412,6 +417,9 @@ func TestGuardAllowlist_AzureWireServerReopenable(t *testing.T) {
|
||||
"WireServer must be refused by the default blocklist, "+
|
||||
"which an allowlist can override",
|
||||
)
|
||||
require.NotErrorIs(t, err, delivery.ErrBlockedPrivateOrReservedIP,
|
||||
"WireServer is public, not private or reserved",
|
||||
)
|
||||
|
||||
assertDialRefused(t, defaultGuard, target)
|
||||
|
||||
@@ -496,7 +504,7 @@ func TestAlwaysBlockedNetworks_PinnedSet(t *testing.T) {
|
||||
want := []string{
|
||||
// IPv4 link-local: the 169.254.169.254 metadata
|
||||
// service on AWS, Azure and others.
|
||||
"169.254.0.0/16",
|
||||
linkLocalIPv4,
|
||||
// IPv6 link-local.
|
||||
"fe80::/10",
|
||||
// AWS IPv6 IMDS, inside the ULA space an operator may
|
||||
@@ -526,6 +534,76 @@ func TestAlwaysBlockedNetworks_PinnedSet(t *testing.T) {
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// TestDefaultBlocklist_PinnedSet pins the default blocklist, its
|
||||
// private and reserved ranges and its public addresses together,
|
||||
// and how ALLOWED_EGRESS_CIDRS opens each entry: listing an entry
|
||||
// opens it unless the unconditional set also holds it.
|
||||
func TestDefaultBlocklist_PinnedSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
cidr string
|
||||
reopenable bool
|
||||
}{
|
||||
{"127.0.0.0/8", true},
|
||||
{"10.0.0.0/8", true},
|
||||
{"172.16.0.0/12", true},
|
||||
{"192.168.0.0/16", true},
|
||||
{linkLocalIPv4, false},
|
||||
{"0.0.0.0/8", true},
|
||||
{"100.64.0.0/10", true},
|
||||
{"192.0.0.0/24", true},
|
||||
{"192.0.2.0/24", true},
|
||||
{"198.18.0.0/15", true},
|
||||
{"198.51.100.0/24", true},
|
||||
{"203.0.113.0/24", true},
|
||||
{"224.0.0.0/4", true},
|
||||
{"240.0.0.0/4", true},
|
||||
{"::1/128", true},
|
||||
{"fc00::/7", true},
|
||||
{"fe80::/10", false},
|
||||
{"168.63.129.16/32", true},
|
||||
}
|
||||
|
||||
want := make([]string, 0, len(tests))
|
||||
for _, tt := range tests {
|
||||
want = append(want, tt.cidr)
|
||||
}
|
||||
|
||||
nets := slices.Concat(
|
||||
delivery.ExportBlockedNetworks(),
|
||||
delivery.ExportBlockedPublicNetworks(),
|
||||
)
|
||||
|
||||
got := make([]string, 0, len(nets))
|
||||
for _, n := range nets {
|
||||
got = append(got, n.String())
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, want, got)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.cidr, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prefix := netip.MustParsePrefix(tt.cidr)
|
||||
ip := net.IP(prefix.Addr().AsSlice())
|
||||
|
||||
require.Error(t,
|
||||
delivery.NewTestGuard().ExportCheckIP(ip),
|
||||
"the default guard must refuse %s", ip,
|
||||
)
|
||||
|
||||
err := delivery.NewTestGuard(prefix).ExportCheckIP(ip)
|
||||
if tt.reopenable {
|
||||
assert.NoError(t, err, "listing %s must open it", tt.cidr)
|
||||
} else {
|
||||
assert.Error(t, err, "listing %s must not open it", tt.cidr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// requireLoopback fails the test unless rawURL's host is a
|
||||
// loopback address, so the allowlist test cannot silently stop
|
||||
// exercising a blocked range.
|
||||
|
||||
Reference in New Issue
Block a user