diff --git a/crates/ruvector-hailo-cluster/src/bin/embed.rs b/crates/ruvector-hailo-cluster/src/bin/embed.rs index f79bf5d6c..7af3c660b 100644 --- a/crates/ruvector-hailo-cluster/src/bin/embed.rs +++ b/crates/ruvector-hailo-cluster/src/bin/embed.rs @@ -69,6 +69,18 @@ fn main() -> Result<(), Box> { // texts are embedded in order and the binary exits. Repeat the // flag to embed multiple texts in one invocation. let mut inline_texts: Vec = Vec::new(); + // Iter 188 — symmetric TLS plumbing (mirror of iter-187 bench + // additions). Lets ops drive a single embed against a TLS-enabled + // worker without building a custom client. All flags + // `#[cfg(feature = "tls")]` so the no-tls build is unchanged. + #[cfg(feature = "tls")] + let mut tls_ca: Option = None; + #[cfg(feature = "tls")] + let mut tls_domain: Option = None; + #[cfg(feature = "tls")] + let mut tls_client_cert: Option = None; + #[cfg(feature = "tls")] + let mut tls_client_key: Option = None; let mut i = 1; while i < args.len() { @@ -130,6 +142,14 @@ fn main() -> Result<(), Box> { } i += 2; } + #[cfg(feature = "tls")] + "--tls-ca" => { tls_ca = args.get(i + 1).cloned(); i += 2; } + #[cfg(feature = "tls")] + "--tls-domain" => { tls_domain = args.get(i + 1).cloned(); i += 2; } + #[cfg(feature = "tls")] + "--tls-client-cert" => { tls_client_cert = args.get(i + 1).cloned(); i += 2; } + #[cfg(feature = "tls")] + "--tls-client-key" => { tls_client_key = args.get(i + 1).cloned(); i += 2; } "--help" | "-h" => { print_help(); return Ok(()); } "--version" | "-V" => { println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); @@ -194,6 +214,55 @@ fn main() -> Result<(), Box> { } } + // Iter 188 — TLS transport when --tls-ca is set; mirrors iter-187 + // bench plumbing. Same partial-config + orphan-flag refusals so a + // misconfigured invocation surfaces an early error instead of a + // silent plaintext downgrade. + #[cfg(feature = "tls")] + let transport: Arc = { + if let Some(ca_path) = tls_ca.as_deref() { + let addr0 = workers.first().map(|w| w.address.clone()).unwrap_or_default(); + let domain = tls_domain.clone().unwrap_or_else(|| { + ruvector_hailo_cluster::tls::domain_from_address(&addr0).to_string() + }); + let mut tls = ruvector_hailo_cluster::tls::TlsClient::from_pem_files(ca_path, &domain) + .map_err(|e| format!("--tls-ca: {}", e))?; + match (tls_client_cert.as_deref(), tls_client_key.as_deref()) { + (Some(c), Some(k)) => { + tls = tls.with_client_identity(c, k) + .map_err(|e| format!("--tls-client-cert/--tls-client-key: {}", e))?; + if !quiet { + eprintln!("ruvector-hailo-embed: mTLS client identity attached"); + } + } + (Some(_), None) | (None, Some(_)) => { + return Err( + "--tls-client-cert and --tls-client-key must both be set or both unset".into(), + ); + } + (None, None) => {} + } + if !quiet { + eprintln!( + "ruvector-hailo-embed: TLS enabled ca={} domain={}", + ca_path, domain + ); + } + Arc::new(GrpcTransport::with_tls( + std::time::Duration::from_secs(5), + std::time::Duration::from_secs(2), + tls, + )?) + } else { + if tls_domain.is_some() || tls_client_cert.is_some() || tls_client_key.is_some() { + return Err( + "--tls-domain / --tls-client-cert / --tls-client-key require --tls-ca".into(), + ); + } + Arc::new(GrpcTransport::new()?) + } + }; + #[cfg(not(feature = "tls"))] let transport: Arc = Arc::new(GrpcTransport::new()?); @@ -601,6 +670,18 @@ OPTIONS: text and exit (skips stdin). Repeat the flag to embed multiple texts in one invocation. + --tls-ca Enable HTTPS by trusting the PEM CA + bundle at . Without this the + embed CLI dials plaintext gRPC. + (Requires --features tls.) + --tls-domain SNI / SAN value to assert against the + server cert. Defaults to the hostname + half of the first worker address. + --tls-client-cert mTLS client cert (PEM). Pair with + --tls-client-key. + --tls-client-key mTLS client private key (PEM). Both + cert and key must be set or both + unset. --help, -h Print this help and exit. --version, -V Print the binary name + version and exit.