From f2e9c8a16ef8feebff4787964ff4557de8aca108 Mon Sep 17 00:00:00 2001 From: Sebastiano Tocci Date: Thu, 31 Aug 2023 03:31:16 +0200 Subject: [PATCH] Add tests for X-Forwarded-For header (kinda) (#1957) * Add tests for X-Forwarded-For header (kinda) * testing for invalid header format * added debug endpoint and got tests working * various fixing here and there --- server/core/src/https/v1.rs | 16 +- server/testkit/tests/https_extractors.rs | 209 +++++++++++++++++++++++ 2 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 server/testkit/tests/https_extractors.rs diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs index 1813e03de..ad1e0f6f6 100644 --- a/server/core/src/https/v1.rs +++ b/server/core/src/https/v1.rs @@ -1425,9 +1425,17 @@ pub async fn auth_valid( to_axum_response(res) } +#[cfg(debug_assertions)] +pub async fn debug_ipinfo( + State(_state): State, + TrustedClientIp(ip_addr): TrustedClientIp, +) -> impl IntoResponse { + to_axum_response(Ok(vec![ip_addr])) +} + #[instrument(skip(state))] pub fn router(state: ServerState) -> Router { - Router::new() + let mut router = Router::new() .route("/v1/oauth2", get(super::oauth2::oauth2_get)) .route("/v1/oauth2/_basic", post(super::oauth2::oauth2_basic_post)) .route( @@ -1728,5 +1736,9 @@ pub fn router(state: ServerState) -> Router { post(sync_account_token_post).delete(sync_account_token_delete), ) .with_state(state) - .layer(from_fn(dont_cache_me)) + .layer(from_fn(dont_cache_me)); + if cfg!(debug_assertions) { + router = router.route("/v1/debug/ipinfo", get(debug_ipinfo)); + } + router } diff --git a/server/testkit/tests/https_extractors.rs b/server/testkit/tests/https_extractors.rs new file mode 100644 index 000000000..ca574a425 --- /dev/null +++ b/server/testkit/tests/https_extractors.rs @@ -0,0 +1,209 @@ +use std::{ + net::{IpAddr, Ipv4Addr}, + str::FromStr, +}; + +use kanidm_client::KanidmClient; + +const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + +// *test where we don't trust the x-forwarded-for header + +#[kanidmd_testkit::test(trust_x_forward_for = false)] +async fn dont_trust_xff_send_header(rsclient: KanidmClient) { + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header( + "X-Forwarded-For", + "An invalid header that will get through!!!", + ) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!(ip_res[0], DEFAULT_IP_ADDRESS); +} + +#[kanidmd_testkit::test(trust_x_forward_for = false)] +async fn dont_trust_xff_dont_send_header(rsclient: KanidmClient) { + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header( + "X-Forwarded-For", + "An invalid header that will get through!!!", + ) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!(ip_res[0], DEFAULT_IP_ADDRESS); +} + +// *test where we trust the x-forwarded-for header + +#[kanidmd_testkit::test(trust_x_forward_for = true)] +async fn trust_xff_send_invalid_header_single_value(rsclient: KanidmClient) { + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header( + "X-Forwarded-For", + "An invalid header that will get through!!!", + ) + .send() + .await + .unwrap(); + + assert_eq!(res.status(), 400); +} + +// TODO: Right now we reject the request only if the leftmost address is invalid. In the future that could change so we could also have a test +// with a valid leftmost address and an invalid address later in the list. Right now it wouldn't work. +// +#[kanidmd_testkit::test(trust_x_forward_for = true)] +async fn trust_xff_send_invalid_header_multiple_values(rsclient: KanidmClient) { + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header( + "X-Forwarded-For", + "203.0.113.195_noooo_my_ip_address, 2001:db8:85a3:8d3:1319:8a2e:370:7348", + ) + .send() + .await + .unwrap(); + + assert_eq!(res.status(), 400); +} + +#[kanidmd_testkit::test(trust_x_forward_for = true)] +async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: KanidmClient) { + let ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348"; + + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header("X-Forwarded-For", ip_addr) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!(ip_res[0], IpAddr::from_str(ip_addr).unwrap()); +} + +#[kanidmd_testkit::test(trust_x_forward_for = true)] +async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: KanidmClient) { + let ip_addr = "203.0.113.195"; + + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header("X-Forwarded-For", ip_addr) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!(ip_res[0], IpAddr::from_str(ip_addr).unwrap()); +} + +#[kanidmd_testkit::test(trust_x_forward_for = true)] +async fn trust_xff_send_valid_header_multiple_address(rsclient: KanidmClient) { + let first_ip_addr = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348"; + + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header("X-Forwarded-For", first_ip_addr) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!( + ip_res[0], + IpAddr::from_str(first_ip_addr.split(",").collect::>()[0]).unwrap() + ); + + let second_ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348, 198.51.100.178, 203.0.113.195"; + + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header("X-Forwarded-For", second_ip_addr) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!( + ip_res[0], + IpAddr::from_str(second_ip_addr.split(",").collect::>()[0]).unwrap() + ); +} + +#[kanidmd_testkit::test(trust_x_forward_for = true)] +async fn trust_xff_dont_send_header(rsclient: KanidmClient) { + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .send() + .await + .unwrap(); + let ip_res: Vec = res + .json() + .await + .expect("Failed to parse response as Vec"); + + assert_eq!(ip_res[0], DEFAULT_IP_ADDRESS); +}