From b80f7e366b14e10b3fb0e9835fb76dd5596d0cf8 Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Thu, 30 Nov 2023 14:29:24 +0100 Subject: [PATCH] feat: grpc over tls (#5990) Description --- Add configuration and support for operating grpc over tls to enhance the overall security of communicating nodes, wallets, and miners. This includes a self-signed certificate generation function for using locally, although it's highly recommended to generate valid, and verifiable certificates if you plan on opening any service up to the internet. ~TODO (before merge, eta: 1day)~: Complete - ~Expand to the merge miner~ - ~Expand to the wallet~ - ~Display a warning about security when generating self signed certificates~ Motivation and Context --- Mo' security mo' better. Closes: #5808 How Has This Been Tested? --- Locally What process can a PR reviewer use to test or verify this change? --- Mostly read about how to set it up, and see if that makes sense. Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --- Cargo.lock | 113 ++++++++++++++++-- applications/minotari_app_grpc/Cargo.toml | 4 +- applications/minotari_app_grpc/src/lib.rs | 2 + .../minotari_app_grpc/src/tls/certs.rs | 78 ++++++++++++ .../minotari_app_grpc/src/tls/error.rs | 35 ++++++ .../minotari_app_grpc/src/tls/identity.rs | 39 ++++++ applications/minotari_app_grpc/src/tls/mod.rs | 29 +++++ .../src/automation/commands.rs | 20 ++++ .../src/automation/error.rs | 3 + .../minotari_console_wallet/src/cli.rs | 1 + .../src/wallet_modes.rs | 45 ++++++- .../minotari_merge_mining_proxy/src/config.rs | 19 +++ .../minotari_merge_mining_proxy/src/error.rs | 2 + .../src/run_merge_miner.rs | 29 ++++- applications/minotari_miner/Cargo.toml | 20 ++-- applications/minotari_miner/src/config.rs | 20 +++- applications/minotari_miner/src/errors.rs | 2 + applications/minotari_miner/src/run_miner.rs | 29 ++++- applications/minotari_node/Cargo.toml | 2 +- .../src/commands/command/create_tls_certs.rs | 63 ++++++++++ .../minotari_node/src/commands/command/mod.rs | 4 + applications/minotari_node/src/config.rs | 9 ++ applications/minotari_node/src/lib.rs | 23 +++- base_layer/wallet/src/config.rs | 9 ++ common/config/presets/c_base_node.toml | 3 + common/config/presets/g_miner.toml | 2 + common/src/exit_codes.rs | 2 + 27 files changed, 567 insertions(+), 40 deletions(-) create mode 100644 applications/minotari_app_grpc/src/tls/certs.rs create mode 100644 applications/minotari_app_grpc/src/tls/error.rs create mode 100644 applications/minotari_app_grpc/src/tls/identity.rs create mode 100644 applications/minotari_app_grpc/src/tls/mod.rs create mode 100644 applications/minotari_node/src/commands/command/create_tls_certs.rs diff --git a/Cargo.lock b/Cargo.lock index 2537059b88..c4ff443a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3033,6 +3033,7 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "rand", + "rcgen", "subtle", "tari_common_types", "tari_comms", @@ -3041,6 +3042,7 @@ dependencies = [ "tari_script", "tari_utilities", "thiserror", + "tokio", "tonic 0.6.2", "tonic-build", "zeroize", @@ -3942,6 +3944,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" +[[package]] +name = "pem" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +dependencies = [ + "base64 0.21.5", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -4549,6 +4561,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -4793,6 +4817,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.1", + "log", + "ring 0.16.20", + "sct 0.6.1", + "webpki 0.21.4", +] + [[package]] name = "rustls" version = "0.20.9" @@ -4801,8 +4838,20 @@ checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring 0.16.20", - "sct", - "webpki", + "sct 0.7.1", + "webpki 0.22.4", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", ] [[package]] @@ -4914,6 +4963,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + [[package]] name = "sct" version = "0.7.1" @@ -5993,7 +6052,7 @@ dependencies = [ "prost 0.9.0", "rand", "reqwest", - "rustls", + "rustls 0.20.9", "semver", "serde", "tari_common", @@ -6011,7 +6070,7 @@ dependencies = [ "tokio-stream", "tower", "trust-dns-client", - "webpki", + "webpki 0.22.4", ] [[package]] @@ -6306,15 +6365,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + [[package]] name = "tokio-rustls" version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", - "webpki", + "webpki 0.22.4", ] [[package]] @@ -6422,7 +6492,9 @@ dependencies = [ "pin-project 1.1.3", "prost 0.9.0", "prost-derive 0.9.0", + "rustls-native-certs", "tokio", + "tokio-rustls 0.22.0", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -6591,12 +6663,12 @@ dependencies = [ "radix_trie", "rand", "ring 0.16.20", - "rustls", + "rustls 0.20.9", "thiserror", "time", "tokio", "trust-dns-proto", - "webpki", + "webpki 0.22.4", ] [[package]] @@ -6618,15 +6690,15 @@ dependencies = [ "log", "rand", "ring 0.16.20", - "rustls", + "rustls 0.20.9", "rustls-pemfile 0.3.0", "smallvec", "thiserror", "tinyvec", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "url", - "webpki", + "webpki 0.22.4", ] [[package]] @@ -6998,6 +7070,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + [[package]] name = "webpki" version = "0.22.4" @@ -7255,6 +7337,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.7.21" diff --git a/applications/minotari_app_grpc/Cargo.toml b/applications/minotari_app_grpc/Cargo.toml index b020837a53..25c0dbb9f9 100644 --- a/applications/minotari_app_grpc/Cargo.toml +++ b/applications/minotari_app_grpc/Cargo.toml @@ -23,10 +23,12 @@ log = "0.4" prost = "0.9" prost-types = "0.9" rand = "0.8" +rcgen = "0.11.3" +subtle = { version = "2.5.0", features = ["core_hint_black_box"] } thiserror = "1" +tokio = "1.23" tonic = "0.6.2" zeroize = "1" -subtle = { version = "2.5.0", features = ["core_hint_black_box"] } [build-dependencies] tonic-build = "0.6.2" diff --git a/applications/minotari_app_grpc/src/lib.rs b/applications/minotari_app_grpc/src/lib.rs index 6cf3715887..326c7630e8 100644 --- a/applications/minotari_app_grpc/src/lib.rs +++ b/applications/minotari_app_grpc/src/lib.rs @@ -23,6 +23,8 @@ pub mod authentication; pub mod conversions; +pub mod tls; + pub mod tari_rpc { tonic::include_proto!("tari.rpc"); } diff --git a/applications/minotari_app_grpc/src/tls/certs.rs b/applications/minotari_app_grpc/src/tls/certs.rs new file mode 100644 index 0000000000..bc124e58e9 --- /dev/null +++ b/applications/minotari_app_grpc/src/tls/certs.rs @@ -0,0 +1,78 @@ +// Copyright 2023. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; + +use rcgen::{generate_simple_self_signed, Certificate, CertificateParams, DnType, IsCa::Ca}; + +use crate::tls::error::GrpcTlsError; + +pub fn generate_self_signed_certs() -> Result<(String, String, String), GrpcTlsError> { + let subject_alt_names = vec!["localhost".to_string(), "127.0.0.1".to_string(), "0.0.0.0".to_string()]; + let mut params = CertificateParams::new(subject_alt_names.clone()); + params.distinguished_name.push(DnType::CommonName, "127.0.0.1"); + params.is_ca = Ca(rcgen::BasicConstraints::Unconstrained); + let ca = Certificate::from_params(params).unwrap(); + let cacert = ca.serialize_pem().unwrap(); + + let server_cert = generate_simple_self_signed(subject_alt_names).unwrap(); + + Ok(( + cacert, + server_cert.serialize_pem_with_signer(&ca).unwrap(), + server_cert.serialize_private_key_pem(), + )) +} + +pub fn write_cert_to_disk(dir: PathBuf, filename: &str, data: &String) -> Result<(), GrpcTlsError> { + let path = dir.join(Path::new(filename)); + let mut file = File::create(&path)?; + file.write_all(data.as_ref())?; + + println!("{:?} written to disk.", path); + + Ok(()) +} +pub fn print_warning() { + println!( + "⚠️WARNING: The use of self-signed TLS certificates poses a significant security risk. These certificates are \ + not issued or verified by a trusted Certificate Authority (CA), making them susceptible to man-in-the-middle \ + attacks. When employing self-signed certificates, the encryption provided is compromised, and your data may \ + be intercepted or manipulated without detection." + ); + println!(); + println!( + "It is strongly advised to use certificates issued by reputable CAs to ensure the authenticity and security \ + of your connections. Self-signed certificates are suitable for testing purposes only and should never be \ + used in a production environment where data integrity and confidentiality are paramount." + ); + println!(); + println!( + "Please exercise extreme caution and prioritize the use of valid, properly authenticated TLS certificates to \ + safeguard your applications and data against potential security threats." + ); + println!(); +} diff --git a/applications/minotari_app_grpc/src/tls/error.rs b/applications/minotari_app_grpc/src/tls/error.rs new file mode 100644 index 0000000000..2f843138c6 --- /dev/null +++ b/applications/minotari_app_grpc/src/tls/error.rs @@ -0,0 +1,35 @@ +// Copyright 2023. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io; + +use rcgen::RcgenError; + +#[derive(Debug, thiserror::Error)] +pub enum GrpcTlsError { + #[error("Couldn't read file: {0}")] + FileReadError(String), + #[error("Error generating the certificate: {0}")] + CertGenerationError(#[from] RcgenError), + #[error("Error opening or writing the file: {0}")] + IoError(#[from] io::Error), +} diff --git a/applications/minotari_app_grpc/src/tls/identity.rs b/applications/minotari_app_grpc/src/tls/identity.rs new file mode 100644 index 0000000000..2f0cde5be4 --- /dev/null +++ b/applications/minotari_app_grpc/src/tls/identity.rs @@ -0,0 +1,39 @@ +// Copyright 2023. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::path::PathBuf; + +use tonic::transport::Identity; + +use crate::tls::error::GrpcTlsError; + +pub async fn read_identity(config_dir: PathBuf) -> Result { + let err = |file| move |e| GrpcTlsError::FileReadError(format!("Could not load the file `{:?}`: {}", file, e)); + + let cert_file = config_dir.join("server.pem"); + let cert = tokio::fs::read(&cert_file).await.map_err(err(cert_file))?; + + let key_file = config_dir.join("server.key"); + let key = tokio::fs::read(&key_file).await.map_err(err(key_file))?; + + Ok(Identity::from_pem(cert, key)) +} diff --git a/applications/minotari_app_grpc/src/tls/mod.rs b/applications/minotari_app_grpc/src/tls/mod.rs new file mode 100644 index 0000000000..b8c3dd80e4 --- /dev/null +++ b/applications/minotari_app_grpc/src/tls/mod.rs @@ -0,0 +1,29 @@ +// Copyright 2023. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod certs; +pub mod error; +pub mod identity; + +pub fn protocol_string(tls_enabled: bool) -> String { + format!("http{}://", if tls_enabled { "s" } else { "" }) +} diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 46be63d208..3949863419 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -34,6 +34,7 @@ use chrono::{DateTime, Utc}; use digest::Digest; use futures::FutureExt; use log::*; +use minotari_app_grpc::tls::certs::{generate_self_signed_certs, print_warning, write_cert_to_disk}; use minotari_wallet::{ connectivity_service::WalletConnectivityInterface, output_manager_service::{handle::OutputManagerHandle, UtxoSelectionCriteria}, @@ -978,6 +979,25 @@ pub async fn command_runner( debug!(target: LOG_TARGET, "Registering VN tx_id {}", tx_id); tx_ids.push(tx_id); }, + CreateTlsCerts => match generate_self_signed_certs() { + Ok((cacert, cert, private_key)) => { + print_warning(); + + write_cert_to_disk(config.config_dir.clone(), "wallet_ca.pem", &cacert)?; + write_cert_to_disk(config.config_dir.clone(), "server.pem", &cert)?; + write_cert_to_disk(config.config_dir.clone(), "server.key", &private_key)?; + + println!(); + println!("Certificates generated successfully."); + println!( + "To continue configuration move the `wallet_ca.pem` to the client service's \ + `application/config/` directory. Restart the base node with the configuration \ + grpc_tls_enabled=true" + ); + println!(); + }, + Err(err) => eprintln!("Error generating certificates: {}", err), + }, } } diff --git a/applications/minotari_console_wallet/src/automation/error.rs b/applications/minotari_console_wallet/src/automation/error.rs index f7758944a5..395e59992b 100644 --- a/applications/minotari_console_wallet/src/automation/error.rs +++ b/applications/minotari_console_wallet/src/automation/error.rs @@ -26,6 +26,7 @@ use std::{ }; use log::*; +use minotari_app_grpc::tls::error::GrpcTlsError; use minotari_wallet::{ error::{WalletError, WalletStorageError}, output_manager_service::error::OutputManagerError, @@ -84,6 +85,8 @@ pub enum CommandError { FixedHashSizeError(#[from] FixedHashSizeError), #[error("ByteArrayError {0}")] ByteArrayError(String), + #[error("gRPC TLS cert error {0}")] + GrpcTlsError(#[from] GrpcTlsError), } impl From for CommandError { diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index 562e872ad4..08afa45b92 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -133,6 +133,7 @@ pub enum CliCommands { ClaimShaAtomicSwapRefund(ClaimShaAtomicSwapRefundArgs), RevalidateWalletDb, RegisterValidatorNode(RegisterValidatorNodeArgs), + CreateTlsCerts, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index d3c96af01a..a518eff7f5 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -26,14 +26,14 @@ use std::{fs, io::Stdout, path::PathBuf}; use clap::Parser; use log::*; -use minotari_app_grpc::authentication::ServerAuthenticationInterceptor; +use minotari_app_grpc::{authentication::ServerAuthenticationInterceptor, tls::identity::read_identity}; use minotari_wallet::{WalletConfig, WalletSqlite}; use rand::{rngs::OsRng, seq::SliceRandom}; use tari_common::exit_codes::{ExitCode, ExitError}; use tari_common_types::grpc_authentication::GrpcAuthentication; use tari_comms::{multiaddr::Multiaddr, peer_manager::Peer, utils::multiaddr::multiaddr_to_socketaddr}; use tokio::{runtime::Handle, sync::broadcast}; -use tonic::transport::Server; +use tonic::transport::{Identity, Server, ServerTlsConfig}; use tui::backend::CrosstermBackend; use crate::{ @@ -276,10 +276,24 @@ pub fn tui_mode( exit_code: ExitCode::UnknownError, details: Some(e.to_string()), })?; + + let mut tls_identity = None; + if config.grpc_tls_enabled { + match handle + .block_on(read_identity(config.config_dir.clone())) + .map(Some) + .map_err(|e| ExitError::new(ExitCode::TlsConfigurationError, e.to_string())) + { + Ok(identity) => tls_identity = identity, + Err(e) => return Err(e), + } + } + handle.spawn(run_grpc( grpc, address, config.grpc_authentication.clone(), + tls_identity, wallet.clone(), )); } @@ -391,8 +405,21 @@ pub fn grpc_mode(handle: Handle, config: &WalletConfig, wallet: WalletSqlite) -> details: Some(e.to_string()), })?; let auth = config.grpc_authentication.clone(); + + let mut tls_identity = None; + if config.grpc_tls_enabled { + match handle + .block_on(read_identity(config.config_dir.clone())) + .map(Some) + .map_err(|e| ExitError::new(ExitCode::TlsConfigurationError, e.to_string())) + { + Ok(identity) => tls_identity = identity, + Err(e) => return Err(e), + } + } + handle - .block_on(run_grpc(grpc, address, auth, wallet)) + .block_on(run_grpc(grpc, address, auth, tls_identity, wallet)) .map_err(|e| ExitError::new(ExitCode::GrpcError, e))?; } #[cfg(not(feature = "grpc"))] @@ -411,6 +438,7 @@ async fn run_grpc( grpc: WalletGrpcServer, grpc_listener_addr: Multiaddr, auth_config: GrpcAuthentication, + tls_identity: Option, wallet: WalletSqlite, ) -> Result<(), String> { // Do not remove this println! @@ -423,7 +451,15 @@ async fn run_grpc( .ok_or("Unable to prepare server gRPC authentication".to_string())?; let service = minotari_app_grpc::tari_rpc::wallet_server::WalletServer::with_interceptor(grpc, auth); - Server::builder() + let mut server_builder = if let Some(identity) = tls_identity { + Server::builder() + .tls_config(ServerTlsConfig::new().identity(identity)) + .map_err(|e| e.to_string())? + } else { + Server::builder() + }; + + server_builder .add_service(service) .serve_with_shutdown(address, wallet.wait_until_shutdown()) .await @@ -498,6 +534,7 @@ mod test { CliCommands::ClaimShaAtomicSwapRefund(_) => {}, CliCommands::RevalidateWalletDb => {}, CliCommands::RegisterValidatorNode(_) => {}, + CliCommands::CreateTlsCerts => {}, } } assert!(get_balance && send_tari && burn_tari && make_it_rain && coin_split && discover_peer && whois); diff --git a/applications/minotari_merge_mining_proxy/src/config.rs b/applications/minotari_merge_mining_proxy/src/config.rs index 3e9f4e8d9b..d9418a5554 100644 --- a/applications/minotari_merge_mining_proxy/src/config.rs +++ b/applications/minotari_merge_mining_proxy/src/config.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::path::{Path, PathBuf}; + use minotari_wallet_grpc_client::GrpcAuthentication; use serde::{Deserialize, Serialize}; use tari_common::{ @@ -47,6 +49,10 @@ pub struct MergeMiningProxyConfig { pub base_node_grpc_address: Option, /// GRPC authentication for base node pub base_node_grpc_authentication: GrpcAuthentication, + /// GRPC domain name for node TLS validation + pub base_node_grpc_tls_domain_name: Option, + /// GRPC ca cert name for TLS + pub base_node_grpc_ca_cert_filename: String, /// Address of the minotari_merge_mining_proxy application pub listener_address: Multiaddr, /// In sole merged mining, the block solution is usually submitted to the Monero blockchain (monerod) as well as to @@ -69,6 +75,8 @@ pub struct MergeMiningProxyConfig { pub coinbase_extra: String, /// Selected network pub network: Network, + /// The relative path to store persistent config + pub config_dir: PathBuf, /// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned pub wallet_payment_address: String, /// Stealth payment yes or no @@ -87,6 +95,8 @@ impl Default for MergeMiningProxyConfig { monerod_use_auth: false, base_node_grpc_address: None, base_node_grpc_authentication: GrpcAuthentication::default(), + base_node_grpc_tls_domain_name: None, + base_node_grpc_ca_cert_filename: "node_ca.pem".to_string(), listener_address: "/ip4/127.0.0.1/tcp/18081".parse().unwrap(), submit_to_origin: true, wait_for_initial_sync_at_startup: true, @@ -94,6 +104,7 @@ impl Default for MergeMiningProxyConfig { max_randomx_vms: 5, coinbase_extra: "tari_merge_mining_proxy".to_string(), network: Default::default(), + config_dir: PathBuf::from("config/merge_mining_proxy"), wallet_payment_address: TariAddress::default().to_hex(), stealth_payment: true, range_proof_type: RangeProofType::RevealedValue, @@ -101,6 +112,14 @@ impl Default for MergeMiningProxyConfig { } } +impl MergeMiningProxyConfig { + pub fn set_base_path>(&mut self, base_path: P) { + if !self.config_dir.is_absolute() { + self.config_dir = base_path.as_ref().join(self.config_dir.as_path()); + } + } +} + impl SubConfigPath for MergeMiningProxyConfig { fn main_key_prefix() -> &'static str { "merge_mining_proxy" diff --git a/applications/minotari_merge_mining_proxy/src/error.rs b/applications/minotari_merge_mining_proxy/src/error.rs index 04d585aea6..8d08675344 100644 --- a/applications/minotari_merge_mining_proxy/src/error.rs +++ b/applications/minotari_merge_mining_proxy/src/error.rs @@ -98,6 +98,8 @@ pub enum MmProxyError { ServersUnavailable, #[error("Invalid difficulty: {0}")] DifficultyError(#[from] DifficultyError), + #[error("TLS connection error: {0}")] + TlsConnectionError(String), #[error("Key manager service error: `{0}`")] KeyManagerServiceError(String), #[error("Key manager error: {0}")] diff --git a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs index 2966bce7d9..95a4736a55 100644 --- a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs +++ b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs @@ -25,6 +25,7 @@ use std::{convert::Infallible, str::FromStr}; use futures::future; use hyper::{service::make_service_fn, Server}; use log::*; +use minotari_app_grpc::tls::protocol_string; use minotari_node_grpc_client::grpc::base_node_client::BaseNodeClient; use minotari_wallet_grpc_client::ClientAuthenticationInterceptor; use tari_common::{ @@ -38,7 +39,7 @@ use tari_core::proof_of_work::randomx_factory::RandomXFactory; use tokio::time::Duration; use tonic::{ codegen::InterceptedService, - transport::{Channel, Endpoint}, + transport::{Certificate, Channel, ClientTlsConfig, Endpoint}, }; use crate::{ @@ -54,6 +55,7 @@ pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { let config_path = cli.common.config_path(); let cfg = load_configuration(&config_path, true, &cli)?; let mut config = MergeMiningProxyConfig::load_from(&cfg)?; + config.set_base_path(cli.common.get_base_path()); setup_grpc_config(&mut config); let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address) @@ -114,16 +116,35 @@ async fn connect_base_node( config: &MergeMiningProxyConfig, ) -> Result>, MmProxyError> { let base_node_addr = format!( - "http://{}", + "{}{}", + protocol_string(config.base_node_grpc_tls_domain_name.is_some()), multiaddr_to_socketaddr( &config .base_node_grpc_address .clone() .expect("Base node grpc address not found") - )? + )?, ); + info!(target: LOG_TARGET, "👛 Connecting to base node at {}", base_node_addr); - let channel = Endpoint::from_str(&base_node_addr)?.connect().await?; + let mut endpoint = Endpoint::from_str(&base_node_addr)?; + + if let Some(domain_name) = config.base_node_grpc_tls_domain_name.as_ref() { + let pem = tokio::fs::read(config.config_dir.join(&config.base_node_grpc_ca_cert_filename)) + .await + .map_err(|e| MmProxyError::TlsConnectionError(e.to_string()))?; + let ca = Certificate::from_pem(pem); + + let tls = ClientTlsConfig::new().ca_certificate(ca).domain_name(domain_name); + endpoint = endpoint + .tls_config(tls) + .map_err(|e| MmProxyError::TlsConnectionError(e.to_string()))?; + } + + let channel = endpoint + .connect() + .await + .map_err(|e| MmProxyError::TlsConnectionError(e.to_string()))?; let node_conn = BaseNodeClient::with_interceptor( channel, ClientAuthenticationInterceptor::create(&config.base_node_grpc_authentication)?, diff --git a/applications/minotari_miner/Cargo.toml b/applications/minotari_miner/Cargo.toml index 09b0380dfc..d12aeb2de4 100644 --- a/applications/minotari_miner/Cargo.toml +++ b/applications/minotari_miner/Cargo.toml @@ -17,26 +17,26 @@ minotari_app_grpc = { path = "../minotari_app_grpc" } tari_crypto = { version = "0.19" } tari_utilities = { version = "0.6" } +base64 = "0.13.0" borsh = "0.10" -crossterm = { version = "0.25.0" } +bufstream = "0.1" +chrono = { version = "0.4.19", default-features = false } clap = { version = "3.2", features = ["derive"] } crossbeam = "0.8" +crossterm = { version = "0.25.0" } +derivative = "2.2.0" futures = "0.3" +hex = "0.4.2" log = { version = "0.4", features = ["std"] } log4rs = { git = "https://github.com/tari-project/log4rs.git", default_features = false, features = ["config_parsing", "threshold_filter", "yaml_format", "console_appender", "rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } +native-tls = "0.2" num_cpus = "1.13" rand = "0.8" serde = { version = "1.0", default_features = false, features = ["derive"] } -tonic = { version = "0.6.2", features = ["transport"] } -tokio = { version = "1.23", default_features = false, features = ["rt-multi-thread"] } -thiserror = "1.0" serde_json = "1.0.57" -native-tls = "0.2" -bufstream = "0.1" -chrono = { version = "0.4.19", default-features = false } -hex = "0.4.2" -derivative = "2.2.0" -base64 = "0.13.0" +thiserror = "1.0" +tokio = { version = "1.23", default_features = false, features = ["rt-multi-thread"] } +tonic = { version = "0.6.2", features = ["tls", "tls-roots" ] } [dev-dependencies] prost-types = "0.9" diff --git a/applications/minotari_miner/src/config.rs b/applications/minotari_miner/src/config.rs index 0ecd018680..2c0412ae63 100644 --- a/applications/minotari_miner/src/config.rs +++ b/applications/minotari_miner/src/config.rs @@ -34,7 +34,10 @@ //! All miner options configured under `[miner]` section of //! Minotari's `config.toml`. -use std::time::Duration; +use std::{ + path::{Path, PathBuf}, + time::Duration, +}; use minotari_app_grpc::tari_rpc::{pow_algo::PowAlgos, NewBlockTemplateRequest, PowAlgo}; use serde::{Deserialize, Serialize}; @@ -50,6 +53,10 @@ pub struct MinerConfig { pub base_node_grpc_address: Option, /// GRPC authentication for base node pub base_node_grpc_authentication: GrpcAuthentication, + /// GRPC domain name for node TLS validation + pub base_node_grpc_tls_domain_name: Option, + /// GRPC ca cert name for TLS + pub base_node_grpc_ca_cert_filename: String, /// Number of mining threads pub num_mining_threads: usize, /// Start mining only when base node is bootstrapped and current block height is on the tip of network @@ -74,6 +81,8 @@ pub struct MinerConfig { pub network: Network, /// Base node reconnect timeout after any gRPC or miner error pub wait_timeout_on_error: u64, + /// The relative path to store persistent config + pub config_dir: PathBuf, /// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned pub wallet_payment_address: String, /// Stealth payment yes or no @@ -101,6 +110,8 @@ impl Default for MinerConfig { Self { base_node_grpc_address: None, base_node_grpc_authentication: GrpcAuthentication::default(), + base_node_grpc_tls_domain_name: None, + base_node_grpc_ca_cert_filename: "node_ca.pem".to_string(), num_mining_threads: num_cpus::get(), mine_on_tip_only: true, proof_of_work_algo: ProofOfWork::Sha3x, @@ -111,6 +122,7 @@ impl Default for MinerConfig { coinbase_extra: "minotari_miner".to_string(), network: Default::default(), wait_timeout_on_error: 10, + config_dir: PathBuf::from("config/miner"), wallet_payment_address: TariAddress::default().to_hex(), stealth_payment: true, range_proof_type: RangeProofType::RevealedValue, @@ -135,6 +147,12 @@ impl MinerConfig { pub fn validate_tip_interval(&self) -> Duration { Duration::from_secs(self.validate_tip_timeout_sec) } + + pub fn set_base_path>(&mut self, base_path: P) { + if !self.config_dir.is_absolute() { + self.config_dir = base_path.as_ref().join(self.config_dir.as_path()); + } + } } #[cfg(test)] diff --git a/applications/minotari_miner/src/errors.rs b/applications/minotari_miner/src/errors.rs index e78749ca2e..ecee01405e 100644 --- a/applications/minotari_miner/src/errors.rs +++ b/applications/minotari_miner/src/errors.rs @@ -49,6 +49,8 @@ pub enum MinerError { BasicAuthError(#[from] BasicAuthError), #[error("Invalid gRPC url: {0}")] InvalidUri(#[from] InvalidUri), + #[error("TLS connection error: {0}")] + TlsConnectionError(String), #[error("Coinbase error: {0}")] CoinbaseError(String), #[error("Consensus build error: {0}")] diff --git a/applications/minotari_miner/src/run_miner.rs b/applications/minotari_miner/src/run_miner.rs index f75de63c39..983e4f88d6 100644 --- a/applications/minotari_miner/src/run_miner.rs +++ b/applications/minotari_miner/src/run_miner.rs @@ -27,6 +27,7 @@ use log::*; use minotari_app_grpc::{ authentication::ClientAuthenticationInterceptor, tari_rpc::{base_node_client::BaseNodeClient, TransactionOutput as GrpcTransactionOutput}, + tls::protocol_string, }; use tari_common::{ configuration::bootstrap::{grpc_default_port, ApplicationType}, @@ -50,7 +51,7 @@ use tari_utilities::hex::Hex; use tokio::time::sleep; use tonic::{ codegen::InterceptedService, - transport::{Channel, Endpoint}, + transport::{Certificate, Channel, ClientTlsConfig, Endpoint}, }; use crate::{ @@ -71,6 +72,7 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { let config_path = cli.common.config_path(); let cfg = load_configuration(config_path.as_path(), true, &cli)?; let mut config = MinerConfig::load_from(&cfg).expect("Failed to load config"); + config.set_base_path(cli.common.get_base_path()); debug!(target: LOG_TARGET_FILE, "{:?}", config); setup_grpc_config(&mut config); let key_manager = create_memory_db_key_manager(); @@ -224,16 +226,35 @@ async fn connect(config: &MinerConfig) -> Result async fn connect_base_node(config: &MinerConfig) -> Result { let base_node_addr = format!( - "http://{}", + "{}{}", + protocol_string(config.base_node_grpc_tls_domain_name.is_some()), multiaddr_to_socketaddr( &config .base_node_grpc_address .clone() .expect("Base node grpc address not found") - )? + )?, ); + info!(target: LOG_TARGET, "👛 Connecting to base node at {}", base_node_addr); - let channel = Endpoint::from_str(&base_node_addr)?.connect().await?; + let mut endpoint = Endpoint::from_str(&base_node_addr)?; + + if let Some(domain_name) = config.base_node_grpc_tls_domain_name.as_ref() { + let pem = tokio::fs::read(config.config_dir.join(&config.base_node_grpc_ca_cert_filename)) + .await + .map_err(|e| MinerError::TlsConnectionError(e.to_string()))?; + let ca = Certificate::from_pem(pem); + + let tls = ClientTlsConfig::new().ca_certificate(ca).domain_name(domain_name); + endpoint = endpoint + .tls_config(tls) + .map_err(|e| MinerError::TlsConnectionError(e.to_string()))?; + } + + let channel = endpoint + .connect() + .await + .map_err(|e| MinerError::TlsConnectionError(e.to_string()))?; let node_conn = BaseNodeClient::with_interceptor( channel, ClientAuthenticationInterceptor::create(&config.base_node_grpc_authentication)?, diff --git a/applications/minotari_node/Cargo.toml b/applications/minotari_node/Cargo.toml index 9df9aaec7b..de24bc4bc2 100644 --- a/applications/minotari_node/Cargo.toml +++ b/applications/minotari_node/Cargo.toml @@ -46,7 +46,7 @@ serde = "1.0.136" strum = { version = "0.22", features = ["derive"] } thiserror = "^1.0.26" tokio = { version = "1.23", features = ["signal"] } -tonic = "0.6.2" +tonic = { version = "0.6.2", features = ["tls", "tls-roots" ] } # Metrics tari_metrics = { path = "../../infrastructure/metrics", optional = true, features = ["server"] } diff --git a/applications/minotari_node/src/commands/command/create_tls_certs.rs b/applications/minotari_node/src/commands/command/create_tls_certs.rs new file mode 100644 index 0000000000..cac1f7f454 --- /dev/null +++ b/applications/minotari_node/src/commands/command/create_tls_certs.rs @@ -0,0 +1,63 @@ +// Copyright 2023, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use anyhow::Error; +use async_trait::async_trait; +use clap::Parser; +use minotari_app_grpc::tls::certs::{generate_self_signed_certs, print_warning, write_cert_to_disk}; + +use super::{CommandContext, HandleCommand}; + +/// Create self signed TLS certificates for use with gRPC +#[derive(Debug, Parser)] +pub struct Args {} + +#[async_trait] +impl HandleCommand for CommandContext { + async fn handle_command(&mut self, _: Args) -> Result<(), Error> { + self.create_tls_certs() + } +} + +impl CommandContext { + pub fn create_tls_certs(&self) -> Result<(), Error> { + match generate_self_signed_certs() { + Ok((cacert, cert, private_key)) => { + print_warning(); + + write_cert_to_disk(self.config.base_node.config_dir.clone(), "node_ca.pem", &cacert)?; + write_cert_to_disk(self.config.base_node.config_dir.clone(), "server.pem", &cert)?; + write_cert_to_disk(self.config.base_node.config_dir.clone(), "server.key", &private_key)?; + + println!(); + println!("Certificates generated successfully."); + println!( + "To continue configuration move the `node_ca.pem` to the client service's `application/config/` \ + directory. Restart the base node with the configuration grpc_tls_enabled=true" + ); + println!(); + }, + Err(err) => eprintln!("Error generating certificates: {}", err), + } + Ok(()) + } +} diff --git a/applications/minotari_node/src/commands/command/mod.rs b/applications/minotari_node/src/commands/command/mod.rs index 4a02c670d0..14184a036b 100644 --- a/applications/minotari_node/src/commands/command/mod.rs +++ b/applications/minotari_node/src/commands/command/mod.rs @@ -25,6 +25,7 @@ mod ban_peer; mod block_timing; mod check_db; mod check_for_updates; +mod create_tls_certs; mod dial_peer; mod discover_peer; mod get_block; @@ -133,6 +134,7 @@ pub enum Command { GetStateInfo(get_state_info::Args), GetNetworkStats(get_network_stats::Args), ListValidatorNodes(list_validator_nodes::Args), + CreateTlsCerts(create_tls_certs::Args), Quit(quit::Args), Exit(quit::Args), Watch(watch_command::Args), @@ -228,6 +230,7 @@ impl CommandContext { Command::Status(_) | Command::Watch(_) | Command::ListValidatorNodes(_) | + Command::CreateTlsCerts(_) | Command::Quit(_) | Command::Exit(_) => 30, // These commands involve intense blockchain db operations and needs a lot of time to complete @@ -293,6 +296,7 @@ impl HandleCommand for CommandContext { Command::Quit(args) | Command::Exit(args) => self.handle_command(args).await, Command::Watch(args) => self.handle_command(args).await, Command::ListValidatorNodes(args) => self.handle_command(args).await, + Command::CreateTlsCerts(args) => self.handle_command(args).await, } } } diff --git a/applications/minotari_node/src/config.rs b/applications/minotari_node/src/config.rs index 091f0814f2..309307a52d 100644 --- a/applications/minotari_node/src/config.rs +++ b/applications/minotari_node/src/config.rs @@ -92,6 +92,8 @@ pub struct BaseNodeConfig { pub grpc_server_deny_methods: Vec, /// GRPC authentication mode pub grpc_authentication: GrpcAuthentication, + /// GRPC tls enabled + pub grpc_tls_enabled: bool, /// A path to the file that stores the base node identity and secret key pub identity_file: PathBuf, /// Spin up and use a built-in Tor instance. This only works on macos/linux - requires that the wallet was built @@ -105,6 +107,8 @@ pub struct BaseNodeConfig { pub lmdb: LMDBConfig, /// The relative path to store persistent data pub data_dir: PathBuf, + /// The relative path to the config directory + pub config_dir: PathBuf, /// The relative path to store the lmbd data pub lmdb_path: PathBuf, /// The maximum amount of VMs that RandomX will be use @@ -159,6 +163,7 @@ impl Default for BaseNodeConfig { GrpcMethod::GetNetworkStatus, ], grpc_authentication: GrpcAuthentication::default(), + grpc_tls_enabled: false, identity_file: PathBuf::from("config/base_node_id.json"), use_libtor: false, tor_identity_file: PathBuf::from("config/base_node_tor_id.json"), @@ -166,6 +171,7 @@ impl Default for BaseNodeConfig { db_type: DatabaseType::Lmdb, lmdb: Default::default(), data_dir: PathBuf::from("data/base_node"), + config_dir: PathBuf::from("config/base_node"), lmdb_path: PathBuf::from("db"), max_randomx_vms: 5, bypass_range_proof_verification: false, @@ -199,6 +205,9 @@ impl BaseNodeConfig { if !self.data_dir.is_absolute() { self.data_dir = base_path.as_ref().join(self.data_dir.as_path()); } + if !self.config_dir.is_absolute() { + self.config_dir = base_path.as_ref().join(self.config_dir.as_path()); + } if !self.lmdb_path.is_absolute() { self.lmdb_path = self.data_dir.join(self.lmdb_path.as_path()); } diff --git a/applications/minotari_node/src/lib.rs b/applications/minotari_node/src/lib.rs index 82702c4dca..572f722342 100644 --- a/applications/minotari_node/src/lib.rs +++ b/applications/minotari_node/src/lib.rs @@ -42,7 +42,7 @@ use std::{process, sync::Arc}; use commands::{cli_loop::CliLoop, command::CommandContext}; use futures::FutureExt; use log::*; -use minotari_app_grpc::authentication::ServerAuthenticationInterceptor; +use minotari_app_grpc::{authentication::ServerAuthenticationInterceptor, tls::identity::read_identity}; use minotari_app_utilities::{common_cli_args::CommonCliArgs, network_check::is_network_choice_valid}; use tari_common::{ configuration::bootstrap::{grpc_default_port, ApplicationType}, @@ -52,7 +52,7 @@ use tari_common_types::grpc_authentication::GrpcAuthentication; use tari_comms::{multiaddr::Multiaddr, utils::multiaddr::multiaddr_to_socketaddr, NodeIdentity}; use tari_shutdown::{Shutdown, ShutdownSignal}; use tokio::task; -use tonic::transport::Server; +use tonic::transport::{Identity, Server, ServerTlsConfig}; use crate::cli::Cli; pub use crate::{ @@ -141,7 +141,15 @@ pub async fn run_base_node_with_cli( config.base_node.grpc_server_deny_methods.clone(), ); let auth = config.base_node.grpc_authentication.clone(); - task::spawn(run_grpc(grpc, grpc_address, auth, shutdown.to_signal())); + + let mut tls_identity = None; + if config.base_node.grpc_tls_enabled { + tls_identity = read_identity(config.base_node.config_dir.clone()) + .await + .map(Some) + .map_err(|e| ExitError::new(ExitCode::TlsConfigurationError, e.to_string()))?; + } + task::spawn(run_grpc(grpc, grpc_address, auth, tls_identity, shutdown.to_signal())); } // Run, node, run! @@ -176,6 +184,7 @@ async fn run_grpc( grpc: grpc::base_node_grpc_server::BaseNodeGrpcServer, grpc_address: Multiaddr, auth_config: GrpcAuthentication, + tls_identity: Option, interrupt_signal: ShutdownSignal, ) -> Result<(), anyhow::Error> { info!(target: LOG_TARGET, "Starting GRPC on {}", grpc_address); @@ -185,7 +194,13 @@ async fn run_grpc( .ok_or(anyhow::anyhow!("Unable to prepare server gRPC authentication"))?; let service = minotari_app_grpc::tari_rpc::base_node_server::BaseNodeServer::with_interceptor(grpc, auth); - Server::builder() + let mut server_builder = if let Some(identity) = tls_identity { + Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? + } else { + Server::builder() + }; + + server_builder .add_service(service) .serve_with_shutdown(grpc_address, interrupt_signal.map(|_| ())) .await diff --git a/base_layer/wallet/src/config.rs b/base_layer/wallet/src/config.rs index 7c385ac25c..8bbb5b8b50 100644 --- a/base_layer/wallet/src/config.rs +++ b/base_layer/wallet/src/config.rs @@ -72,6 +72,8 @@ pub struct WalletConfig { pub base_node_service_config: BaseNodeServiceConfig, /// The relative path to store persistent data pub data_dir: PathBuf, + /// The relative path to the config directory + pub config_dir: PathBuf, /// The main wallet db file pub db_file: PathBuf, /// The main wallet db sqlite database backend connection pool size for concurrent reads @@ -99,6 +101,8 @@ pub struct WalletConfig { pub grpc_address: Option, /// GRPC authentication mode pub grpc_authentication: GrpcAuthentication, + /// GRPC tls enabled + pub grpc_tls_enabled: bool, /// A custom base node peer that will be used to obtain metadata from pub custom_base_node: Option, /// A list of base node peers that the wallet should use for service requests and tracking chain state @@ -132,6 +136,7 @@ impl Default for WalletConfig { network: Default::default(), base_node_service_config: Default::default(), data_dir: PathBuf::from_str("data/wallet").unwrap(), + config_dir: PathBuf::from_str("config/wallet").unwrap(), db_file: PathBuf::from_str("db/console_wallet.db").unwrap(), db_connection_pool_size: 16, // Note: Do not reduce this default number password: None, @@ -143,6 +148,7 @@ impl Default for WalletConfig { grpc_enabled: false, grpc_address: None, grpc_authentication: GrpcAuthentication::default(), + grpc_tls_enabled: false, custom_base_node: None, base_node_service_peers: StringList::default(), recovery_retry_limit: 3, @@ -165,6 +171,9 @@ impl WalletConfig { if !self.data_dir.is_absolute() { self.data_dir = base_path.as_ref().join(self.data_dir.as_path()); } + if !self.config_dir.is_absolute() { + self.config_dir = base_path.as_ref().join(self.config_dir.as_path()); + } if !self.db_file.is_absolute() { self.db_file = self.data_dir.join(self.db_file.as_path()); } diff --git a/common/config/presets/c_base_node.toml b/common/config/presets/c_base_node.toml index 8e536cfda9..aefb01b5c8 100644 --- a/common/config/presets/c_base_node.toml +++ b/common/config/presets/c_base_node.toml @@ -39,6 +39,9 @@ identity_file = "config/base_node_id_nextnet.json" # gRPC authentication method (default = "none") #grpc_authentication = { username = "admin", password = "xxxx" } +# Use gRPC over TLS (default = false) +#grpc_tls_enabled = false + # Uncomment all gRPC server methods that should be denied default (only active when `grpc_enabled = true`) grpc_server_deny_methods = [ "get_version", diff --git a/common/config/presets/g_miner.toml b/common/config/presets/g_miner.toml index bc42c32bf4..35b58500f9 100644 --- a/common/config/presets/g_miner.toml +++ b/common/config/presets/g_miner.toml @@ -11,6 +11,8 @@ #base_node_grpc_address = "/ip4/127.0.0.1/tcp/18142" # GRPC authentication for the base node (default = "none") #base_node_grpc_authentication = { username = "miner", password = "xxxx" } +# GRPC TLS communication is turned on by defining the domain name for the service (default = "none") +#base_node_grpc_tls_domain_name = "localhost" # Number of mining threads (default: number of logical CPU cores) #num_mining_threads = 8 diff --git a/common/src/exit_codes.rs b/common/src/exit_codes.rs index 6d90d49d5b..412afe54e2 100644 --- a/common/src/exit_codes.rs +++ b/common/src/exit_codes.rs @@ -128,6 +128,8 @@ pub enum ExitCode { ConsensusManagerBuilderError = 122, #[error("Payment wallet address error")] WalletPaymentAddress = 123, + #[error("Unable to configure TLS")] + TlsConfigurationError = 124, } impl From for ExitError {