Skip to content
Draft
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Removed the distributable Kubernetes administrator `Secret` and historical placeholder credential. Production deployments must provision `waf-ids-ai-soc-admin` / `ADMIN_TOKEN` through the external secret-management control plane; the workload's `secretKeyRef` is explicitly non-optional.
- Added a structural regression contract that rejects shipped administrator Secret objects, placeholder credentials, decoy workloads, init-container false positives, and optional administrator Secret references.
- Forwarded client IP headers are now ignored unless the direct peer matches `TRUSTED_PROXY_CIDRS`. Trusted chains are parsed right to left, malformed chains fail closed to the peer address, and IPv4-mapped trusted peers normalize correctly before rate limiting and DNSBL attribution.

### Operations

Expand Down
50 changes: 50 additions & 0 deletions docs/papers/trusted-proxy-client-ip-attribution-sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Trusted proxy client IP attribution sources

This note records the standards, operational guidance, and peer-reviewed
security evidence that ground Wardnet's trusted-proxy client IP attribution
behavior. The implementation anchors trust in the direct transport peer and
considers forwarded metadata only when that peer belongs to an explicitly
configured trusted range.

## Evidence synthesis

RFC 7239 defines forwarding metadata as information added by intermediaries; it
is not self-authenticating client truth. NGINX documents the trusted-proxy rule
explicitly and resolves recursive address chains to the last non-trusted hop.
Envoy documents the same right-to-left trust model for `X-Forwarded-For` with
trusted CIDR lists.

Pletinckx, Kruegel, and Vigna's NDSS 2025 Internet-scale measurement provides
independent empirical security evidence for the same boundary. Their study
shows that backends accepting proxy-supplied source identity from arbitrary
network sources can permit access-control bypass and other security failures.
For Wardnet, that supports direct-peer verification before forwarded metadata
can affect rate limiting, DNSBL decisions, or event attribution. The paper does
not prescribe Wardnet's exact HTTP malformed-chain algorithm; Wardnet's choice
to reject an incomplete or unparsable `X-Forwarded-For` chain and fall back to
the direct peer is a conservative fail-closed policy derived from the broader
untrusted-metadata threat.

## References

- Nottingham, M. (Ed.), & Kamp, P. H. (Ed.). (2014). *Forwarded HTTP
extension* (RFC 7239). Internet Engineering Task Force.
https://datatracker.ietf.org/doc/html/rfc7239
- NGINX, Inc. (n.d.). *Module ngx_http_realip_module*.
https://nginx.org/en/docs/http/ngx_http_realip_module.html
- Envoy contributors. (n.d.). *HTTP header manipulation*.
https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- Pletinckx, S., Kruegel, C., & Vigna, G. (2025). A large-scale measurement
study of the PROXY protocol and its security implications. *Network and
Distributed System Security Symposium 2025*.
https://doi.org/10.14722/ndss.2025.242247
Open-access paper: https://www.ndss-symposium.org/wp-content/uploads/2025-2247-paper.pdf

## Redistribution note

The IETF, NGINX, Envoy, and NDSS source locations are linked directly so the
repository preserves authoritative origin and version context. The NDSS paper
is openly readable from the symposium site, but this repository does not vendor
a copy until redistribution terms for storing a derivative repository copy are
explicitly verified. Linkability and free access are not treated as permission
to redistribute a binary artifact.
36 changes: 36 additions & 0 deletions docs/runbooks/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ cargo run
Health reports `credentials_source` (`file` / `env` / `none`) and
`admin_auth_configured` (boolean) without exposing secret values.

## Trusted proxy client IP attribution

Forwarded client IP headers are untrusted by default. Gateway rate limiting,
DNSBL matching, and event attribution use the direct peer address unless that
peer matches `TRUSTED_PROXY_CIDRS`.

```bash
TRUSTED_PROXY_CIDRS=192.0.2.0/24,2001:db8::/32 \
cargo run
```

When a peer is trusted, Wardnet parses the complete `X-Forwarded-For` chain
from right to left and picks the first hop that is not itself a trusted proxy.
If the header is absent, Wardnet may use `X-Real-IP` from that same trusted
context. If any forwarded hop is empty or invalid, the whole chain is rejected
and Wardnet falls back to the direct peer without consulting `X-Real-IP`.

Wardnet follows the trust-boundary model documented by RFC 7239 and common
edge proxies. RFC 7239 defines forwarded metadata as proxy-added information,
which means the application must first trust the direct peer before treating
the header as evidence. NGINX documents recursive resolution as "the last
non-trusted address" in the chain, and Envoy documents trusted CIDR handling
from the right side of `X-Forwarded-For` toward the client.

Reference links:

- RFC 7239, *Forwarded HTTP extension*:
<https://datatracker.ietf.org/doc/html/rfc7239>
- NGINX `ngx_http_realip_module`:
<https://nginx.org/en/docs/http/ngx_http_realip_module.html>
- Envoy HTTP header manipulation:
<https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers>

For an implementation-oriented source note, see
`docs/papers/trusted-proxy-client-ip-attribution-sources.md`.

## Health Check

```bash
Expand Down
19 changes: 18 additions & 1 deletion src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
//! [`CredentialRegistry::get_credential`].

use serde::{Deserialize, Serialize};
use std::{collections::HashMap, io::ErrorKind, path::Path};
use std::{
collections::HashMap,
io::ErrorKind,
path::{Path, PathBuf},
};

/// Well-known credentials loaded into the registry at bootstrap.
pub const CRED_ADMIN_TOKEN: &str = "admin_token";
Expand Down Expand Up @@ -49,6 +53,19 @@ impl CredentialRegistry {
Self::default()
}

/// Bootstrap the registry from process-edge delivery inputs.
pub fn bootstrap_from_env() -> Result<(Self, Option<PathBuf>), String> {
let credentials_path = std::env::var("WAF_IDS_CREDENTIALS_PATH")
.ok()
.map(PathBuf::from);
let registry = Self::bootstrap_secrets(
credentials_path.as_deref(),
std::env::var("ADMIN_TOKEN").ok(),
std::env::var("ADMIN_TOKENS").ok(),
)?;
Ok((registry, credentials_path))
}

pub fn get_credential(&self, name: &str) -> Option<&str> {
self.values.get(name).map(String::as_str)
}
Expand Down
Loading
Loading