Skip to content

Commit ff1a88f

Browse files
authored
fix: align Admin TLS trust behavior with S3 for self-signed CAs (#35)
* feat(phase-2): support admin TLS native roots and ca bundle * fix(phase-2): tighten admin CA bundle validation
1 parent 464d49c commit ff1a88f

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ mime_guess = "2.0"
6363
glob = "0.3"
6464

6565
# HTTP client for Admin API
66-
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
66+
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots", "json"] }
6767
aws-sigv4 = "1.2"
6868
http = "1.2"
6969
sha2 = "0.10"

crates/s3/src/admin.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,28 @@ pub struct AdminClient {
3434
impl AdminClient {
3535
/// Create a new AdminClient from an Alias
3636
pub fn new(alias: &Alias) -> Result<Self> {
37-
let http_client = Client::builder()
37+
let mut builder = Client::builder()
3838
.danger_accept_invalid_certs(alias.insecure)
39+
.tls_built_in_native_certs(true)
40+
.tls_built_in_webpki_certs(true);
41+
42+
if let Some(bundle_path) = alias.ca_bundle.as_deref() {
43+
let pem = std::fs::read(bundle_path).map_err(|e| {
44+
Error::Network(format!("Failed to read CA bundle '{bundle_path}': {e}"))
45+
})?;
46+
let certs = reqwest::Certificate::from_pem_bundle(&pem)
47+
.map_err(|e| Error::Network(format!("Invalid CA bundle '{bundle_path}': {e}")))?;
48+
if certs.is_empty() {
49+
return Err(Error::Network(format!(
50+
"Invalid CA bundle '{bundle_path}': no certificates found"
51+
)));
52+
}
53+
for cert in certs {
54+
builder = builder.add_root_certificate(cert);
55+
}
56+
}
57+
58+
let http_client = builder
3959
.build()
4060
.map_err(|e| Error::Network(format!("Failed to create HTTP client: {e}")))?;
4161

@@ -700,6 +720,7 @@ impl AdminApi for AdminClient {
700720
#[cfg(test)]
701721
mod tests {
702722
use super::*;
723+
use tempfile::tempdir;
703724

704725
#[test]
705726
fn test_admin_url_construction() {
@@ -748,4 +769,48 @@ mod tests {
748769
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
749770
);
750771
}
772+
773+
#[test]
774+
fn test_admin_client_invalid_ca_bundle_path_surfaces_error() {
775+
let mut alias = Alias::new("test", "https://localhost:9000", "access", "secret");
776+
alias.ca_bundle = Some("/definitely-not-here/ca.pem".to_string());
777+
778+
let result = AdminClient::new(&alias);
779+
match result {
780+
Err(Error::Network(msg)) => {
781+
assert!(
782+
msg.contains("Failed to read CA bundle"),
783+
"Unexpected error message: {msg}"
784+
);
785+
}
786+
Ok(_) => panic!("Expected Error::Network for invalid path, got Ok(_)"),
787+
Err(e) => panic!("Expected Error::Network for invalid path, got Err({e})"),
788+
}
789+
}
790+
791+
#[test]
792+
fn test_admin_client_invalid_ca_bundle_pem_surfaces_error() {
793+
let temp_dir = tempdir().expect("create temp dir");
794+
let bad_pem_path = temp_dir.path().join("bad-ca.pem");
795+
std::fs::write(
796+
&bad_pem_path,
797+
b"-----BEGIN CERTIFICATE-----\ninvalid-base64\n-----END CERTIFICATE-----\n",
798+
)
799+
.expect("write invalid PEM");
800+
801+
let mut alias = Alias::new("test", "https://localhost:9000", "access", "secret");
802+
alias.ca_bundle = Some(bad_pem_path.display().to_string());
803+
804+
let result = AdminClient::new(&alias);
805+
match result {
806+
Err(Error::Network(msg)) => {
807+
assert!(
808+
msg.contains("Invalid CA bundle") && msg.contains("bad-ca.pem"),
809+
"Unexpected error message for invalid PEM CA bundle: {msg}"
810+
);
811+
}
812+
Ok(_) => panic!("Expected Error::Network for invalid PEM, got Ok(_)"),
813+
Err(e) => panic!("Expected Error::Network for invalid PEM, got Err({e})"),
814+
}
815+
}
751816
}

0 commit comments

Comments
 (0)