diff --git a/Cargo.lock b/Cargo.lock index 9cf93153..a653fb69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,7 +246,6 @@ version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f834a80c3ab6109b9c8f5ca6661a578cf31e088e831b6ce07c6b23cca04f6742" dependencies = [ - "aws-lc-rs", "base64 0.22.1", "bytes", "futures-util", @@ -258,6 +257,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regex", + "ring", "rustls-native-certs 0.7.3", "rustls-pemfile", "rustls-webpki 0.102.8", @@ -374,30 +374,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "axum" version = "0.8.4" @@ -493,26 +469,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.9.3", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.1", - "shlex", - "syn", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -740,15 +696,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.3" @@ -785,17 +732,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.46" @@ -855,15 +791,6 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "cobs" version = "0.3.0" @@ -1535,12 +1462,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clone" version = "1.0.20" @@ -1879,12 +1800,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -2107,12 +2022,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "glow" version = "0.16.0" @@ -4663,7 +4572,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -4772,7 +4681,6 @@ version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", @@ -4832,10 +4740,9 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -4844,10 +4751,9 @@ version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -5681,7 +5587,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f591660438b3038dd04d16c938271c79e7e06260ad2ea2885a4861bfb238605d" dependencies = [ - "aws-lc-rs", "base64 0.22.1", "bytes", "futures-core", @@ -5689,6 +5594,7 @@ dependencies = [ "http", "httparse", "rand 0.8.5", + "ring", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -6031,12 +5937,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 3ca1cdd5..52163b3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ pbjson-types = { version = "0.8.0", default-features = false } pbjson-build = { version = "0.8.0", default-features = false } prost = { version = "0.14", default-features = false } reqwest = { version = "0.12.20", default-features = false, features = ["json", "rustls-tls"] } -rustls = { version = "0.23", default-features = false, features = ["std", "tls12"] } +rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring"] } rustls-pemfile = { version = "2.2", default-features = false, features = ["std"] } schemars = { version = "0.8", default-features = false } git2 = { version = "0.19", default-features = false } diff --git a/crates/wash-runtime/Cargo.toml b/crates/wash-runtime/Cargo.toml index 4e507d10..9b921867 100644 --- a/crates/wash-runtime/Cargo.toml +++ b/crates/wash-runtime/Cargo.toml @@ -23,7 +23,7 @@ wasi-webgpu = ["dep:wasi-webgpu-wasmtime", "dep:wasi-graphics-context-wasmtime"] [dependencies] anyhow = { workspace = true } -async-nats = { workspace = true, features = ["aws-lc-rs"] } +async-nats = { workspace = true, features = ["ring"] } async-trait = { workspace = true } bytes = { workspace = true } chrono = { workspace = true } @@ -49,7 +49,7 @@ tokio-rustls = { workspace = true } tokio-util = { workspace = true, features = ["rt"] } tonic = { workspace = true, features = [ "gzip", - "tls-aws-lc", + "tls-ring", "transport", "router", "codegen", diff --git a/crates/wash-runtime/src/washlet/mod.rs b/crates/wash-runtime/src/washlet/mod.rs index 3f97c07f..212c605b 100644 --- a/crates/wash-runtime/src/washlet/mod.rs +++ b/crates/wash-runtime/src/washlet/mod.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -191,14 +192,59 @@ pub async fn run_cluster_host( }) } +/// Configuration options for NATS connections +#[derive(Debug, Clone, Default)] +pub struct NatsConnectionOptions { + /// Request timeout for NATS operations + pub request_timeout: Option, + /// Path to TLS CA certificate file for NATS connection + pub tls_ca: Option, + /// Enable TLS handshake first mode for NATS connection + pub tls_first: bool, + /// Path to NATS credentials file + pub credentials: Option, +} + pub async fn connect_nats( addr: impl async_nats::ToServerAddrs, - request_timeout: Option, + options: Option, ) -> Result { + let options = options.unwrap_or_default(); + + // Install the default crypto provider for rustls when using TLS options. + // This must be done before any TLS-related operations. + // We use ring for consistency with other dependencies in the workspace. + // It's safe to call multiple times - it will only install once. + if options.tls_ca.is_some() || options.tls_first { + let _ = rustls::crypto::ring::default_provider().install_default(); + } + let mut opts = async_nats::ConnectOptions::new(); - if let Some(timeout) = request_timeout { + if let Some(timeout) = options.request_timeout { opts = opts.request_timeout(Some(timeout)); - }; + } + if let Some(tls_ca) = options.tls_ca { + anyhow::ensure!( + tls_ca.exists(), + "NATS TLS CA certificate file does not exist: {}", + tls_ca.display() + ); + opts = opts.add_root_certificates(tls_ca); + } + if options.tls_first { + opts = opts.tls_first(); + } + if let Some(credentials) = options.credentials { + anyhow::ensure!( + credentials.exists(), + "NATS credentials file does not exist: {}", + credentials.display() + ); + opts = opts + .credentials_file(&credentials) + .await + .context("failed to load NATS credentials")?; + } opts.connect(addr) .await .context("failed to connect to NATS") @@ -611,8 +657,6 @@ impl From for types::v2::WorkloadStatus { #[cfg(test)] mod tests { - use crate::host; - use super::*; #[tokio::test] diff --git a/crates/wash/src/cli/host.rs b/crates/wash/src/cli/host.rs index 2523cf5b..9aef054e 100644 --- a/crates/wash/src/cli/host.rs +++ b/crates/wash/src/cli/host.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, sync::Arc, time::Duration}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use anyhow::Context as _; use clap::Args; @@ -22,6 +22,18 @@ pub struct HostCommand { #[clap(long = "data-nats-url", default_value = "nats://localhost:4222")] pub data_nats_url: String, + /// Path to TLS CA certificate file for NATS connection + #[clap(long = "nats-tls-ca")] + pub nats_tls_ca: Option, + + /// Enable TLS handshake first mode for NATS connection + #[clap(long = "nats-tls-first", default_value_t = false)] + pub nats_tls_first: bool, + + /// Path to NATS credentials file + #[clap(long = "nats-creds")] + pub nats_creds: Option, + /// The host name to assign to the host #[clap(long = "host-name")] pub host_name: Option, @@ -46,13 +58,20 @@ pub struct HostCommand { impl CliCommand for HostCommand { async fn handle(&self, _ctx: &CliContext) -> anyhow::Result { + let nats_options = Some(wash_runtime::washlet::NatsConnectionOptions { + tls_ca: self.nats_tls_ca.clone(), + tls_first: self.nats_tls_first, + credentials: self.nats_creds.clone(), + ..Default::default() + }); + let scheduler_nats_client = - wash_runtime::washlet::connect_nats(self.scheduler_nats_url.clone(), None) + wash_runtime::washlet::connect_nats(self.scheduler_nats_url.clone(), nats_options.clone()) .await .context("failed to connect to NATS Scheduler URL")?; let data_nats_client = - wash_runtime::washlet::connect_nats(self.data_nats_url.clone(), None) + wash_runtime::washlet::connect_nats(self.data_nats_url.clone(), nats_options) .await .context("failed to connect to NATS")?; let data_nats_client = Arc::new(data_nats_client); diff --git a/runtime-operator/.gitignore b/runtime-operator/.gitignore index 9ff929e4..71868fa6 100644 --- a/runtime-operator/.gitignore +++ b/runtime-operator/.gitignore @@ -34,4 +34,7 @@ tmp/** !tmp/.gitkeep # Junk -.DS_Store \ No newline at end of file +.DS_Store + +# Compiled binary +/main diff --git a/runtime-operator/cmd/main.go b/runtime-operator/cmd/main.go index 411fed8a..4fd4af3c 100644 --- a/runtime-operator/cmd/main.go +++ b/runtime-operator/cmd/main.go @@ -61,6 +61,8 @@ func main() { metricsAddr string natsUrl string natsCreds string + natsTlsCa string + natsTlsFirst bool enableLeaderElection bool probeAddr string secureMetrics bool @@ -76,6 +78,8 @@ func main() { flag.StringVar(&probeAddr, "health-probe-bind-address", ":8082", "The address the probe endpoint binds to.") flag.StringVar(&natsCreds, "nats-creds", "", "Path to NATS credentials file.") flag.StringVar(&natsUrl, "nats-url", wasmbus.NatsDefaultURL, "The nats server address to connect to.") + flag.StringVar(&natsTlsCa, "nats-tls-ca", "", "Path to TLS CA certificate file for NATS connection.") + flag.BoolVar(&natsTlsFirst, "nats-tls-first", false, "Enable TLS handshake first mode for NATS connection.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") @@ -122,9 +126,25 @@ func main() { } if natsCreds != "" { + if _, err := os.Stat(natsCreds); os.IsNotExist(err) { + setupLog.Error(err, "NATS credentials file does not exist", "path", natsCreds) + os.Exit(1) + } operatorCfg.NatsOptions = append(operatorCfg.NatsOptions, nats.UserCredentials(natsCreds)) } + if natsTlsCa != "" { + if _, err := os.Stat(natsTlsCa); os.IsNotExist(err) { + setupLog.Error(err, "NATS TLS CA certificate file does not exist", "path", natsTlsCa) + os.Exit(1) + } + operatorCfg.NatsOptions = append(operatorCfg.NatsOptions, nats.RootCAs(natsTlsCa)) + } + + if natsTlsFirst { + operatorCfg.NatsOptions = append(operatorCfg.NatsOptions, nats.TLSHandshakeFirst()) + } + // if the enable-http2 flag is false (the default), http/2 should be disabled // due to its vulnerabilities. More specifically, disabling http/2 will // prevent from being vulnerable to the HTTP/2 Stream Cancellation and