From 8d6917c98c75188ebba8e07cddea5217fb75aaa7 Mon Sep 17 00:00:00 2001 From: Adel Zaalouk Date: Mon, 20 Jul 2026 11:15:42 +0200 Subject: [PATCH] fix(cli): present client certificates in --gateway-insecure mode --gateway-insecure (OPENSHELL_GATEWAY_INSECURE=true) disables TLS server certificate verification but was also dropping client certificate presentation via .with_no_client_auth(). This broke mTLS gateways that require client certs. Now build_insecure_rustls_config accepts optional TlsMaterials. Both call sites (build_channel and http_health_check) attempt to load client certs from the gateway's mtls directory and pass them through. If certs are unavailable, falls back to no client auth. Fixes #2354 Signed-off-by: Adel Zaalouk --- crates/openshell-cli/src/run.rs | 3 +- crates/openshell-cli/src/tls.rs | 78 ++++++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index d9b2df10b5..922b9f70f9 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -1611,7 +1611,8 @@ async fn http_health_check(server: &str, tls: &TlsOptions) -> Result for InsecureTlsConnector { } } -pub fn build_insecure_rustls_config() -> Result { - let config = rustls::ClientConfig::builder() +pub fn build_insecure_rustls_config( + materials: Option<&TlsMaterials>, +) -> Result { + let dangerous = rustls::ClientConfig::builder() .dangerous() - .with_custom_certificate_verifier(std::sync::Arc::new(InsecureServerCertVerifier)) - .with_no_client_auth(); + .with_custom_certificate_verifier(std::sync::Arc::new(InsecureServerCertVerifier)); + let config = if let Some(m) = materials { + let mut cert_cursor = Cursor::new(&m.cert); + let cert_chain = rustls_pemfile::certs(&mut cert_cursor) + .collect::>, _>>() + .into_diagnostic()?; + let key = load_private_key(&m.key)?; + dangerous + .with_client_auth_cert(cert_chain, key) + .into_diagnostic()? + } else { + dangerous.with_no_client_auth() + }; Ok(config) } @@ -372,7 +385,8 @@ pub async fn build_channel(server: &str, tls: &TlsOptions) -> Result { if tls.gateway_insecure && server.starts_with("https://") { tracing::warn!("TLS certificate verification is disabled — do not use in production"); - let rustls_config = build_insecure_rustls_config()?; + let materials = require_tls_materials(server, tls).ok(); + let rustls_config = build_insecure_rustls_config(materials.as_ref())?; let tls_connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(rustls_config)); let connector = InsecureTlsConnector { tls_connector }; // Use http:// so tonic does not layer its own TLS on top — our @@ -449,3 +463,57 @@ pub async fn grpc_inference_client(server: &str, tls: &TlsOptions) -> Result TlsMaterials { + use rcgen::{BasicConstraints, CertificateParams, ExtendedKeyUsagePurpose, IsCa, KeyPair}; + let ca_key = KeyPair::generate().unwrap(); + let mut ca_params = CertificateParams::new(Vec::::new()).unwrap(); + ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + let ca_cert = ca_params.self_signed(&ca_key).unwrap(); + + let client_key = KeyPair::generate().unwrap(); + let mut client_params = CertificateParams::new(Vec::::new()).unwrap(); + client_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth]; + let client_cert = client_params + .signed_by(&client_key, &ca_cert, &ca_key) + .unwrap(); + + TlsMaterials { + ca: ca_cert.pem().into_bytes(), + cert: client_cert.pem().into_bytes(), + key: client_key.serialize_pem().into_bytes(), + } + } + + #[test] + fn insecure_config_without_materials_succeeds() { + install_provider(); + build_insecure_rustls_config(None).expect("should build without client materials"); + } + + #[test] + fn insecure_config_with_materials_succeeds() { + install_provider(); + let materials = generate_test_materials(); + build_insecure_rustls_config(Some(&materials)).expect("should build with client materials"); + } + + #[test] + fn insecure_config_with_invalid_cert_fails() { + install_provider(); + let materials = TlsMaterials { + ca: b"not a cert".to_vec(), + cert: b"not a cert".to_vec(), + key: b"not a key".to_vec(), + }; + assert!(build_insecure_rustls_config(Some(&materials)).is_err()); + } +}