ipguard

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package ipguard holds the SSRF private/reserved membership test shared by the egress proxy's policy guard and the composer transport guard. MEMBERSHIP ONLY by design: each consumer keeps its own predicate around it since their semantics deliberately differ. Carries only what stdlib lacks (netip.Addr.IsPrivate covers RFC1918 + IPv6 ULA, so those are NOT re-listed in ReservedV4 — they ARE in Liftable, a different job).

Index

Constants

This section is empty.

Variables

View Source
var Liftable = []netip.Prefix{
	netip.MustParsePrefix("10.0.0.0/8"),
	netip.MustParsePrefix("172.16.0.0/12"),
	netip.MustParsePrefix("192.168.0.0/16"),
	netip.MustParsePrefix("fc00::/7"),
	netip.MustParsePrefix("100.64.0.0/10"),
}

Liftable is the private/reserved space an operator may declare an internal hostname's SSRF-guard exception into (SiteConfig's InternalHosts). SECURITY invariant: deliberately narrower than PrivateReserved's full denial set — RFC1918, IPv6 ULA, and CGNAT only. Loopback/link-local/metadata/ unspecified/multicast/NAT64-embedded stay un-liftable by construction.

View Source
var NAT64Prefixes = []netip.Prefix{
	netip.MustParsePrefix("64:ff9b::/96"),
	netip.MustParsePrefix("64:ff9b:1::/48"),
}

NAT64Prefixes are the well-known + local-use NAT64 translation prefixes (RFC 6052 / RFC 8215). SECURITY: an address inside one carries a real IPv4 in its low 32 bits, so a private/metadata target can be smuggled as an IPv6 literal (64:ff9b::a9fe:a9fe -> 169.254.169.254) past every stdlib predicate — every guard must block these wholesale and re-check the embedded v4.

View Source
var NeverLift = []netip.Prefix{
	netip.MustParsePrefix("fd00:ec2::/64"),
	netip.MustParsePrefix("100.100.100.200/32"),
}

NeverLift carves the cloud instance-metadata endpoints out of Liftable: they sit inside the ULA and CGNAT ranges an operator may lift, but lifting one would hand a sandbox the host's cloud credentials.

View Source
var ReservedV4 = []netip.Prefix{
	netip.MustParsePrefix("100.64.0.0/10"),
	netip.MustParsePrefix("0.0.0.0/8"),
	netip.MustParsePrefix("192.0.0.0/24"),
	netip.MustParsePrefix("198.18.0.0/15"),
	netip.MustParsePrefix("255.255.255.255/32"),
	netip.MustParsePrefix("192.0.2.0/24"),
	netip.MustParsePrefix("198.51.100.0/24"),
	netip.MustParsePrefix("203.0.113.0/24"),
	netip.MustParsePrefix("192.88.99.0/24"),
	netip.MustParsePrefix("240.0.0.0/4"),
}

ReservedV4 are the reserved IPv4 ranges every guard denies that netip.Addr.IsPrivate doesn't cover.

SECURITY invariant: canonical surface is the IANA IPv4 Special-Purpose Address Registry, not a remembered RFC list — an entry belongs here when the registry's "Globally Reachable" column is False and no stdlib predicate covers it; diff against the registry, not intuition. Rows that ARE globally reachable (192.31.196.0/24, 192.52.193.0/24, 192.175.48.0/24) stay out.

Order matters only for the reason string: PrivateReserved names the FIRST matching prefix, so 255.255.255.255/32 stays listed ahead of the 240.0.0.0/4 that now contains it.

View Source
var ReservedV6 = []netip.Prefix{
	netip.MustParsePrefix("fec0::/10"),
	netip.MustParsePrefix("2002::/16"),

	netip.MustParsePrefix("2001::/32"),
	netip.MustParsePrefix("2001:2::/48"),
	netip.MustParsePrefix("2001:10::/28"),
	netip.MustParsePrefix("2001:db8::/32"),
	netip.MustParsePrefix("3fff::/20"),
	netip.MustParsePrefix("5f00::/16"),
	netip.MustParsePrefix("100::/64"),
}

ReservedV6 are the reserved IPv6 ranges every guard denies that no stdlib predicate covers (IsPrivate covers fc00::/7, IsLinkLocalUnicast covers fe80::/10). Same canonical surface as ReservedV4.

2002::/16 is denied wholesale here, not parsed in NAT64Prefixes: it embeds a real IPv4 at bits 16..48, not the low 32 NAT64EmbeddedV4 extracts, and 6to4 is deprecated and unroutable anyway.

Functions

func GatewayIPRefused added in v0.7.0

func GatewayIPRefused(ip net.IP) bool

GatewayIPRefused reports whether ip is a kind an operator-configured internal model gateway can never legitimately be: loopback, link-local (metadata address included), unspecified, multicast, or NAT64-embedded. RFC1918/ULA/CGNAT are NOT refused — an internal gateway is expected to live there, so this is a DIFFERENT predicate from PrivateReserved, not a subset of it.

TRUST BOUNDARY: shared body for two consumers whose agreement matters — the control plane validates the configured gateway at boot, and the proxy re-checks the RESOLVED answer per request before dialling it with the brokered model credential.

A nil/!ok address is REFUSED: addrOf fails only on a slice that isn't an address at all, and an unparseable gateway address is not one this may admit.

func InLiftable added in v0.7.0

func InLiftable(ip net.IP) bool

InLiftable reports whether ip falls inside the Liftable set and outside NeverLift.

func NAT64EmbeddedV4 added in v0.3.1

func NAT64EmbeddedV4(ip net.IP) (net.IP, bool)

NAT64EmbeddedV4 returns the IPv4 embedded in ip's low 32 bits and true when ip is inside a NAT64 prefix, else (nil, false). SECURITY: callers must block the prefix wholesale and re-run the embedded v4 through their own v4 guard, so a NAT64-smuggled private/metadata target can't slip past To4()==nil.

func PrivateReserved added in v0.4.3

func PrivateReserved(ip net.IP) (bool, string)

PrivateReserved reports whether ip is in a private/reserved range every guard denies regardless of mode (RFC1918 + IPv6 ULA via stdlib IsPrivate, plus ReservedV4/ReservedV6), naming the matching range for the denial reason. IPv4-mapped IPv6 addresses are unwrapped first.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL