From 40e7f44a2a6d1e1a950db75f8f772d47660827ed Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 24 Aug 2022 19:37:15 +0400 Subject: [PATCH] feat(miner): add client wallet grpc authentication interceptor --- Cargo.lock | 1 + .../src/authentication/basic_auth.rs | 35 ++++++++-- .../src/authentication/client_interceptor.rs | 65 +++++++++++++++++++ .../tari_app_grpc/src/authentication/mod.rs | 7 +- .../{interceptor.rs => server_interceptor.rs} | 8 +-- .../tari_console_wallet/src/wallet_modes.rs | 4 +- applications/tari_miner/Cargo.toml | 13 ++-- applications/tari_miner/src/config.rs | 4 ++ applications/tari_miner/src/errors.rs | 6 ++ applications/tari_miner/src/main.rs | 33 ++++++++-- common/config/presets/g_miner.toml | 2 + 11 files changed, 153 insertions(+), 25 deletions(-) create mode 100644 applications/tari_app_grpc/src/authentication/client_interceptor.rs rename applications/tari_app_grpc/src/authentication/{interceptor.rs => server_interceptor.rs} (94%) diff --git a/Cargo.lock b/Cargo.lock index 722bcb9a680..38e66f09d38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5205,6 +5205,7 @@ dependencies = [ "tari_app_grpc", "tari_app_utilities", "tari_common", + "tari_common_types", "tari_comms", "tari_core", "tari_crypto", diff --git a/applications/tari_app_grpc/src/authentication/basic_auth.rs b/applications/tari_app_grpc/src/authentication/basic_auth.rs index bff07ccf5f5..4d806b79f58 100644 --- a/applications/tari_app_grpc/src/authentication/basic_auth.rs +++ b/applications/tari_app_grpc/src/authentication/basic_auth.rs @@ -20,11 +20,11 @@ // 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::string::FromUtf8Error; +use std::{borrow::Cow, string::FromUtf8Error}; use argon2::{password_hash::Encoding, Argon2, PasswordHash, PasswordVerifier}; use tari_utilities::SafePassword; -use zeroize::Zeroizing; +use zeroize::{Zeroize, Zeroizing}; /// Implements [RFC 2617](https://www.ietf.org/rfc/rfc2617.txt#:~:text=The%20%22basic%22%20authentication%20scheme%20is,other%20realms%20on%20that%20server.) /// Represents the username and password contained within a Authenticate header. @@ -35,7 +35,7 @@ pub struct BasicAuthCredentials { } impl BasicAuthCredentials { - fn new(user_name: String, password: SafePassword) -> Self { + pub fn new(user_name: String, password: SafePassword) -> Self { Self { user_name, password } } @@ -91,6 +91,19 @@ impl BasicAuthCredentials { .map_err(|_| BasicAuthError::InvalidCredentials)?; Ok(()) } + + pub fn generate_header(username: &str, password: &[u8]) -> Result { + let password_str = String::from_utf8_lossy(password); + let token_str = Zeroizing::new(format!("{}:{}", username, password_str)); + let mut token = base64::encode(token_str); + let header = format!("Basic {}", token); + token.zeroize(); + match password_str { + Cow::Borrowed(_) => {}, + Cow::Owned(mut owned) => owned.zeroize(), + } + Ok(header) + } } /// Authorization Header Error @@ -133,10 +146,12 @@ mod tests { mod validate { use super::*; + use crate::authentication::salted_password::create_salted_hashed_password; #[test] fn it_validates_for_matching_credentials() { - let credentials = BasicAuthCredentials::new("admin".to_string(), "secret".to_string().into()); + let hashed = create_salted_hashed_password(b"secret").unwrap(); + let credentials = BasicAuthCredentials::new("admin".to_string(), hashed.to_string().into()); credentials.validate("admin", b"secret").unwrap(); } @@ -151,4 +166,16 @@ mod tests { assert_eq!(err, BasicAuthError::InvalidCredentials); } } + + mod generate_header { + use super::*; + + #[test] + fn it_generates_a_valid_header() { + let header = BasicAuthCredentials::generate_header("admin", b"secret").unwrap(); + let cred = BasicAuthCredentials::from_header(&header).unwrap(); + assert_eq!(cred.user_name, "admin"); + assert_eq!(cred.password.reveal(), &b"secret"[..]); + } + } } diff --git a/applications/tari_app_grpc/src/authentication/client_interceptor.rs b/applications/tari_app_grpc/src/authentication/client_interceptor.rs new file mode 100644 index 00000000000..47c75a91566 --- /dev/null +++ b/applications/tari_app_grpc/src/authentication/client_interceptor.rs @@ -0,0 +1,65 @@ +// Copyright 2022. 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 tari_common_types::grpc_authentication::GrpcAuthentication; +use tonic::{ + codegen::http::header::AUTHORIZATION, + metadata::{Ascii, MetadataValue}, + service::Interceptor, + Request, + Status, +}; + +use crate::authentication::{BasicAuthCredentials, BasicAuthError}; + +#[derive(Debug, Clone)] +pub struct ClientAuthenticationInterceptor { + authorization_header: Option>, +} + +impl ClientAuthenticationInterceptor { + pub fn create(auth: &GrpcAuthentication) -> Result { + let authorization_header = match auth { + GrpcAuthentication::None => None, + GrpcAuthentication::Basic { username, password } => Some( + BasicAuthCredentials::generate_header(username, password.reveal())? + .parse() + .unwrap(), + ), + }; + Ok(Self { authorization_header }) + } +} + +impl Interceptor for ClientAuthenticationInterceptor { + fn call(&mut self, mut request: Request<()>) -> Result, Status> { + match self.authorization_header.clone() { + Some(authorization_header) => { + request + .metadata_mut() + .insert(AUTHORIZATION.as_str(), authorization_header); + Ok(request) + }, + None => Ok(request), + } + } +} diff --git a/applications/tari_app_grpc/src/authentication/mod.rs b/applications/tari_app_grpc/src/authentication/mod.rs index 1bb5ebb2b29..cc5715afd08 100644 --- a/applications/tari_app_grpc/src/authentication/mod.rs +++ b/applications/tari_app_grpc/src/authentication/mod.rs @@ -20,10 +20,13 @@ // 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. -mod interceptor; -pub use interceptor::AuthenticationInterceptor; +mod client_interceptor; +pub use client_interceptor::ClientAuthenticationInterceptor; mod basic_auth; pub use basic_auth::{BasicAuthCredentials, BasicAuthError}; pub mod salted_password; + +mod server_interceptor; +pub use server_interceptor::ServerAuthenticationInterceptor; diff --git a/applications/tari_app_grpc/src/authentication/interceptor.rs b/applications/tari_app_grpc/src/authentication/server_interceptor.rs similarity index 94% rename from applications/tari_app_grpc/src/authentication/interceptor.rs rename to applications/tari_app_grpc/src/authentication/server_interceptor.rs index f874a966ddf..f38a6d31385 100644 --- a/applications/tari_app_grpc/src/authentication/interceptor.rs +++ b/applications/tari_app_grpc/src/authentication/server_interceptor.rs @@ -29,11 +29,11 @@ use crate::authentication::BasicAuthCredentials; const LOG_TARGET: &str = "applications::tari_app_grpc::authentication"; #[derive(Debug)] -pub struct AuthenticationInterceptor { +pub struct ServerAuthenticationInterceptor { auth: GrpcAuthentication, } -impl AuthenticationInterceptor { +impl ServerAuthenticationInterceptor { pub fn new(auth: GrpcAuthentication) -> Self { Self { auth } } @@ -58,7 +58,7 @@ impl AuthenticationInterceptor { } } -impl Interceptor for AuthenticationInterceptor { +impl Interceptor for ServerAuthenticationInterceptor { fn call(&mut self, request: Request<()>) -> Result, Status> { match &self.auth { GrpcAuthentication::None => Ok(request), @@ -70,7 +70,7 @@ impl Interceptor for AuthenticationInterceptor { } } -impl Clone for AuthenticationInterceptor { +impl Clone for ServerAuthenticationInterceptor { fn clone(&self) -> Self { Self { auth: self.auth.clone(), diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 1e6b5aea4b1..4c21e48359f 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -25,7 +25,7 @@ use std::{fs, io::Stdout, path::PathBuf}; use clap::Parser; use log::*; use rand::{rngs::OsRng, seq::SliceRandom}; -use tari_app_grpc::authentication::AuthenticationInterceptor; +use tari_app_grpc::authentication::ServerAuthenticationInterceptor; 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}; @@ -386,7 +386,7 @@ async fn run_grpc( info!(target: LOG_TARGET, "Starting GRPC on {}", grpc_listener_addr); let address = multiaddr_to_socketaddr(&grpc_listener_addr).map_err(|e| e.to_string())?; - let auth = AuthenticationInterceptor::new(auth_config); + let auth = ServerAuthenticationInterceptor::new(auth_config); let service = tari_app_grpc::tari_rpc::wallet_server::WalletServer::with_interceptor(grpc, auth); Server::builder() diff --git a/applications/tari_miner/Cargo.toml b/applications/tari_miner/Cargo.toml index 1db7135b6b5..1958d4080c3 100644 --- a/applications/tari_miner/Cargo.toml +++ b/applications/tari_miner/Cargo.toml @@ -8,11 +8,12 @@ version = "0.36.0" edition = "2018" [dependencies] -tari_core = { path = "../../base_layer/core", default-features = false } -tari_common = { path = "../../common" } -tari_comms = { path = "../../comms/core" } -tari_app_utilities = { path = "../tari_app_utilities"} -tari_app_grpc = { path = "../tari_app_grpc" } +tari_core = { path = "../../base_layer/core", default-features = false } +tari_common = { path = "../../common" } +tari_common_types = { path = "../../base_layer/common_types" } +tari_comms = { path = "../../comms/core" } +tari_app_utilities = { path = "../tari_app_utilities" } +tari_app_grpc = { path = "../tari_app_grpc" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.4" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.5" } @@ -29,7 +30,7 @@ serde = { version = "1.0", default_features = false, features = ["derive"] } tonic = { version = "0.6.2", features = ["transport"] } tokio = { version = "1.11", default_features = false, features = ["rt-multi-thread"] } thiserror = "1.0" -reqwest = { version = "0.11", features = [ "json"] } +reqwest = { version = "0.11", features = ["json"] } serde_json = "1.0.57" native-tls = "0.2" bufstream = "0.1" diff --git a/applications/tari_miner/src/config.rs b/applications/tari_miner/src/config.rs index b28d9f7c3f8..a6f70099bf9 100644 --- a/applications/tari_miner/src/config.rs +++ b/applications/tari_miner/src/config.rs @@ -41,6 +41,7 @@ use std::{str::FromStr, time::Duration}; use serde::{Deserialize, Serialize}; use tari_app_grpc::tari_rpc::{pow_algo::PowAlgos, NewBlockTemplateRequest, PowAlgo}; use tari_common::SubConfigPath; +use tari_common_types::grpc_authentication::GrpcAuthentication; use tari_comms::multiaddr::Multiaddr; #[derive(Serialize, Deserialize, Debug)] @@ -50,6 +51,8 @@ pub struct MinerConfig { pub base_node_grpc_address: Multiaddr, /// GRPC address of console wallet pub wallet_grpc_address: Multiaddr, + /// GRPC authentication for console wallet + pub wallet_grpc_authentication: GrpcAuthentication, /// 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 @@ -85,6 +88,7 @@ impl Default for MinerConfig { Self { base_node_grpc_address: Multiaddr::from_str("/ip4/127.0.0.1/tcp/18142").unwrap(), wallet_grpc_address: Multiaddr::from_str("/ip4/127.0.0.1/tcp/18143").unwrap(), + wallet_grpc_authentication: GrpcAuthentication::default(), num_mining_threads: num_cpus::get(), mine_on_tip_only: true, proof_of_work_algo: ProofOfWork::Sha3, diff --git a/applications/tari_miner/src/errors.rs b/applications/tari_miner/src/errors.rs index 9dc4c304da0..522d6483107 100644 --- a/applications/tari_miner/src/errors.rs +++ b/applications/tari_miner/src/errors.rs @@ -20,7 +20,9 @@ // 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 tari_app_grpc::authentication::BasicAuthError; use thiserror::Error; +use tonic::codegen::http::uri::InvalidUri; #[derive(Debug, Error)] pub enum MinerError { @@ -42,6 +44,10 @@ pub enum MinerError { BlockHeader(String), #[error("Conversion error: {0}")] Conversion(String), + #[error("Invalid grpc credentials: {0}")] + BasicAuthError(#[from] BasicAuthError), + #[error("Invalid grpc url: {0}")] + InvalidUri(#[from] InvalidUri), } pub fn err_empty(name: &str) -> MinerError { diff --git a/applications/tari_miner/src/main.rs b/applications/tari_miner/src/main.rs index 2515c1701b7..06a48ac577e 100644 --- a/applications/tari_miner/src/main.rs +++ b/applications/tari_miner/src/main.rs @@ -23,6 +23,7 @@ use std::{ convert::TryFrom, io::{stdout, Write}, + str::FromStr, thread, time::Instant, }; @@ -33,7 +34,10 @@ use errors::{err_empty, MinerError}; use futures::stream::StreamExt; use log::*; use miner::Miner; -use tari_app_grpc::tari_rpc::{base_node_client::BaseNodeClient, wallet_client::WalletClient}; +use tari_app_grpc::{ + authentication::ClientAuthenticationInterceptor, + tari_rpc::{base_node_client::BaseNodeClient, wallet_client::WalletClient}, +}; use tari_app_utilities::consts; use tari_common::{ exit_codes::{ExitCode, ExitError}, @@ -46,7 +50,10 @@ use tari_core::blocks::BlockHeader; use tari_crypto::ristretto::RistrettoPublicKey; use tari_utilities::hex::Hex; use tokio::{runtime::Runtime, time::sleep}; -use tonic::transport::Channel; +use tonic::{ + codegen::InterceptedService, + transport::{Channel, Endpoint}, +}; use utils::{coinbase_request, extract_outputs_and_kernels}; use crate::{cli::Cli, config::MinerConfig, miner::MiningReport, stratum::stratum_controller::controller::Controller}; @@ -62,6 +69,8 @@ mod miner; mod stratum; mod utils; +type WalletGrpcClient = WalletClient>; + /// Application entry point fn main() { let rt = Runtime::new().expect("Failed to start tokio runtime"); @@ -194,20 +203,30 @@ async fn main_inner() -> Result<(), ExitError> { } } -async fn connect(config: &MinerConfig) -> Result<(BaseNodeClient, WalletClient), MinerError> { +async fn connect(config: &MinerConfig) -> Result<(BaseNodeClient, WalletGrpcClient), MinerError> { let base_node_addr = multiaddr_to_socketaddr(&config.base_node_grpc_address)?; info!(target: LOG_TARGET, "🔗 Connecting to base node at {}", base_node_addr); let node_conn = BaseNodeClient::connect(format!("http://{}", base_node_addr)).await?; - let wallet_addr = multiaddr_to_socketaddr(&config.wallet_grpc_address)?; - info!(target: LOG_TARGET, "👛 Connecting to wallet at {}", wallet_addr); - let wallet_conn = WalletClient::connect(format!("http://{}", wallet_addr)).await?; + let wallet_conn = connect_wallet(config).await?; Ok((node_conn, wallet_conn)) } +async fn connect_wallet(config: &MinerConfig) -> Result { + let wallet_addr = format!("http://{}", multiaddr_to_socketaddr(&config.wallet_grpc_address)?); + info!(target: LOG_TARGET, "👛 Connecting to wallet at {}", wallet_addr); + let channel = Endpoint::from_str(&wallet_addr)?.connect().await?; + let wallet_conn = WalletClient::with_interceptor( + channel, + ClientAuthenticationInterceptor::create(&config.wallet_grpc_authentication)?, + ); + + Ok(wallet_conn) +} + async fn mining_cycle( node_conn: &mut BaseNodeClient, - wallet_conn: &mut WalletClient, + wallet_conn: &mut WalletGrpcClient, config: &MinerConfig, cli: &Cli, ) -> Result { diff --git a/common/config/presets/g_miner.toml b/common/config/presets/g_miner.toml index 258207d0761..31e649f54e5 100644 --- a/common/config/presets/g_miner.toml +++ b/common/config/presets/g_miner.toml @@ -12,6 +12,8 @@ # GRPC address of console wallet (default = "/ip4/127.0.0.1/tcp/18143") #wallet_grpc_address = "/ip4/127.0.0.1/tcp/18143" +# GRPC authentication for the console wallet (default = "none") +#wallet_grpc_authentication = { username: "miner", password: "$argon..." } # Number of mining threads (default: number of logical CPU cores) #num_mining_threads = 8