Lines
100 %
Functions
Branches
use super::*;
const INGRESS: &str = "10.104.0.0/16";
fn peer(addr: &str) -> SocketAddr {
SocketAddr::new(addr.parse().expect("test address parses"), 45678)
}
fn ip(addr: &str) -> IpAddr {
addr.parse().expect("test address parses")
#[test]
fn cidr_matches_only_inside_its_prefix() {
let cidr = Cidr::parse(INGRESS).expect("valid cidr");
assert!(cidr.contains(ip("10.104.0.9")));
assert!(cidr.contains(ip("10.104.255.1")));
assert!(!cidr.contains(ip("10.105.0.9")));
assert!(!cidr.contains(ip("10.4.0.9")));
fn cidr_handles_prefixes_that_do_not_land_on_a_byte() {
let cidr = Cidr::parse("192.160.0.0/12").expect("valid cidr");
assert!(cidr.contains(ip("192.160.0.1")));
assert!(cidr.contains(ip("192.170.5.5")));
assert!(!cidr.contains(ip("192.176.0.1")));
fn a_bare_address_is_a_single_host_range() {
let cidr = Cidr::parse("10.104.0.9").expect("valid address");
assert!(!cidr.contains(ip("10.104.0.10")));
fn cidr_rejects_nonsense() {
assert!(Cidr::parse("not-an-address").is_none());
assert!(Cidr::parse("10.104.0.0/33").is_none());
assert!(Cidr::parse("10.104.0.0/abc").is_none());
fn a_v4_range_never_matches_a_v6_peer() {
let cidr = Cidr::parse("0.0.0.0/0").expect("valid cidr");
assert!(!cidr.contains(ip("::1")));
fn an_untrusted_peer_cannot_forge_its_address() {
let trusted = TrustedProxies::from_spec(INGRESS);
assert_eq!(
trusted.client_ip(peer("203.0.113.7"), Some("1.2.3.4")),
ip("203.0.113.7"),
"a header from an untrusted peer must be ignored outright"
);
fn a_trusted_proxy_speaks_for_its_client() {
trusted.client_ip(peer("10.104.0.9"), Some("203.0.113.7")),
ip("203.0.113.7")
fn a_client_supplied_prefix_cannot_shadow_the_observed_hop() {
// The attacker sent `X-Forwarded-For: 1.2.3.4`; our proxy appended what it
// actually saw. Only the rightmost untrusted entry is evidence.
trusted.client_ip(peer("10.104.0.9"), Some("1.2.3.4, 203.0.113.7")),
"throttling must key on the hop our proxy observed, not the one the client claimed"
fn trusted_hops_are_skipped_from_the_right() {
trusted.client_ip(peer("10.104.0.9"), Some("203.0.113.7, 10.104.0.3")),
fn a_trusted_peer_with_no_usable_header_falls_back_to_itself() {
trusted.client_ip(peer("10.104.0.9"), None),
ip("10.104.0.9")
trusted.client_ip(peer("10.104.0.9"), Some("")),
trusted.client_ip(peer("10.104.0.9"), Some("garbage, junk")),
fn trusting_nothing_always_uses_the_peer() {
let trusted = TrustedProxies::from_spec("");
ip("10.104.0.9"),
"an unconfigured deployment must not honour forwarded headers"
/// `from_spec` is lenient by design; `from_env` is the strict door. Keeping the
/// distinction tested stops someone "fixing" the leniency and breaking callers
/// that pass a literal.
fn from_spec_drops_unparseable_entries_where_from_env_would_refuse() {
let trusted = TrustedProxies::from_spec("nonsense, 10.104.0.0/16, /16");
assert!(trusted.trusts(ip("10.104.0.9")));
assert!(!trusted.trusts(ip("203.0.113.7")));
fn a_v4_mapped_hop_is_the_same_client_as_its_plain_form() {
let ingress = peer("10.104.0.9");
trusted.client_ip(ingress, Some("::ffff:203.0.113.7")),
"a mapped address must not buy a second budget under a different key"
fn a_v4_mapped_proxy_is_still_recognised_as_trusted() {
assert!(
trusted.trusts(ip("::ffff:10.104.0.9")),
"the mapped form of a trusted proxy must not be treated as an outsider"
fn every_forwarded_header_line_is_read_not_just_the_first() {
let mut headers = axum::http::HeaderMap::new();
headers.append("x-forwarded-for", "1.2.3.4".parse().expect("valid header"));
headers.append(
"x-forwarded-for",
"203.0.113.7".parse().expect("valid header"),
let joined = forwarded_for(&headers).expect("header present");
trusted.client_ip(peer("10.104.0.9"), Some(&joined)),
"a client-supplied header line must not hide the hop the proxy appended"
fn an_absent_forwarded_header_reads_as_nothing() {
assert!(forwarded_for(&axum::http::HeaderMap::new()).is_none());
fn throttle_key_folds_an_ipv6_host_range_but_not_a_network() {
throttle_key(ip("2001:db8:1:1::dead")),
throttle_key(ip("2001:db8:1:1::beef")),
"hosts inside one /64 are one client"
assert_ne!(
throttle_key(ip("2001:db8:1:1::1")),
throttle_key(ip("2001:db8:1:2::1")),
"a different /64 is a different client"
fn throttle_key_leaves_ipv4_alone() {
assert_eq!(throttle_key(ip("203.0.113.7")), ip("203.0.113.7"));
throttle_key(ip("203.0.113.7")),
throttle_key(ip("203.0.113.8"))
fn throttle_key_folds_a_mapped_address_onto_its_v4_form() {
assert_eq!(throttle_key(ip("::ffff:203.0.113.7")), ip("203.0.113.7"));
// `TRUSTED_PROXIES` is process-global, so these cases share one test rather
// than racing each other across threads.
fn a_partly_unparseable_list_is_refused_outright() {
// Dropping the bad entry with a warning would leave the good one trusted and
// the other ingress path silently untrusted — every user on it sharing one
// login budget, which is what this variable exists to prevent.
// SAFETY: single-threaded within this test.
unsafe {
std::env::set_var("TRUSTED_PROXIES", "10.104.0.0/20,not-a-cidr");
assert!(matches!(
TrustedProxies::from_env(),
Err(TrustedProxyError::Unparseable(_))
));
std::env::set_var("TRUSTED_PROXIES", "10.104.0.0/20,10.244.0.0/16");
let trusted = TrustedProxies::from_env().expect("both entries parse");
assert!(trusted.trusts(ip("10.104.0.7")));
assert!(trusted.trusts(ip("10.244.2.231")));
std::env::remove_var("TRUSTED_PROXIES");
fn from_env_demands_an_explicit_decision() {
// SAFETY: single-threaded within this test; the variable is read only here.
matches!(TrustedProxies::from_env(), Err(TrustedProxyError::Unset)),
"an unset variable must not be guessed either way"
std::env::set_var("TRUSTED_PROXIES", "garbage");
// Entries that are all empty separators leave nothing to parse at all.
std::env::set_var("TRUSTED_PROXIES", " , ");
Err(TrustedProxyError::NoUsableEntry(_))
std::env::set_var("TRUSTED_PROXIES", "none");
let trusted = TrustedProxies::from_env().expect("`none` is a valid answer");
assert!(!trusted.trusts(ip("10.104.0.9")));
std::env::set_var("TRUSTED_PROXIES", INGRESS);
let trusted = TrustedProxies::from_env().expect("a CIDR list is a valid answer");
fn both_deployed_proxy_ranges_are_trusted() {
// The production value. Two ranges, because the nginx ingress is a
// hostNetwork DaemonSet: from another node the peer is the VPC address, but
// from the node the web pod itself runs on it is that node's cilium_host,
// inside the pod CIDR. Measured 2026-09-05.
let trusted = TrustedProxies::from_spec("10.104.0.0/20,10.244.0.0/16");
trusted.trusts(ip("10.104.0.7")),
"another node's VPC address"
trusted.trusts(ip("10.244.2.231")),
"the co-located node's cilium_host"
!trusted.trusts(ip("203.0.113.7")),
"a real client is still not a proxy"
// A client behind either path keeps its own identity.
trusted.client_ip(peer("10.244.2.231"), Some("203.0.113.7")),
"same-node ingress must speak for its client too, or that share of users \
collapses into one shared budget"