Skip to content

Commit 8916087

Browse files
feat: new trusted_headers, trusted_proxies, and use_forward_headers config options
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent 6a12ba7 commit 8916087

8 files changed

Lines changed: 363 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,29 @@ Since this is not a library, this changelog focuses on the changes that are rele
1818

1919
## [Unreleased]
2020

21-
- Added new entry/exit page metric
21+
### Features
22+
23+
- Added new `trusted_headers`, `trusted_proxies`, and `use_forward_headers` config options
24+
- Added new entry page and exit page dimensions
25+
- Added new screen width and orientation dimensions
26+
27+
### Bug Fixes
28+
29+
- Fixed error when both `listen` and `port` config options are set
30+
- Added retry logic when opening the DuckDB database to handle potential locking issues on startup (e.g. when the database is being updated by another process or when using a shared network drive)
31+
- Fixed an issue where users with invalid session tokens would show up in the UI as logged in
32+
33+
### Other
34+
35+
- Added `?ref` / `?referrer` / `?source` as fallbacks for UTM source
2236
- Updated to the latest version of DuckDB (1.5.1)
23-
- Fixed error when both `listen` and `port` configuration options are set
24-
- Added retry logic when loading the DuckDB database to handle potential locking issues on startup (e.g. when the database is being updated by another process or when using a shared network drive)
2537
- Improved filtering out invalid referrers
2638
- Improved world map rendering & graph for bounce rate metric
39+
- Improved UI error messages / loading states
2740

2841
## [v1.4.0] - 2026-03-14
2942

30-
- Updated to the latest version of DuckDB (1.5)
43+
- Updated to the latest version of DuckDB (1.5.0)
3144
- GeoIP database now automatically reloads if it has been updated on disk / fixed an issue where the GeoIP database would not be updated on the first boot
3245
- Switched to using `axum` as the web framework and `ua-parser` for user-agent parsing
3346
- Added new `listen` configuration option to specify the address and port to listen on (can be a socket address or just a port number). The old `port` option is still supported for backwards compatibility.
@@ -69,7 +82,7 @@ If you map a folder to `/data` and encounter permission issues, ensure it’s ow
6982

7083
## [v1.0.0] - 2024-12-06
7184

72-
### 🚀 Features
85+
### Features
7386

