Read a trusted_proxies entry in the IPv4-mapped form as its IPv4 range (closes #303)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The client address is matched against trusted_proxies as the IPv4 address an IPv4-mapped address holds, so an entry such as ::ffff:10.0.0.0/104 trusted nothing. parseCIDRList, which reads both trusted_proxies and blocked_networks, now keeps such an entry as the IPv4 range it covers and refuses one shorter than /96 naming the setting it reads. parseBlockedNetworks, which did this for blocked_networks alone, is gone. Model: opus-5-5
This commit is contained in:
@@ -709,16 +709,22 @@ Key settings in more detail:
|
||||
omitted key defaults to the RFC 1918 private ranges (`10.0.0.0/8`,
|
||||
`172.16.0.0/12`, `192.168.0.0/16`), since pixa is deployed behind a proxy on a
|
||||
private network; an explicitly empty list (`[]`) trusts no one, and an
|
||||
explicit list replaces the default. An invalid CIDR aborts startup. Set this
|
||||
to the address pixa sees for requests that come through your proxy, such as
|
||||
`172.17.0.1/32`, when the defaults do not cover it, or to trust nothing else
|
||||
(see the login limit and `image_requests_per_minute` under Routes). For a
|
||||
proxy on the Docker host that connects to pixa over `127.0.0.1`, that address
|
||||
is the gateway of the container's Docker network (`172.17.0.1` on the default
|
||||
bridge), not the proxy's own address; a proxy that connects through another of
|
||||
the host's addresses is seen with that address. To be sure which address it
|
||||
is, set this to `[]` (or `PIXA_TRUSTED_PROXIES` to empty), send a request
|
||||
through the proxy, and read `remoteIP` in pixa's request log line for it
|
||||
explicit list replaces the default. An invalid CIDR aborts startup. An IPv6
|
||||
address in the IPv4-mapped form (`::ffff:a.b.c.d`) is matched only as the IPv4
|
||||
address it holds, so an IPv6 range such as `::/0` trusts neither it nor a
|
||||
plain IPv4 address. An entry written in that form is read as the IPv4 range it
|
||||
covers, so `::ffff:10.0.0.0/104` trusts what `10.0.0.0/8` trusts; such an
|
||||
entry shorter than `/96` covers more than IPv4 addresses and aborts startup.
|
||||
Set this to the address pixa sees for requests that come through your proxy,
|
||||
such as `172.17.0.1/32`, when the defaults do not cover it, or to trust
|
||||
nothing else (see the login limit and `image_requests_per_minute` under
|
||||
Routes). For a proxy on the Docker host that connects to pixa over
|
||||
`127.0.0.1`, that address is the gateway of the container's Docker network
|
||||
(`172.17.0.1` on the default bridge), not the proxy's own address; a proxy
|
||||
that connects through another of the host's addresses is seen with that
|
||||
address. To be sure which address it is, set this to `[]` (or
|
||||
`PIXA_TRUSTED_PROXIES` to empty), send a request through the proxy, and read
|
||||
`remoteIP` in pixa's request log line for it
|
||||
- `upstream_fetch_timeout` — time allowed for one fetch from an upstream host,
|
||||
as a duration such as `30s` (the default) or `2m`
|
||||
- `upstream_max_response_size` — largest upstream response accepted, in bytes;
|
||||
|
||||
@@ -30,6 +30,10 @@ P2: security: per-origin rate limiting
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-19 a `trusted_proxies` entry in the IPv4-mapped form trusted nothing;
|
||||
it is now read as the IPv4 range it covers by the same code that reads
|
||||
`blocked_networks`, and such an entry shorter than `/96` aborts startup
|
||||
(closes #303).
|
||||
- 2026-10-18 a `blocked_networks` entry in the IPv4-mapped form refused nothing;
|
||||
`::ffff:10.0.0.0/104` is now read as `10.0.0.0/8`, and such an entry shorter
|
||||
than `/96` aborts startup (closes #301).
|
||||
|
||||
+20
-32
@@ -272,7 +272,7 @@ func newFromSmartConfig(sc *smartconfig.Config) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockedNetworks, err := parseBlockedNetworks(sc)
|
||||
blockedNetworks, err := parseCIDRList(sc, keyBlockedNetworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1249,6 +1249,13 @@ func defaultTrustedProxies() []netip.Prefix {
|
||||
// wrong type, an empty entry, a non-string entry, or an unparseable CIDR
|
||||
// aborts startup naming the key and the offending value; the default
|
||||
// (an empty list) applies only to an omitted key.
|
||||
//
|
||||
// An entry written in the IPv4-mapped form (::ffff:a.b.c.d) is kept as the
|
||||
// IPv4 range it covers: ::ffff:10.0.0.0/104 is kept as 10.0.0.0/8. Both
|
||||
// blocked_networks and trusted_proxies are matched against an IPv4-mapped
|
||||
// address as the IPv4 address it holds, so the entry as written would match
|
||||
// nothing. Such an entry shorter than /96 covers more than the IPv4
|
||||
// addresses and aborts startup.
|
||||
func parseCIDRList(sc *smartconfig.Config, key string) ([]netip.Prefix, error) {
|
||||
raw, ok := lookupValue(sc, key)
|
||||
if !ok {
|
||||
@@ -1264,6 +1271,10 @@ func parseCIDRList(sc *smartconfig.Config, key string) ([]netip.Prefix, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// An IPv4-mapped address is the 96 bits of ::ffff:0:0/96 followed by
|
||||
// the 32 bits of the IPv4 address it holds.
|
||||
const mappedPrefixBits = 96
|
||||
|
||||
prefixes := make([]netip.Prefix, 0, len(entries))
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -1273,40 +1284,17 @@ func parseCIDRList(sc *smartconfig.Config, key string) ([]netip.Prefix, error) {
|
||||
settingName(key), entry, errNotAValidCIDR)
|
||||
}
|
||||
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
}
|
||||
|
||||
// parseBlockedNetworks parses blocked_networks with parseCIDRList, and keeps
|
||||
// an entry written in the IPv4-mapped form (::ffff:a.b.c.d) as the IPv4
|
||||
// range it covers: ::ffff:10.0.0.0/104 is kept as 10.0.0.0/8. The fetcher
|
||||
// matches an IPv4-mapped address as the IPv4 address it holds, so the entry
|
||||
// as written would match nothing. Such an entry shorter than /96 covers more
|
||||
// than the IPv4 addresses and aborts startup.
|
||||
func parseBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) {
|
||||
prefixes, err := parseCIDRList(sc, keyBlockedNetworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// An IPv4-mapped address is the 96 bits of ::ffff:0:0/96 followed by
|
||||
// the 32 bits of the IPv4 address it holds.
|
||||
const mappedPrefixBits = 96
|
||||
|
||||
for i, prefix := range prefixes {
|
||||
if !prefix.Addr().Is4In6() {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefix.Bits() < mappedPrefixBits {
|
||||
if prefix.Addr().Is4In6() && prefix.Bits() < mappedPrefixBits {
|
||||
return nil, fmt.Errorf("%s: value %q is %w",
|
||||
settingName(keyBlockedNetworks), prefix, errMappedTooShort)
|
||||
settingName(key), entry, errMappedTooShort)
|
||||
}
|
||||
|
||||
prefixes[i] = netip.PrefixFrom(prefix.Addr().Unmap(),
|
||||
prefix.Bits()-mappedPrefixBits)
|
||||
if prefix.Addr().Is4In6() {
|
||||
prefix = netip.PrefixFrom(prefix.Addr().Unmap(),
|
||||
prefix.Bits()-mappedPrefixBits)
|
||||
}
|
||||
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
|
||||
Reference in New Issue
Block a user