Disable TLS native roots when not needed

This commit is contained in:
William Brown 2025-02-24 15:51:13 +10:00
parent 2309aa83bd
commit 5e8e1cda53
8 changed files with 118 additions and 84 deletions
Cargo.toml
libs/client/src
server
core/src
lib/src
testkit/src
unix_integration/resolver/tests

View file

@ -241,6 +241,7 @@ reqwest = { version = "0.12.12", default-features = false, features = [
"json",
"gzip",
"rustls-tls-native-roots",
"rustls-tls-native-roots-no-provider",
] }
rusqlite = { version = "^0.28.0", features = ["array", "bundled"] }
rustls = { version = "0.23.21", default-features = false, features = [

View file

@ -137,6 +137,7 @@ pub struct KanidmClientBuilder {
use_system_proxies: bool,
/// Where to store auth tokens, only use in testing!
token_cache_path: Option<String>,
disable_system_ca_store: bool,
}
impl Display for KanidmClientBuilder {
@ -170,33 +171,6 @@ impl Display for KanidmClientBuilder {
}
}
#[test]
fn test_kanidmclientbuilder_display() {
let defaultclient = KanidmClientBuilder::default();
println!("{}", defaultclient);
assert!(defaultclient.to_string().contains("verify_ca"));
let testclient = KanidmClientBuilder {
address: Some("https://example.com".to_string()),
verify_ca: true,
verify_hostnames: true,
ca: None,
connect_timeout: Some(420),
request_timeout: Some(69),
use_system_proxies: true,
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
};
println!("testclient {}", testclient);
assert!(testclient.to_string().contains("verify_ca: true"));
assert!(testclient.to_string().contains("verify_hostnames: true"));
let badness = testclient.danger_accept_invalid_hostnames(true);
let badness = badness.danger_accept_invalid_certs(true);
println!("badness: {}", badness);
assert!(badness.to_string().contains("verify_ca: false"));
assert!(badness.to_string().contains("verify_hostnames: false"));
}
#[derive(Debug)]
pub struct KanidmClient {
pub(crate) client: reqwest::Client,
@ -233,6 +207,7 @@ impl KanidmClientBuilder {
request_timeout: None,
use_system_proxies: true,
token_cache_path: None,
disable_system_ca_store: false,
}
}
@ -290,6 +265,7 @@ impl KanidmClientBuilder {
request_timeout,
use_system_proxies,
token_cache_path,
disable_system_ca_store,
} = self;
// Process and apply all our options if they exist.
let address = match kcc.uri {
@ -316,6 +292,7 @@ impl KanidmClientBuilder {
request_timeout,
use_system_proxies,
token_cache_path,
disable_system_ca_store,
})
}
@ -416,6 +393,16 @@ impl KanidmClientBuilder {
}
}
/// Enable or disable the native ca roots. By default these roots are enabled.
pub fn enable_native_ca_roots(self, enable: bool) -> Self {
KanidmClientBuilder {
// We have to flip the bool state here due to Default on bool being false
// and we want our options to be positive to a native speaker.
disable_system_ca_store: !enable,
..self
}
}
pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostnames: bool) -> Self {
KanidmClientBuilder {
// We have to flip the bool state here due to english language.
@ -527,6 +514,7 @@ impl KanidmClientBuilder {
// implement sticky sessions with cookies.
.cookie_store(true)
.cookie_provider(client_cookies.clone())
.tls_built_in_native_certs(!self.disable_system_ca_store)
.danger_accept_invalid_hostnames(!self.verify_hostnames)
.danger_accept_invalid_certs(!self.verify_ca);
@ -579,32 +567,6 @@ impl KanidmClientBuilder {
}
}
#[test]
fn test_make_url() {
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}", DEFAULT_SERVER_ADDRESS))
.build()
.unwrap();
assert_eq!(
client.get_url(),
Url::parse(&format!("https://{}", DEFAULT_SERVER_ADDRESS)).unwrap()
);
assert_eq!(
client.make_url("/hello"),
Url::parse(&format!("https://{}/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}/cheese/", DEFAULT_SERVER_ADDRESS))
.build()
.unwrap();
assert_eq!(
client.make_url("hello"),
Url::parse(&format!("https://{}/cheese/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
}
/// This is probably pretty jank but it works and was pulled from here:
/// <https://github.com/seanmonstar/reqwest/issues/1602#issuecomment-1220996681>
fn find_reqwest_error_source<E: std::error::Error + 'static>(
@ -2174,31 +2136,97 @@ impl KanidmClient {
}
}
#[tokio::test]
async fn test_no_client_version_check_on_502() {
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::GATEWAY_TIMEOUT)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 504 and shouldn't check version...");
client.expect_version(&res).await;
#[cfg(test)]
mod tests {
use super::{KanidmClient, KanidmClientBuilder};
use kanidm_proto::constants::CLIENT_TOKEN_CACHE;
use reqwest::StatusCode;
use url::Url;
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::BAD_GATEWAY)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 502 and shouldn't check version...");
client.expect_version(&res).await;
#[tokio::test]
async fn test_no_client_version_check_on_502() {
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::GATEWAY_TIMEOUT)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.enable_native_ca_roots(false)
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 504 and shouldn't check version...");
client.expect_version(&res).await;
let res = reqwest::Response::from(
http::Response::builder()
.status(StatusCode::BAD_GATEWAY)
.body("")
.unwrap(),
);
let client = KanidmClientBuilder::new()
.address("http://localhost:8080".to_string())
.enable_native_ca_roots(false)
.build()
.expect("Failed to build client");
eprintln!("This should pass because we are returning 502 and shouldn't check version...");
client.expect_version(&res).await;
}
#[test]
fn test_make_url() {
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}", DEFAULT_SERVER_ADDRESS))
.enable_native_ca_roots(false)
.build()
.unwrap();
assert_eq!(
client.get_url(),
Url::parse(&format!("https://{}", DEFAULT_SERVER_ADDRESS)).unwrap()
);
assert_eq!(
client.make_url("/hello"),
Url::parse(&format!("https://{}/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
let client: KanidmClient = KanidmClientBuilder::new()
.address(format!("https://{}/cheese/", DEFAULT_SERVER_ADDRESS))
.enable_native_ca_roots(false)
.build()
.unwrap();
assert_eq!(
client.make_url("hello"),
Url::parse(&format!("https://{}/cheese/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
);
}
#[test]
fn test_kanidmclientbuilder_display() {
let defaultclient = KanidmClientBuilder::default();
println!("{}", defaultclient);
assert!(defaultclient.to_string().contains("verify_ca"));
let testclient = KanidmClientBuilder {
address: Some("https://example.com".to_string()),
verify_ca: true,
verify_hostnames: true,
ca: None,
connect_timeout: Some(420),
request_timeout: Some(69),
use_system_proxies: true,
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
disable_system_ca_store: false,
};
println!("testclient {}", testclient);
assert!(testclient.to_string().contains("verify_ca: true"));
assert!(testclient.to_string().contains("verify_hostnames: true"));
let badness = testclient.danger_accept_invalid_hostnames(true);
let badness = badness.danger_accept_invalid_certs(true);
println!("badness: {}", badness);
assert!(badness.to_string().contains("verify_ca: false"));
assert!(badness.to_string().contains("verify_hostnames: false"));
}
}

View file

@ -115,9 +115,9 @@ async fn setup_qs_idms(
.await?;
// We generate a SINGLE idms only!
let is_integration_test = config.integration_test_config.is_some();
let (idms, idms_delayed, idms_audit) =
IdmServer::new(query_server.clone(), &config.origin).await?;
IdmServer::new(query_server.clone(), &config.origin, is_integration_test).await?;
Ok((query_server, idms, idms_delayed, idms_audit))
}

View file

@ -143,8 +143,9 @@ impl IdmServer {
pub async fn new(
qs: QueryServer,
origin: &str,
is_integration_test: bool,
) -> Result<(IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
let crypto_policy = if cfg!(test) {
let crypto_policy = if cfg!(test) || is_integration_test {
CryptoPolicy::danger_test_minimum()
} else {
// This is calculated back from:

View file

@ -624,7 +624,7 @@ impl Plugin for MemberOf {
// Can they both be reference sets?
match edmos.as_refer_set() {
Some(a) => {
let diff: Vec<_> = a.symmetric_difference(&b).collect();
let diff: Vec<_> = a.symmetric_difference(b).collect();
if !diff.is_empty() {
error!(
"MemberOfInvalid: Entry {}, DMO has inconsistencies",

View file

@ -89,7 +89,7 @@ pub async fn setup_idm_test(
) -> (IdmServer, IdmServerDelayed, IdmServerAudit) {
let qs = setup_test(config).await;
IdmServer::new(qs, "https://idm.example.com")
IdmServer::new(qs, "https://idm.example.com", true)
.await
.expect("Failed to setup idms")
}

View file

@ -92,6 +92,7 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
#[allow(clippy::panic)]
let rsclient = match KanidmClientBuilder::new()
.address(addr.clone())
.enable_native_ca_roots(false)
.no_proxy()
.build()
{

View file

@ -88,6 +88,7 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
// Run fixtures
let adminclient = KanidmClientBuilder::new()
.address(addr.clone())
.enable_native_ca_roots(false)
.no_proxy()
.build()
.expect("Failed to build sync client");
@ -96,12 +97,14 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
let client = KanidmClientBuilder::new()
.address(addr.clone())
.enable_native_ca_roots(false)
.no_proxy()
.build()
.expect("Failed to build async admin client");
let rsclient = KanidmClientBuilder::new()
.address(addr)
.enable_native_ca_roots(false)
.no_proxy()
.build()
.expect("Failed to build client");