Skip to content

Commit a6ab070

Browse files
chore: add back default client_ip_headers and trusted_proxies values
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent 1f1eeff commit a6ab070

3 files changed

Lines changed: 65 additions & 39 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Since this is not a library, this changelog focuses on the changes that are rele
2222

2323
### ⚠️ Breaking Changes
2424

25-
- Proxy-provided client IP headers are no longer trusted by default. Configure `client_ip_headers` and `trusted_proxies` to opt in
25+
- Client IP detection now defaults to `X-Forwarded-For` from IPv4 and IPv6 loopback proxies only. Other headers and proxies must be configured with the `client_ip_headers` and `trusted_proxies` options. See the [configuration reference](https://liwan.dev/reference/configuration/) for details.
2626

2727
### Features
2828

‎data/config.example.toml‎

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,49 @@
11
base_url = "http://localhost:9042" # The base URL of the Liwan instance
22
listen = 9042 # The port to listen on (http)
33

4-
# # Disable favicon fetching from the DuckDuckGo API.
5-
# disable_favicons=false
4+
## Disable favicon fetching from the DuckDuckGo API.
5+
# disable_favicons = false
66

7-
# # Folder to store the database in (Will be created if it doesn't exist)
8-
# # defaults to $HOME/.local/share/liwan/data on linux/macos
9-
# # defaults to ./liwan-data on other platforms
7+
## Folder to store the database in (Will be created if it doesn't exist)
8+
## defaults to $HOME/.local/share/liwan/data on linux/macos
9+
## defaults to ./liwan-data on other platforms
1010
# data_dir="./liwan-data"
1111

12-
# # Trusted client IP headers (used in order).
13-
# # Empty by default, meaning only the direct peer IP is used.
14-
# # Presets: cloudflare, fastly, fly, cloudfront, akamai.
15-
# client_ip_headers="cloudflare"
16-
# # Provider presets can be combined with standard or custom headers.
17-
# client_ip_headers=["cloudflare", "fly", "X-Forwarded-For", "X-Client-IP"]
12+
## Trusted client IP headers (used in order), defaults to X-Forwarded-For.
13+
## Presets: cloudflare, fastly, fly, cloudfront, akamai.
14+
## Provider presets can be combined with standard or custom headers.
15+
client_ip_headers = ["X-Forwarded-For"]
1816

19-
# # Trusted reverse proxies (IP or CIDR).
20-
# # Empty by default, meaning forwarded client IP and GeoIP headers are not trusted.
21-
# trusted_proxies=["127.0.0.1", "10.0.0.0/8"]
17+
## Trusted reverse proxies (IP or CIDR), defaults to the IPv4 and IPv6 loopback ranges.
18+
trusted_proxies = ["127.0.0.1/8", "::1/128"]
2219

23-
# # Hour of day, in the server's local timezone, when visitor grouping salts rotate.
24-
# # Defaults to 4, meaning 4am local time.
20+
## Hour of day, in the server's local timezone, when visitor grouping salts rotate.
21+
## Defaults to 4, meaning 4am local time.
2522
# visitor_group_rotation_hour=4
2623

27-
# GeoIp settings (Optional)
28-
[geoip]
29-
# # Read location data supplied by a trusted proxy.
30-
# # Presets: akamai, cloudflare, cloudfront, netlify, vercel.
31-
# headers="cloudflare"
32-
# # Multiple presets and custom mappings are also supported, in priority order.
33-
# headers=["cloudflare", { country="X-Country", city="X-City" }]
34-
35-
# # MaxMind account ID and license key
36-
# # Required to automatically download the database and keep it up to date
24+
[geoip] # GeoIp settings (Optional)
25+
## MaxMind account ID and license key
26+
## Required to automatically download the database and keep it up to date
3727
# maxmind_account_id="MY_ACCOUNT_ID"
3828
# maxmind_license_key="MY_LICENSE_KEY"
3929
# maxmind_edition="GeoLite2-City"
4030

41-
# # If you don't want to automatically download the database, you can specify a path to a local file
42-
# # Otherwise, the database will be downloaded automatically and stored in the data_dir
31+
## If you don't want to automatically download the database, you can specify a path to a local file
32+
## Otherwise, the database will be downloaded automatically and stored in the data_dir
4333
# maxmind_db_path="./GeoLite2-City.mmdb"
4434

45-
# Database settings
46-
[duckdb]
47-
# # See https://liwan.dev/guides/duckdb for guidance
35+
## Read location data supplied by a trusted proxy.
36+
## Presets: akamai, cloudflare, cloudfront, netlify, vercel.
37+
## Multiple presets and custom mappings are also supported, in priority order:
38+
# headers=["cloudflare", { country="X-Country", city="X-City" }]
39+
# headers="cloudflare"
40+
41+
[duckdb] # Database settings
42+
## See https://liwan.dev/guides/duckdb for guidance
4843
# threads=2
4944
# memory_limit=2G
5045

51-
# Resource limits for dashboard reports.
52-
[limits]
46+
[limits] # Resource limits for dashboard reports.
5347
# report_timeout_seconds=30
5448
# report_max_concurrency=8
5549
# report_max_range_days=3660

‎src/config.rs‎

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ pub struct Config {
3737

3838
/// Client IP header names or provider presets.
3939
/// Presets: `cloudflare`, `fastly`, `fly`, `cloudfront`, and `akamai`.
40-
#[serde(default, alias = "trusted_headers")]
40+
#[serde(default = "default_client_ip_headers", alias = "trusted_headers")]
4141
pub client_ip_headers: OneOrMany<ClientIpHeaderSource>,
4242

43-
#[serde(default)]
43+
#[serde(default = "default_trusted_proxies")]
4444
pub trusted_proxies: OneOrMany<TrustedProxy>,
4545

4646
#[serde(default = "default_visitor_group_rotation_hour")]
@@ -58,8 +58,8 @@ impl Default for Config {
5858
disable_favicons: false,
5959
listen: None,
6060
port: None,
61-
client_ip_headers: OneOrMany::default(),
62-
trusted_proxies: OneOrMany::default(),
61+
client_ip_headers: default_client_ip_headers(),
62+
trusted_proxies: default_trusted_proxies(),
6363
visitor_group_rotation_hour: default_visitor_group_rotation_hour(),
6464
}
6565
}
@@ -167,6 +167,18 @@ fn default_visitor_group_rotation_hour() -> u8 {
167167
4
168168
}
169169

170+
fn default_client_ip_headers() -> OneOrMany<ClientIpHeaderSource> {
171+
vec![ClientIpHeaderSource::Header("x-forwarded-for".to_string())].into()
172+
}
173+
174+
fn default_trusted_proxies() -> OneOrMany<TrustedProxy> {
175+
vec![
176+
TrustedProxy::Cidr("127.0.0.1/8".parse().expect("valid default trusted proxy")),
177+
TrustedProxy::Cidr("::1/128".parse().expect("valid default trusted proxy")),
178+
]
179+
.into()
180+
}
181+
170182
fn default_report_max_concurrency() -> usize {
171183
8
172184
}
@@ -391,6 +403,26 @@ mod test {
391403
assert_eq!(config.base_url, "http://localhost:8081");
392404
assert_eq!(config.data_dir, "./liwan-test-data");
393405
assert_eq!(config.listen_addr(), "0.0.0.0:9042");
406+
assert_eq!(config.client_ip_headers, default_client_ip_headers());
407+
assert_eq!(Config::default().client_ip_headers, default_client_ip_headers());
408+
assert_eq!(config.trusted_proxies, default_trusted_proxies());
409+
assert_eq!(Config::default().trusted_proxies, default_trusted_proxies());
410+
}
411+
412+
#[test]
413+
fn test_empty_proxy_config_overrides_defaults() {
414+
let (_temp_dir, config_path) = temp_config(
415+
"empty-proxies.config.toml",
416+
r#"
417+
client_ip_headers = []
418+
trusted_proxies = []
419+
"#,
420+
);
421+
422+
let config = Config::load(Some(config_path), Vec::<(String, String)>::new()).expect("failed to load config");
423+
424+
assert!(config.client_ip_headers.is_empty());
425+
assert!(config.trusted_proxies.is_empty());
394426
}
395427

396428
#[test]
@@ -501,7 +533,7 @@ mod test {
501533
assert!(config.geoip.maxmind_license_key.is_none());
502534
assert_eq!(config.base_url, "http://localhost:9042");
503535
assert_eq!(config.listen_addr(), "0.0.0.0:9042");
504-
assert!(config.client_ip_headers.is_empty());
536+
assert_eq!(config.client_ip_headers, default_client_ip_headers());
505537
assert_eq!(config.limits.report_max_concurrency, 8);
506538
assert_eq!(config.limits.report_timeout_seconds, 30);
507539
assert_eq!(config.limits.report_max_range_days, 3660);

0 commit comments

Comments
 (0)