Skip to content

Commit

Permalink
feat(miner): add client wallet grpc authentication interceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbondi committed Aug 24, 2022
1 parent a3788d0 commit 40e7f44
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 25 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 31 additions & 4 deletions applications/tari_app_grpc/src/authentication/basic_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 }
}

Expand Down Expand Up @@ -91,6 +91,19 @@ impl BasicAuthCredentials {
.map_err(|_| BasicAuthError::InvalidCredentials)?;
Ok(())
}

pub fn generate_header(username: &str, password: &[u8]) -> Result<String, BasicAuthError> {
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
Expand Down Expand Up @@ -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();
}

Expand All @@ -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"[..]);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<MetadataValue<Ascii>>,
}

impl ClientAuthenticationInterceptor {
pub fn create(auth: &GrpcAuthentication) -> Result<Self, BasicAuthError> {
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<Request<()>, Status> {
match self.authorization_header.clone() {
Some(authorization_header) => {
request
.metadata_mut()
.insert(AUTHORIZATION.as_str(), authorization_header);
Ok(request)
},
None => Ok(request),
}
}
}
7 changes: 5 additions & 2 deletions applications/tari_app_grpc/src/authentication/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand All @@ -58,7 +58,7 @@ impl AuthenticationInterceptor {
}
}

impl Interceptor for AuthenticationInterceptor {
impl Interceptor for ServerAuthenticationInterceptor {
fn call(&mut self, request: Request<()>) -> Result<Request<()>, Status> {
match &self.auth {
GrpcAuthentication::None => Ok(request),
Expand All @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions applications/tari_console_wallet/src/wallet_modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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()
Expand Down
13 changes: 7 additions & 6 deletions applications/tari_miner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand All @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions applications/tari_miner/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions applications/tari_miner/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
33 changes: 26 additions & 7 deletions applications/tari_miner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use std::{
convert::TryFrom,
io::{stdout, Write},
str::FromStr,
thread,
time::Instant,
};
Expand All @@ -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},
Expand All @@ -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};
Expand All @@ -62,6 +69,8 @@ mod miner;
mod stratum;
mod utils;

type WalletGrpcClient = WalletClient<InterceptedService<Channel, ClientAuthenticationInterceptor>>;

/// Application entry point
fn main() {
let rt = Runtime::new().expect("Failed to start tokio runtime");
Expand Down Expand Up @@ -194,20 +203,30 @@ async fn main_inner() -> Result<(), ExitError> {
}
}

async fn connect(config: &MinerConfig) -> Result<(BaseNodeClient<Channel>, WalletClient<Channel>), MinerError> {
async fn connect(config: &MinerConfig) -> Result<(BaseNodeClient<Channel>, 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<WalletGrpcClient, MinerError> {
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<Channel>,
wallet_conn: &mut WalletClient<Channel>,
wallet_conn: &mut WalletGrpcClient,
config: &MinerConfig,
cli: &Cli,
) -> Result<bool, MinerError> {
Expand Down
2 changes: 2 additions & 0 deletions common/config/presets/g_miner.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 40e7f44

Please sign in to comment.