Documentation
¶
Overview ¶
Package clientip is the ONE answer to "which address is this request from?".
Why it exists ¶
Three copies of this question lived in the code (api/middleware.go, auth/audit_middleware.go, api/handlers/drop.go), and all three believed X-Forwarded-For from ANY caller. That was harmless while the address only labelled a log line. It is not harmless once the address decides something: the sign-in throttle counts failures per address and exempts an allow-listed one, so a header the client writes itself would let an attacker (a) rotate through forged addresses and never hit the per-address limit, and (b) claim an allow-listed address and walk past every lock.
The rule ¶
The socket's peer is the truth. X-Forwarded-For and X-Real-IP are read ONLY when the peer is a configured trusted proxy, and then the forwarded chain is walked from its trusted end (right to left), skipping further trusted proxies, until the first address that is not one: that is the address the outermost trusted proxy actually saw. The leftmost entry — what the old code took — is whatever the client wrote and is never preferred.
The trusted list is the `login.trusted_proxies` setting, else FILEX_TRUSTED_PROXIES, else `auto` (the setting wins over the environment, which wins over the default). Besides addresses and CIDR networks it takes four words:
auto what filex works out about where it runs (auto.go) loopback netip.Addr.IsLoopback — this machine private netip.Addr.IsPrivate — RFC 1918 / RFC 4193 private ranges link-local netip.Addr.IsLinkLocalUnicast
Unset, it is `auto`: loopback on a plain install and with host networking; in a container on its own network, loopback plus the container networks it is attached to, never their gateways (what the host relays arrives from the gateway), never its own addresses (what rootless Podman relays arrives from them) and never a network that puts it on the LAN (macvlan, ipvlan). A proxy anywhere else - on the host through a published port, on another machine, an ingress controller on another Kubernetes node - is named by hand ("auto, 192.0.2.10"); a peer that sends X-Forwarded-For without being trusted is remembered (forwarders.go) so the Sign-in security page can name it. `none` trusts no proxy. A deployment whose proxy chain has PUBLIC hops (Cloudflare's edge in front of a proxy) lists those ranges too, or every visitor resolves to the edge's address and shares one per-address counter.
Until 0.50 the default was loopback, private and link-local: every private address, the LAN included, could name the client.
⚠ The classes are the standard library's definition, not a list of ranges kept here: an address is compared after unmapping (an IPv4-mapped IPv6 address is the IPv4 address it carries), so `::ffff:<private IPv4>` is private exactly as the IPv4 address is.
Protocol servers (FTP, SFTP) have no headers: their peer is the client, and PeerOf is the whole answer.
Index ¶
- Constants
- func FromContext(ctx context.Context) string
- func FromRequest(r *http.Request) string
- func IsAutoRelay(ip netip.Addr) bool
- func Observe(r *http.Request)
- func PeerOf(remote string) string
- func PeerTrusted(r *http.Request) bool
- func ProxyItself(r *http.Request) bool
- func ResetForwarders()
- func SetAutoResolver(fn func() AutoResolution) (restore func())
- func SetSource(fn func() *Set) (restore func())
- func WithIP(ctx context.Context, ip string) context.Context
- type AutoInterface
- type AutoResolution
- type Classes
- type Forwarder
- type Iface
- type Set
- type System
Constants ¶
const ( EnvPlain = "plain" EnvContainer = "container" EnvHostNetwork = "host-network" EnvKubernetes = "kubernetes" EnvPodmanRootless = "podman-rootless" EnvUnknown = "unknown" )
The environments `auto` tells apart (AutoResolution.Environment).
const ( RuntimeDocker = "docker" RuntimePodman = "podman" RuntimeKubernetes = "kubernetes" RuntimeContainerd = "containerd" RuntimeOther = "container" )
The container runtimes the markers name (AutoResolution.Runtime).
const ( ReasonContainerNetwork = "container-network" // a veth on a container network: its subnet is trusted ReasonLAN = "lan" // macvlan / ipvlan: the container sits on the LAN ReasonTunnel = "tunnel" // tun/tap: user-mode networking or a VPN ReasonHost = "host" // a physical NIC or a bridge: the host's own network ReasonLinkLocal = "link-local-only" // a veth with nothing but link-local addresses ReasonDown = "down" // not up ReasonRootless = "rootless" // rootless Podman: nothing but loopback ReasonOther = "other" // another kind of interface )
Why an interface is or is not trusted (AutoInterface.Reason).
const ( // WarnUnreadable: something the answer depends on could not be read // (the container markers, the interfaces, their kinds, the routes). WarnUnreadable = "unreadable" // WarnAmbiguous: what was read contradicts itself (a container network // next to a bridge of the host's kind, with no physical NIC to settle it). WarnAmbiguous = "ambiguous" )
The warnings `auto` can raise (AutoResolution.Warning). Each means it fell back to loopback only.
const ( WordAuto = "auto" WordLoopback = "loopback" WordPrivate = "private" WordLinkLocal = "link-local" WordNone = "none" )
The words a trusted-proxy list understands besides addresses and networks.
const (
// MaxForwarders is how many untrusted forwarders are remembered.
MaxForwarders = 256
)
Variables ¶
This section is empty.
Functions ¶
func FromContext ¶
FromContext is the address WithIP carried, or "".
func FromRequest ¶
FromRequest is the address the request came from, without a port; see the package comment for what is believed and why.
func IsAutoRelay ¶
IsAutoRelay reports whether ip is an address `auto` carves out of its networks because relayed connections arrive from it: a gateway (the host's docker-proxy, a host process) or filex's own address (rootless Podman's port forwarding).
func Observe ¶
Observe notes a request whose peer sent X-Forwarded-For or X-Real-IP without being a trusted proxy. The access-log middleware calls it once per request; FromRequest (asked many times per request) does not.
func PeerOf ¶
PeerOf is the address of a socket peer ("host:port" or a bare address), for the protocol servers that have no headers.
func PeerTrusted ¶ added in v0.54.0
PeerTrusted reports whether the socket's peer is a trusted proxy: the only sender whose X-Forwarded-* headers are believed (X-Forwarded-Proto as much as X-Forwarded-For). Unlike ProxyItself it does not ask whether the request was forwarded - a proxy passing a visitor's request on is exactly the sender whose X-Forwarded-Proto says how the visitor arrived.
func ProxyItself ¶
ProxyItself reports whether a request was made BY a trusted proxy, not forwarded through one: the socket's peer is in the trusted set and the request carries no forwarded-for header (X-Forwarded-For, X-Real-IP, Forwarded) that a proxy adds to what it passes on. The question a hook the proxy alone may ask must answer (/api/tls/ask, docs/TENANT-ADMIN.md): a stranger's request forwarded by the same proxy is not the proxy's.
func ResetForwarders ¶
func ResetForwarders()
ResetForwarders forgets every peer and the log budget (tests).
func SetAutoResolver ¶
func SetAutoResolver(fn func() AutoResolution) (restore func())
SetAutoResolver replaces what works the automatic set out (tests; nil = the running system) and forgets the answer in force, so the next use asks fn. It returns a func that puts the previous resolver back.
Types ¶
type AutoInterface ¶
type AutoInterface struct {
Name string `json:"name"`
// Kind is the link kind the kernel reports (veth, macvlan, ipvlan, tun,
// bridge, ...), "physical" for a NIC with a device behind it, "" when it
// has none of either.
Kind string `json:"kind"`
// Addresses are its addresses with their prefix length.
Addresses []string `json:"addresses"`
// Trusted: its network is part of the automatic set.
Trusted bool `json:"trusted"`
// Reason is one of the Reason* codes.
Reason string `json:"reason"`
}
AutoInterface is one network interface as `auto` saw it.
type AutoResolution ¶
type AutoResolution struct {
// Environment is plain, container, host-network, kubernetes,
// podman-rootless or unknown (the Env* constants).
Environment string `json:"environment"`
// Runtime is the container runtime the markers name: docker, podman,
// kubernetes, containerd, container (a marker without a name); "" outside
// a container.
Runtime string `json:"runtime"`
// Networks are the container networks trusted besides loopback, as CIDR.
Networks []string `json:"networks"`
// ExcludedGateways are the gateway addresses found (route next hops, the
// first host address of each trusted network): never trusted by `auto`,
// even inside a trusted network.
ExcludedGateways []string `json:"excluded_gateways"`
// ExcludedSelf are filex's own addresses on the trusted networks: never
// trusted either. A connection from one of them was made inside filex's
// own network namespace - measured: rootless Podman on a user-defined
// network relays every published-port connection (from the internet
// too) from the container's own address.
ExcludedSelf []string `json:"excluded_self"`
// Interfaces is what was looked at and what became of each (the "why").
Interfaces []AutoInterface `json:"interfaces"`
// Warning is "" or one of the Warn* codes; WarningDetail says what
// exactly (English, for the log and the page's detail line).
Warning string `json:"warning"`
WarningDetail string `json:"warning_detail,omitempty"`
// ResolvedAt is when this answer was worked out.
ResolvedAt time.Time `json:"resolved_at"`
}
AutoResolution is what `auto` resolved to, and why.
func Auto ¶
func Auto() AutoResolution
Auto answers what `auto` resolves to right now (resolving it if nothing has yet).
func RefreshAuto ¶
func RefreshAuto() AutoResolution
RefreshAuto works the automatic set out again and puts it in force. It logs only when the answer changed (the first answer is always logged), so it can run every minute. It returns the answer.
func ResolveAuto ¶
func ResolveAuto(sys System) AutoResolution
ResolveAuto works out the automatic set from sys. See the file comment for the rule; every branch has a fixture in auto_test.go.
type Classes ¶
type Classes struct {
// Auto: the automatic set - loopback plus, in a container on its own
// network, that network minus its gateways (AutoResolution).
Auto bool `json:"auto"`
// Loopback: netip.Addr.IsLoopback — this machine.
Loopback bool `json:"loopback"`
// Private: netip.Addr.IsPrivate — the RFC 1918 (IPv4) and RFC 4193 (IPv6)
// private ranges.
Private bool `json:"private"`
// LinkLocal: netip.Addr.IsLinkLocalUnicast.
LinkLocal bool `json:"link_local"`
}
Classes says which classes of address a set takes in whole. Loopback, Private and LinkLocal are each the standard library's own test (net/netip), never a list of ranges kept here; Auto is the automatic set in force (see auto.go), read at the moment of asking.
func DefaultClasses ¶
func DefaultClasses() Classes
DefaultClasses is what an unset trusted-proxy list means: `auto`.
type Forwarder ¶
type Forwarder struct {
Address string `json:"address"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Count is how many requests carried the header from it.
Count int64 `json:"count"`
}
Forwarder is one peer that sent a forwarded address without being trusted.
func UntrustedForwarders ¶
func UntrustedForwarders() []Forwarder
UntrustedForwarders lists the peers remembered, the busiest first (then the most recent). A peer that has since been trusted, or that has been quiet for a day, is forgotten here.
type Iface ¶
type Iface struct {
Name string
Up bool
Loopback bool
// Addrs are the interface's addresses with their prefix length (the
// host part kept: 172.18.0.5/16).
Addrs []netip.Prefix
}
Iface is one network interface as the System reports it.
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Set is a list of addresses and networks, plus the classes of address (the words of a trusted-proxy list) it takes in whole.
func ParseEntries ¶
ParseEntries is ParseList for a caller that already holds separate entries, and rejects the words: an allow-list of client addresses must name addresses.
func ParseList ¶
ParseList reads a trusted-proxy list: IP addresses and CIDR networks separated by commas, spaces or newlines, and the words `auto`, `loopback`, `private` and `link-local` (each takes that whole class of address, see Classes). A list replaces the default, so a list that should keep the automatic set names it: "auto, 203.0.113.0/24". `none` trusts no proxy at all (the socket's peer is always the client); written next to other entries it adds nothing and takes nothing away.
Blank text is "not set" and answers (nil, nil), so a caller falls through to the next source. A bad entry is an error that names it.
func (*Set) Classes ¶
Classes reports which classes of address the set takes in whole (none for a nil set).
func (*Set) ContainsString ¶
ContainsString is Contains for an address in text; anything that does not parse is not contained.
func (*Set) Effective ¶
Effective lists what the set trusts right now, with `auto` spelled out: the class words (loopback, private, link-local - `auto` always brings loopback), then the container networks `auto` resolved to, then the set's own addresses and networks; nothing twice. What `auto` carves out of its networks is AutoResolution.ExcludedGateways and ExcludedSelf.
type System ¶
type System interface {
// GOOS is runtime.GOOS; only Linux has containers to look for.
GOOS() string
// ReadFile reads a file (/proc/..., /run/.containerenv).
ReadFile(name string) ([]byte, error)
// Stat reports whether a path exists: nil, fs.ErrNotExist, or another
// error when it cannot be told.
Stat(name string) error
// Getenv reads an environment variable.
Getenv(key string) string
// Interfaces lists the network interfaces with their addresses.
Interfaces() ([]Iface, error)
// LinkKinds answers the kernel's link kind of each interface by name
// (netlink IFLA_INFO_KIND: veth, macvlan, ipvlan, tun, bridge, ...); an
// interface with no kind (a physical NIC, loopback) is absent or "".
LinkKinds() (map[string]string, error)
// ByteOrder is how /proc/net/route prints an address: the host's own.
ByteOrder() binary.ByteOrder
// Now is the clock.
Now() time.Time
}
System is everything `auto` reads. The real one (auto_linux.go, auto_other.go) reads the running system; tests hand in fixtures.