From 4ba03ff5a1cced77946200909e900714b2a97420 Mon Sep 17 00:00:00 2001 From: Conor Schaefer Date: Mon, 13 Jan 2025 10:39:35 -0800 Subject: [PATCH] fix: use rustls for https client support Pulls in a dependency on `rustls` to configure the TLS config for HTTPS clients. Refactored the `pd_channel` logic into a reusable class method. --- Cargo.lock | 38 ++++++++++++++-------------- Cargo.toml | 1 + crates/bin/pcli/Cargo.toml | 1 + crates/bin/pcli/src/command/tx.rs | 2 +- crates/bin/pcli/src/main.rs | 7 ++++++ crates/bin/pcli/src/network.rs | 21 ++++++---------- crates/bin/pclientd/Cargo.toml | 1 + crates/bin/pclientd/src/lib.rs | 6 +---- crates/bin/pclientd/src/main.rs | 7 ++++++ crates/bin/pmonitor/Cargo.toml | 1 + crates/bin/pmonitor/src/main.rs | 26 +++++++------------- crates/misc/measure/Cargo.toml | 1 + crates/misc/measure/src/main.rs | 26 +++++++++----------- crates/view/Cargo.toml | 2 +- crates/view/src/service.rs | 41 +++++++++++++++++++++++++------ 15 files changed, 103 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20d2827160..e851194c07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -616,12 +616,11 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" +checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" dependencies = [ "aws-lc-sys", - "mirai-annotations", "paste", "untrusted 0.7.1", "zeroize", @@ -629,16 +628,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" +checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" dependencies = [ - "bindgen 0.69.4", + "bindgen 0.69.5", "cc", "cmake", "dunce", "fs_extra", - "libc", "paste", ] @@ -736,7 +734,7 @@ dependencies = [ "hyper 1.5.1", "hyper-util", "pin-project-lite", - "rustls 0.23.20", + "rustls 0.23.21", "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", @@ -835,9 +833,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.4" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags 2.6.0", "cexpr", @@ -2409,7 +2407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.20", + "rustls 0.23.21", "rustls-pki-types", ] @@ -2892,7 +2890,7 @@ dependencies = [ "http 1.2.0", "hyper 1.5.1", "hyper-util", - "rustls 0.23.20", + "rustls 0.23.21", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -3601,7 +3599,7 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae9ea4b75e1a81675429dafe43441df1caea70081e82246a8cccf514884a88bb" dependencies = [ - "bindgen 0.69.4", + "bindgen 0.69.5", "errno", "libc", ] @@ -4420,6 +4418,7 @@ dependencies = [ "rand_core", "regex", "rpassword", + "rustls 0.23.21", "serde", "serde_json", "serde_with", @@ -4474,6 +4473,7 @@ dependencies = [ "prost", "rand", "rand_core", + "rustls 0.23.21", "serde", "serde_json", "serde_with", @@ -4837,7 +4837,7 @@ dependencies = [ "anyhow", "axum-server", "futures", - "rustls 0.23.20", + "rustls 0.23.21", "rustls-acme", "tracing", ] @@ -5288,6 +5288,7 @@ dependencies = [ "penumbra-proto", "penumbra-view", "predicates 2.1.5", + "rustls 0.23.21", "serde_json", "tokio", "tonic", @@ -6105,6 +6106,7 @@ dependencies = [ "penumbra-tct", "penumbra-view", "regex", + "rustls 0.23.21", "serde", "serde_json", "tempfile", @@ -6995,9 +6997,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ "aws-lc-rs", "log", @@ -7634,7 +7636,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.23.20", + "rustls 0.23.21", "rustls-pemfile 2.2.0", "serde", "serde_json", @@ -8351,7 +8353,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.20", + "rustls 0.23.21", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index cd86283743..e3b4e4ef93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -212,6 +212,7 @@ rand_chacha = { version = "0.3.1" } rand_core = { version = "0.6.4" } regex = { version = "1.8.1" } rocksdb = { version = "0.21.0" } +rustls = { version = "0.23.21" } serde = { version = "1.0.186" } serde_json = { version = "1.0.96" } serde_unit_struct = { version = "0.1" } diff --git a/crates/bin/pcli/Cargo.toml b/crates/bin/pcli/Cargo.toml index 4f0f9da2cc..bca319b6e3 100644 --- a/crates/bin/pcli/Cargo.toml +++ b/crates/bin/pcli/Cargo.toml @@ -88,6 +88,7 @@ rand_chacha = {workspace = true} rand_core = {workspace = true, features = ["getrandom"]} regex = {workspace = true} rpassword = "7" +rustls = {workspace = true} serde = {workspace = true, features = ["derive"]} serde_json = {workspace = true} serde_with = {workspace = true, features = ["hex"]} diff --git a/crates/bin/pcli/src/command/tx.rs b/crates/bin/pcli/src/command/tx.rs index 3b5a943941..65fd9b1a70 100644 --- a/crates/bin/pcli/src/command/tx.rs +++ b/crates/bin/pcli/src/command/tx.rs @@ -1409,7 +1409,7 @@ impl TxCmd { let mut noble_client = CosmosServiceClient::new( Channel::from_shared(noble_node.to_string())? - .tls_config(ClientTlsConfig::new())? + .tls_config(ClientTlsConfig::new().with_webpki_roots())? .connect() .await?, ); diff --git a/crates/bin/pcli/src/main.rs b/crates/bin/pcli/src/main.rs index 43be79b1bf..b6990d0db0 100644 --- a/crates/bin/pcli/src/main.rs +++ b/crates/bin/pcli/src/main.rs @@ -5,6 +5,7 @@ use std::fs; use anyhow::{Context, Result}; use clap::Parser; +use rustls::crypto::aws_lc_rs; use pcli::{command::*, opt::Opt}; @@ -21,6 +22,12 @@ async fn main() -> Result<()> { // that tracing is set up even for wallet commands that don't build the `App`. opt.init_tracing(); + // Initialize HTTPS support + // rustls::crypto::aws_lc_rs::default_provider().install_default(); + aws_lc_rs::default_provider() + .install_default() + .expect("failed to initialize rustls support, via aws-lc-rs"); + //Ensure that the data_path exists, in case this is a cold start fs::create_dir_all(&opt.home) .with_context(|| format!("Failed to create home directory {}", opt.home))?; diff --git a/crates/bin/pcli/src/network.rs b/crates/bin/pcli/src/network.rs index 56c7331536..f809f7543f 100644 --- a/crates/bin/pcli/src/network.rs +++ b/crates/bin/pcli/src/network.rs @@ -10,9 +10,9 @@ use penumbra_proto::{ }; use penumbra_stake::validator::Validator; use penumbra_transaction::{txhash::TransactionId, Transaction, TransactionPlan}; -use penumbra_view::ViewClient; +use penumbra_view::{ViewClient, ViewServer}; use std::{fs, future::Future}; -use tonic::transport::{Channel, ClientTlsConfig}; +use tonic::transport::Channel; use tracing::instrument; use crate::App; @@ -177,19 +177,12 @@ impl App { Ok(()) } - // TODO: why do we need this here but not in the view crate? + /// Convenience method for obtaining a `tonic::Channel` for the remote + /// `pd` endpoint, as configured for `pcli`. pub async fn pd_channel(&self) -> anyhow::Result { - match self.config.grpc_url.scheme() { - "http" => Ok(Channel::from_shared(self.config.grpc_url.to_string())? - .connect() - .await?), - "https" => Ok(Channel::from_shared(self.config.grpc_url.to_string())? - .tls_config(ClientTlsConfig::new())? - .connect() - .await?), - other => Err(anyhow::anyhow!("unknown url scheme {other}")) - .with_context(|| format!("could not connect to {}", self.config.grpc_url)), - } + ViewServer::get_pd_channel(self.config.grpc_url.clone()) + .await + .context(format!("could not connect to {}", self.config.grpc_url)) } pub async fn tendermint_proxy_client( diff --git a/crates/bin/pclientd/Cargo.toml b/crates/bin/pclientd/Cargo.toml index eafbf96aa1..69749becbb 100644 --- a/crates/bin/pclientd/Cargo.toml +++ b/crates/bin/pclientd/Cargo.toml @@ -38,6 +38,7 @@ penumbra-view = {workspace = true} prost = {workspace = true} rand = {workspace = true} rand_core = {workspace = true, features = ["getrandom"]} +rustls = {workspace = true} serde = {workspace = true, features = ["derive"]} serde_json = {workspace = true} serde_with = {workspace = true, features = ["hex"]} diff --git a/crates/bin/pclientd/src/lib.rs b/crates/bin/pclientd/src/lib.rs index cf0bc5b979..d198d4d533 100644 --- a/crates/bin/pclientd/src/lib.rs +++ b/crates/bin/pclientd/src/lib.rs @@ -298,11 +298,7 @@ impl Opt { .load_or_init_sqlite(&config.full_viewing_key, &config.grpc_url) .await?; - let proxy_channel = - tonic::transport::Channel::from_shared(config.grpc_url.to_string()) - .expect("this is a valid address") - .connect() - .await?; + let proxy_channel = ViewServer::get_pd_channel(config.grpc_url.clone()).await?; let app_query_proxy = AppQueryProxy(proxy_channel.clone()); let governance_query_proxy = GovernanceQueryProxy(proxy_channel.clone()); diff --git a/crates/bin/pclientd/src/main.rs b/crates/bin/pclientd/src/main.rs index 377028314a..87e12a0723 100644 --- a/crates/bin/pclientd/src/main.rs +++ b/crates/bin/pclientd/src/main.rs @@ -3,6 +3,7 @@ use std::io::IsTerminal as _; use anyhow::Result; use clap::Parser; +use rustls::crypto::aws_lc_rs; use tracing_subscriber::{prelude::*, EnvFilter}; use pclientd::Opt; @@ -24,5 +25,11 @@ async fn main() -> Result<()> { let opt = Opt::parse(); + // Initialize HTTPS support + // rustls::crypto::aws_lc_rs::default_provider().install_default(); + aws_lc_rs::default_provider() + .install_default() + .expect("failed to initialize rustls support, via aws-lc-rs"); + opt.exec().await } diff --git a/crates/bin/pmonitor/Cargo.toml b/crates/bin/pmonitor/Cargo.toml index f9799f9f91..bdc18cbb05 100644 --- a/crates/bin/pmonitor/Cargo.toml +++ b/crates/bin/pmonitor/Cargo.toml @@ -31,6 +31,7 @@ penumbra-stake = {workspace = true, default-features = false} penumbra-tct = {workspace = true, default-features = false} penumbra-view = {workspace = true} regex = {workspace = true} +rustls = {workspace = true} serde = {workspace = true, features = ["derive"]} serde_json = {workspace = true} tokio = {workspace = true, features = ["full"]} diff --git a/crates/bin/pmonitor/src/main.rs b/crates/bin/pmonitor/src/main.rs index 372f425adb..d8d448c0c0 100644 --- a/crates/bin/pmonitor/src/main.rs +++ b/crates/bin/pmonitor/src/main.rs @@ -20,10 +20,11 @@ use clap::{self, Parser}; use directories::ProjectDirs; use futures::StreamExt; use penumbra_asset::STAKING_TOKEN_ASSET_ID; +use rustls::crypto::aws_lc_rs; use std::fs; use std::io::IsTerminal as _; use std::str::FromStr; -use tonic::transport::{Channel, ClientTlsConfig}; +use tonic::transport::Channel; use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; use uuid::Uuid; @@ -86,6 +87,12 @@ fn init_tracing() -> anyhow::Result<()> { async fn main() -> Result<()> { let opt = Opt::parse(); init_tracing()?; + + // Initialize HTTPS support + aws_lc_rs::default_provider() + .install_default() + .expect("failed to initialize rustls support, via aws-lc-rs"); + tracing::info!(?opt, version = env!("CARGO_PKG_VERSION"), "running command"); opt.exec().await } @@ -226,21 +233,6 @@ impl Opt { compact_block.try_into() } - /// Stolen from pcli - pub async fn pd_channel(&self, grpc_url: Url) -> anyhow::Result { - match grpc_url.scheme() { - "http" => Ok(Channel::from_shared(grpc_url.to_string())? - .connect() - .await?), - "https" => Ok(Channel::from_shared(grpc_url.to_string())? - .tls_config(ClientTlsConfig::new())? - .connect() - .await?), - other => Err(anyhow::anyhow!("unknown url scheme {other}")) - .with_context(|| format!("could not connect to {}", grpc_url)), - } - } - /// Create wallet given a path and fvk pub async fn create_wallet( &self, @@ -403,7 +395,7 @@ impl Opt { ))?)?; let mut stake_client = StakeQueryServiceClient::new( - self.pd_channel(pmonitor_config.grpc_url()).await?, + ViewServer::get_pd_channel(pmonitor_config.grpc_url()).await?, ); // Sync each wallet to the latest block height, check for new migrations, and check the balance. diff --git a/crates/misc/measure/Cargo.toml b/crates/misc/measure/Cargo.toml index ef48253a4b..f60b627eb3 100644 --- a/crates/misc/measure/Cargo.toml +++ b/crates/misc/measure/Cargo.toml @@ -21,6 +21,7 @@ indicatif = {workspace = true} penumbra-compact-block = {workspace = true, default-features = false} penumbra-proto = {workspace = true, features = ["rpc"], default-features = true} penumbra-view = {workspace = true} +rustls = {workspace = true} serde_json = {workspace = true} tokio = {workspace = true, features = ["full"]} tonic = {workspace = true, features = ["tls"]} diff --git a/crates/misc/measure/src/main.rs b/crates/misc/measure/src/main.rs index 38211fe3d2..9cd9e7ec5d 100644 --- a/crates/misc/measure/src/main.rs +++ b/crates/misc/measure/src/main.rs @@ -4,6 +4,7 @@ extern crate tracing; use std::path::PathBuf; use clap::Parser; +use rustls::crypto::aws_lc_rs; use tracing::Instrument; use tracing_subscriber::EnvFilter; @@ -21,8 +22,9 @@ use penumbra_proto::{ }, DomainType, Message, }; +use penumbra_view::ViewServer; -use tonic::transport::{Channel, ClientTlsConfig}; +use tonic::transport::Channel; use url::Url; // The expected maximum size of a compact block message. @@ -215,9 +217,7 @@ impl Opt { } } Command::StreamBlocks { skip_genesis } => { - let channel = Channel::from_shared(self.node.to_string())? - .connect() - .await?; + let channel = ViewServer::get_pd_channel(self.node.clone()).await?; let mut cb_client = CompactBlockQueryServiceClient::new(channel.clone()) .max_decoding_message_size(MAX_CB_SIZE_BYTES); @@ -306,25 +306,21 @@ impl Opt { } } -// This code is ripped from the pcli code, and could be split out into something common. +// Wrapper for the `get_pd_channel` method from the view crate. async fn get_tendermint_proxy_client( pd_url: Url, ) -> anyhow::Result> { - let pd_channel: Channel = match pd_url.scheme() { - "http" => Channel::from_shared(pd_url.to_string())?.connect().await?, - "https" => { - Channel::from_shared(pd_url.to_string())? - .tls_config(ClientTlsConfig::new())? - .connect() - .await? - } - other => anyhow::bail!(format!("unknown url scheme {other}")), - }; + let pd_channel = ViewServer::get_pd_channel(pd_url).await?; Ok(TendermintProxyServiceClient::new(pd_channel)) } #[tokio::main] async fn main() -> anyhow::Result<()> { + // Initialize HTTPS support + aws_lc_rs::default_provider() + .install_default() + .expect("failed to initialize rustls support, via aws-lc-rs"); + let mut opt = Opt::parse(); opt.init_tracing(); opt.run().await?; diff --git a/crates/view/Cargo.toml b/crates/view/Cargo.toml index e4e849b3e1..3e6e067a42 100644 --- a/crates/view/Cargo.toml +++ b/crates/view/Cargo.toml @@ -65,7 +65,7 @@ tap = {workspace = true} tendermint = {workspace = true} tokio = {workspace = true, features = ["full"]} tokio-stream = {workspace = true, features = ["sync"]} -tonic = {workspace = true} +tonic = {workspace = true, features = ["tls", "tls-webpki-roots"]} tracing = {workspace = true} tracing-subscriber = {workspace = true} url = {workspace = true} diff --git a/crates/view/src/service.rs b/crates/view/src/service.rs index 8e3c8ce26c..465dc8c028 100644 --- a/crates/view/src/service.rs +++ b/crates/view/src/service.rs @@ -17,6 +17,8 @@ use rand_core::OsRng; use tap::{Tap, TapFallible}; use tokio::sync::{watch, RwLock}; use tokio_stream::wrappers::WatchStream; +use tonic::transport::channel::ClientTlsConfig; +use tonic::transport::channel::Endpoint; use tonic::{async_trait, transport::Channel, Request, Response, Status}; use tracing::{instrument, Instrument}; use url::Url; @@ -123,13 +125,7 @@ impl ViewServer { /// will be backed by the same scanning task, rather than each spawning its own. pub async fn new(storage: Storage, node: Url) -> anyhow::Result { let span = tracing::error_span!(parent: None, "view"); - let channel = Channel::from_shared(node.to_string()) - .with_context(|| "could not parse node URI")? - .connect() - .instrument(span.clone()) - .await - .with_context(|| "could not connect to grpc server") - .tap_err(|error| tracing::error!(?error, "could not connect to grpc server"))?; + let channel = Self::get_pd_channel(node.clone()).await?; let (worker, state_commitment_tree, error_slot, sync_height_rx) = Worker::new(storage.clone(), channel) @@ -150,6 +146,24 @@ impl ViewServer { }) } + /// Obtain a Tonic [Channel] to a remote `pd` endpoint. + /// + /// Provided as a convenience method for bootstrapping a connection. + /// Handles configuring TLS if the URL is HTTPS. Also adds a tracing span + /// to the working [Channel]. + pub async fn get_pd_channel(node: Url) -> anyhow::Result { + let endpoint = get_pd_endpoint(node).await?; + let span = tracing::error_span!(parent: None, "view"); + let c: Channel = endpoint + .connect() + .instrument(span.clone()) + .await + .with_context(|| "could not connect to grpc server") + .tap_err(|error| tracing::error!(?error, "could not connect to grpc server"))?; + + Ok(c) + } + /// Checks if the view server worker has encountered an error. /// /// This function returns a gRPC [`tonic::Status`] containing the view server worker error if @@ -1843,3 +1857,16 @@ impl ViewService for ViewServer { unimplemented!("unbonding_tokens_by_address_index currently only implemented on web") } } + +/// Convert a pd node URL to a Tonic `Endpoint`. +/// +/// Required in order to configure TLS for HTTPS endpoints. +async fn get_pd_endpoint(node: Url) -> anyhow::Result { + let endpoint = match node.scheme() { + "http" => Channel::from_shared(node.to_string())?, + "https" => Channel::from_shared(node.to_string())? + .tls_config(ClientTlsConfig::new().with_webpki_roots())?, + other => anyhow::bail!("unknown url scheme {other}"), + }; + Ok(endpoint) +}