7487
- **UTM parameters**: Added support for UTM parameters. You can filter and search by UTM source, medium, campaign, content, and term. ([#13](https://github.com/explodingcamera/liwan/pull/13))
7588
- **New Date Ranges**: Fully reworked date ranges. Data is more accurate and consistent now, and you can move to the next or previous time range. Also includes some new time ranges like `Week to Date` and `All Time`. You can now also select a custom date range to view your data. ([97cdfce](https://github.com/explodingcamera/liwan/commit/97cdfce509ed2fd2fd74b23c73726a5e01b7b288), [391c580](https://github.com/explodingcamera/liwan/commit/391c580c926e2b4ca250e08bbe725210774d99b2))
@@ -78,7 +91,7 @@ If you map a folder to `/data` and encounter permission issues, ensure it’s ow
7891
- **Favicons can be disabled**: You can now disable fetching favicons from DuckDuckGo (`config.toml` setting: `disable_favicons`) ([2100bfe](https://github.com/explodingcamera/liwan/commit/2100bfe6ba868b59d2b383220f22b0dbf23a6712))
7992
- **New Graphs**: Graphs are now custom-built using d3 directly to improve performance and flexibility. ([eb1415d](https://github.com/explodingcamera/liwan/commit/eb1415d6bdf6d3be9509b0b4fa743b6f112b2c0a))
8093

81-
### 🐛 Bug Fixes
94+
### Bug Fixes
8295

8396
- Fixed a potential panic when entities are not found in the database ([`31405a7`](https://github.com/explodingcamera/liwan/commit/31405a721dc5c5493098e211927281cca7816fec))
8497
- Fixed issues with the `Yesterday` Date Range ([`76278b57`](https://github.com/explodingcamera/liwan/commit/76278b579c5fe1557bf1c184542ed6ed2aba57cd))

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ url="2.5"
8080
ua-parser="0.2"
8181
rust-embed={version="8.11", features=["mime-guess"]}
8282
reqwest={version="0.13", default-features=false, features=["json", "stream", "charset", "rustls"]}
83+
ipnet="2.11"
8384

8485
# database
8586
duckdb={version="1.10501", features=["chrono", "bundled", "r2d2"]}

data/config.example.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ listen=9042 # The port to listen on (http)
66
# # defaults to ./liwan-data on other platforms
77
# data_dir="./liwan-data"
88

9+
# Trusted client IP headers (used in order).
10+
# Defaults to all supported headers.
11+
# trusted_headers=["cf-connecting-ip", "fly-client-ip", "true-client-ip", "x-real-ip", "cloudfront-viewer-address", "x-forwarded-for", "forwarded"]
12+
# You can also include custom headers. Names are case-insensitive.
13+
# trusted_headers=["X-Forwarded-For", "X-Client-IP"]
14+
15+
# Trusted reverse proxies (IP or CIDR).
16+
# Empty by default, meaning all proxies are trusted.
17+
# trusted_proxies=["127.0.0.1", "10.0.0.0/8"]
18+
19+
# Whether to use forwarded IP headers at all.
20+
# If false, only the direct peer IP is used.
21+
# use_forward_headers=true
22+
923
# GeoIp settings (Optional)
1024
[geoip]
1125
# # MaxMind account ID and license key

src/config.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::utils::ip_headers::{TrustedHeader, TrustedProxy, deserialize_trusted_headers, deserialize_trusted_proxies};
12
use anyhow::{Context, Result, bail};
23
use figment::Figment;
34
use figment::providers::{Env, Format, Toml};
@@ -36,6 +37,14 @@ fn default_data_dir() -> String {
3637
"./liwan-data".to_string()
3738
}
3839

40+
fn default_trusted_headers() -> Vec<TrustedHeader> {
41+
TrustedHeader::all().to_vec()
42+
}
43+
44+
fn default_use_forward_headers() -> bool {
45+
true
46+
}
47+
3948
#[derive(Debug, Clone, Serialize, Deserialize)]
4049
pub struct Config {
4150
#[serde(default = "default_base")]
@@ -59,6 +68,15 @@ pub struct Config {
5968

6069
#[serde(default)]
6170
pub duckdb: DuckdbConfig,
71+
72+
#[serde(default = "default_trusted_headers", deserialize_with = "deserialize_trusted_headers")]
73+
pub trusted_headers: Vec<TrustedHeader>,
74+
75+
#[serde(default, deserialize_with = "deserialize_trusted_proxies")]
76+
pub trusted_proxies: Vec<TrustedProxy>,
77+
78+
#[serde(default = "default_use_forward_headers")]
79+
pub use_forward_headers: bool,
6280
}
6381

6482
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -87,6 +105,9 @@ impl Default for Config {
87105
disable_favicons: false,
88106
listen: None,
89107
port: None,
108+
trusted_headers: default_trusted_headers(),
109+
trusted_proxies: Vec::new(),
110+
use_forward_headers: default_use_forward_headers(),
90111
}
91112
}
92113
}
@@ -194,6 +215,7 @@ impl Config {
194215
#[allow(clippy::result_large_err)]
195216
mod test {
196217
use super::*;
218+
use crate::utils::ip_headers::{TrustedHeader, TrustedProxy};
197219
use figment::Jail;
198220

199221
#[test]
@@ -285,10 +307,28 @@ mod test {
285307
jail.set_env("LIWAN_DATA_DIR", "/data");
286308
jail.set_env("LIWAN_BASE_URL", "https://example.com");
287309
jail.set_env("LIWAN_MAXMIND_ACCOUNT_ID", 123);
310+
jail.set_env("LIWAN_TRUSTED_HEADERS", "X_Forwarded_For,Forwarded");
311+
jail.set_env("LIWAN_TRUSTED_PROXIES", "127.0.0.1,10.0.0.0/8");
288312
let config = Config::load(None).expect("failed to load config");
289313
assert_eq!(config.data_dir, "/data");
290314
assert_eq!(config.base_url, "https://example.com");
291315
assert_eq!(config.geoip.maxmind_account_id, Some("123".to_string()));
316+
assert_eq!(config.trusted_headers, vec![TrustedHeader::XForwardedFor, TrustedHeader::Forwarded]);
317+
assert_eq!(
318+
config.trusted_proxies,
319+
vec![TrustedProxy::Ip("127.0.0.1".parse().unwrap()), TrustedProxy::Cidr("10.0.0.0/8".parse().unwrap())]
320+
);
321+
assert!(config.use_forward_headers);
322+
Ok(())
323+
});
324+
}
325+
326+
#[test]
327+
fn test_env_custom_trusted_header() {
328+
Jail::expect_with(|jail| {
329+
jail.set_env("LIWAN_TRUSTED_HEADERS", "X_CLIENT_IP");
330+
let config = Config::load(None).expect("failed to load config");
331+
assert_eq!(config.trusted_headers, vec![TrustedHeader::Other("x-client-ip".to_string())]);
292332
Ok(())
293333
});
294334
}

0 commit comments

Comments
 (0)