X-Forwarded-For catcher - improve ip addr parsing (#1725)

This commit is contained in:
James Hodgkinson 2023-06-12 12:14:34 +10:00 committed by GitHub
parent 9378ddb41b
commit 18fe86db26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 45 deletions

View file

@ -254,4 +254,3 @@ This is despite the fact:
- The third is an incorrect port. - The third is an incorrect port.
To diagnose errors like this, you may need to add "-d 1" to your LDAP commands or client. To diagnose errors like this, you may need to add "-d 1" to your LDAP commands or client.

View file

@ -264,9 +264,9 @@ In the virtual host, to protect a location:
### Miniflux ### Miniflux
Miniflux is a feedreader that supports OAuth 2.0 and OpenID connect. It automatically appends Miniflux is a feedreader that supports OAuth 2.0 and OpenID connect. It automatically appends the
the `.well-known` parts to the discovery endpoint. The application name in the redirect URL `.well-known` parts to the discovery endpoint. The application name in the redirect URL needs to
needs to match the `OAUTH2_PROVIDER` name. match the `OAUTH2_PROVIDER` name.
``` ```
OAUTH2_PROVIDER = "kanidm"; OAUTH2_PROVIDER = "kanidm";
@ -274,10 +274,11 @@ OAUTH2_CLIENT_ID = "miniflux";
OAUTH2_CLIENT_SECRET = "<oauth2_rs_basic_secret>"; OAUTH2_CLIENT_SECRET = "<oauth2_rs_basic_secret>";
OAUTH2_REDIRECT_URL = "https://feeds.example.com/oauth2/kanidm/callback"; OAUTH2_REDIRECT_URL = "https://feeds.example.com/oauth2/kanidm/callback";
OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://idm.example.com/oauth2/openid/<oauth2_rs_name>"; OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://idm.example.com/oauth2/openid/<oauth2_rs_name>";
```` ```
Currently Miniflux [does not support PKCE](https://github.com/miniflux/v2/issues/1910) and Kanidm will Currently Miniflux [does not support PKCE](https://github.com/miniflux/v2/issues/1910) and Kanidm
prevent logins until you [disable PKCE](#extended-options-for-legacy-clients) for the resource server. will prevent logins until you [disable PKCE](#extended-options-for-legacy-clients) for the resource
server.
### Nextcloud ### Nextcloud

View file

@ -12,6 +12,7 @@ log_level = "verbose"
domain = "localhost" domain = "localhost"
origin = "https://localhost:8443" origin = "https://localhost:8443"
trust_x_forward_for = true
[online_backup] [online_backup]
path = "/tmp/kanidm/backups/" path = "/tmp/kanidm/backups/"

View file

@ -183,14 +183,25 @@ impl RequestExtensions for tide::Request<AppState> {
(eventid, hv) (eventid, hv)
} }
/// Returns the remote address of the client, based on if you've got trust_x_forward_for set in config.
fn get_remote_addr(&self) -> Option<IpAddr> { fn get_remote_addr(&self) -> Option<IpAddr> {
if self.state().trust_x_forward_for { if self.state().trust_x_forward_for {
self.remote() // split the socket address off if we've got one, then parse it as an `IpAddr`
// xff headers don't have a port, but if we're going direct you might get one
let res = self
.remote()
.map(|addr| addr.split(':').next().unwrap_or(addr))
.and_then(|ip| ip.parse::<IpAddr>().ok());
debug!("Trusting XFF, using remote src_ip={:?}", res);
res
} else { } else {
self.peer_addr() let res = self
.peer_addr()
.map(|addr| addr.parse::<SocketAddr>().unwrap())
.map(|s_ad: SocketAddr| s_ad.ip());
debug!("Not trusting XFF, using peer_addr src_ip={:?}", res);
res
} }
.and_then(|add_str| add_str.parse().ok())
.map(|s_ad: SocketAddr| s_ad.ip())
} }
} }

View file

@ -1065,17 +1065,18 @@ pub async fn do_nothing(_req: tide::Request<AppState>) -> tide::Result {
} }
pub async fn reauth(mut req: tide::Request<AppState>) -> tide::Result { pub async fn reauth(mut req: tide::Request<AppState>) -> tide::Result {
let uat = req.get_current_uat(); // check that we can get the remote IP address first, since this doesn't touch the backend at all
let (eventid, hvalue) = req.new_eventid();
let ip_addr = req.get_remote_addr().ok_or_else(|| { let ip_addr = req.get_remote_addr().ok_or_else(|| {
error!("Unable to process remote addr, refusing to proceed"); error!("Unable to get remote addr for auth event, refusing to proceed");
tide::Error::from_str( tide::Error::from_str(
tide::StatusCode::InternalServerError, tide::StatusCode::InternalServerError,
"unable to validate peer address", "unable to validate peer address",
) )
})?; })?;
let uat = req.get_current_uat();
let (eventid, hvalue) = req.new_eventid();
let obj: AuthIssueSession = req.body_json().await.map_err(|e| { let obj: AuthIssueSession = req.body_json().await.map_err(|e| {
debug!("Failed get body JSON? {:?}", e); debug!("Failed get body JSON? {:?}", e);
e e
@ -1092,6 +1093,14 @@ pub async fn reauth(mut req: tide::Request<AppState>) -> tide::Result {
} }
pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result { pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
// check that we can get the remote IP address first, since this doesn't touch the backend at all
let ip_addr = req.get_remote_addr().ok_or_else(|| {
error!("Unable to get remote addr for auth event, refusing to proceed");
tide::Error::from_str(
tide::StatusCode::InternalServerError,
"unable to validate peer address",
)
})?;
// First, deal with some state management. // First, deal with some state management.
// Do anything here first that's needed like getting the session details // Do anything here first that's needed like getting the session details
// out of the req cookie. // out of the req cookie.
@ -1099,14 +1108,6 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
let maybe_sessionid: Option<Uuid> = req.get_current_auth_session_id(); let maybe_sessionid: Option<Uuid> = req.get_current_auth_session_id();
let ip_addr = req.get_remote_addr().ok_or_else(|| {
error!("Unable to process remote addr, refusing to proceed");
tide::Error::from_str(
tide::StatusCode::InternalServerError,
"unable to validate peer address",
)
})?;
let obj: AuthRequest = req.body_json().await.map_err(|e| { let obj: AuthRequest = req.body_json().await.map_err(|e| {
debug!("Failed get body JSON? {:?}", e); debug!("Failed get body JSON? {:?}", e);
e e

View file

@ -941,7 +941,7 @@ async fn test_repl_increment_basic_bidirectional_recycle(
server_a_txn.commit().expect("Failed to commit"); server_a_txn.commit().expect("Failed to commit");
drop(server_b_txn); drop(server_b_txn);
// On both servers, at seperate timestamps, run the recycle. // On both servers, at separate timestamps, run the recycle.
let ct = ct + Duration::from_secs(1); let ct = ct + Duration::from_secs(1);
let mut server_a_txn = server_a.write(ct).await; let mut server_a_txn = server_a.write(ct).await;
assert!(server_a_txn.internal_delete_uuid(t_uuid).is_ok()); assert!(server_a_txn.internal_delete_uuid(t_uuid).is_ok());

View file

@ -0,0 +1,45 @@
#[test]
fn bench_ip_address_parsing() {
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use std::time::Instant;
let ip_input_some = Some("1.2.3.4:1234");
let test_val = Some(IpAddr::from_str("1.2.3.4").unwrap());
let iterations = 10000000u128;
// test the split method
let split_start = Instant::now();
for _ in 0..iterations {
let res: Option<IpAddr> = ip_input_some
.map(|addr| addr.split(':').next().unwrap_or(addr))
.and_then(|ip| ip.parse::<IpAddr>().ok());
assert_eq!(test_val, res);
}
let split_end = Instant::now();
// test the socket parsing method
let socket_start = Instant::now();
for _ in 0..iterations {
let res: Option<IpAddr> = ip_input_some
.and_then(|add_str| add_str.parse().ok())
.map(|s_ad: SocketAddr| s_ad.ip());
assert_eq!(test_val, res);
}
let socket_end = Instant::now();
let split_time = split_end.duration_since(split_start);
let socket_time = socket_end.duration_since(socket_start);
println!(
"Split time: {:?}, {}ns/iteration",
split_time,
split_time.as_nanos() / iterations
);
println!(
"Socket time: {:?}, {}ns/iteration",
socket_time,
socket_time.as_nanos() / iterations
);
}

View file

@ -4,13 +4,13 @@ default-run = "kanidm"
description = "Kanidm Client Tools" description = "Kanidm Client Tools"
documentation = "https://kanidm.github.io/kanidm/stable/" documentation = "https://kanidm.github.io/kanidm/stable/"
version.workspace = true version = { workspace=true }
authors.workspace = true authors = { workspace=true }
rust-version.workspace = true rust-version = { workspace=true }
edition.workspace = true edition = { workspace=true }
license.workspace = true license = { workspace=true }
homepage.workspace = true homepage = { workspace=true }
repository.workspace = true repository = { workspace=true }
[features] [features]
default = ["unix"] default = ["unix"]
@ -30,31 +30,31 @@ name = "kanidm_ssh_authorizedkeys_direct"
path = "src/ssh_authorizedkeys.rs" path = "src/ssh_authorizedkeys.rs"
[dependencies] [dependencies]
async-recursion.workplace = true async-recursion = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }
compact_jwt = { workspace = true, features = ["openssl"] } compact_jwt = { workspace = true, features = ["openssl"] }
dialoguer.workspace = true dialoguer = { workspace=true }
futures-concurrency.workspace = true futures-concurrency = { workspace=true }
libc.workspace = true libc = { workspace=true }
kanidm_client.workspace = true kanidm_client = { workspace=true }
kanidm_proto.workspace = true kanidm_proto = { workspace=true }
qrcode = { workspace = true } qrcode = { workspace = true }
rpassword.workspace = true rpassword = { workspace=true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true serde_json = { workspace=true }
shellexpand.workspace = true shellexpand = { workspace=true }
time = { workspace = true, features = ["serde", "std"] } time = { workspace = true, features = ["serde", "std"] }
tracing.workspace = true tracing = { workspace=true }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
tokio = { workspace = true, features = ["rt", "macros"] } tokio = { workspace = true, features = ["rt", "macros"] }
url = { workspace = true, features = ["serde"] } url = { workspace = true, features = ["serde"] }
uuid.workspace = true uuid = { workspace=true }
zxcvbn.workspace = true zxcvbn = { workspace=true }
[build-dependencies] [build-dependencies]
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
clap_complete.workspace = true clap_complete = { workspace=true }
uuid.workspace = true uuid = { workspace=true }
[target."cfg(target_os = \"windows\")".dependencies.webauthn-authenticator-rs] [target."cfg(target_os = \"windows\")".dependencies.webauthn-authenticator-rs]
workspace = true workspace = true