support api secrets, handle SIGQUIT, SIGINT, and SIGTERM

This commit is contained in:
scilio 2022-07-22 10:47:04 -04:00
parent 40bf97a841
commit e902f78e32
8 changed files with 132 additions and 22 deletions

1
Cargo.lock generated
View file

@ -2475,6 +2475,7 @@ dependencies = [
"bytes 0.5.6", "bytes 0.5.6",
"chacha20", "chacha20",
"clap", "clap",
"dirs",
"failure", "failure",
"futures 0.3.17", "futures 0.3.17",
"grin_api 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)", "grin_api 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)",

View file

@ -11,6 +11,7 @@ byteorder = "1"
bytes = "0.5.6" bytes = "0.5.6"
chacha20 = "0.8.1" chacha20 = "0.8.1"
clap = { version = "2.33", features = ["yaml"] } clap = { version = "2.33", features = ["yaml"] }
dirs = "2.0"
failure = "0.1.8" failure = "0.1.8"
futures = "0.3" futures = "0.3"
hmac = { version = "0.12.0", features = ["std"]} hmac = { version = "0.12.0", features = ["std"]}

View file

@ -13,11 +13,19 @@ args:
short: n short: n
long: grin_node_url long: grin_node_url
takes_value: true takes_value: true
- grin_node_secret_path:
help: Path to a file containing the secret for the GRIN node api
long: grin_node_secret_path
takes_value: true
- wallet_owner_url: - wallet_owner_url:
help: Api address of running wallet owner listener help: Api address of running wallet owner listener
short: l short: l
long: wallet_owner_url long: wallet_owner_url
takes_value: true takes_value: true
- wallet_owner_secret_path:
help: Path to a file containing the secret for the wallet owner api
long: wallet_owner_secret_path
takes_value: true
- wallet_pass: - wallet_pass:
help: The wallet's password help: The wallet's password
long: wallet_pass long: wallet_pass

View file

@ -2,7 +2,7 @@ use crate::error::{self, Result};
use crate::secp::SecretKey; use crate::secp::SecretKey;
use core::num::NonZeroU32; use core::num::NonZeroU32;
use grin_util::{ToHex, ZeroingString}; use grin_util::{file, ToHex, ZeroingString};
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use ring::{aead, pbkdf2}; use ring::{aead, pbkdf2};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -11,6 +11,11 @@ use std::io::prelude::*;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
const GRIN_HOME: &str = ".grin";
const CHAIN_NAME: &str = "main";
const NODE_API_SECRET_FILE_NAME: &str = ".api_secret";
const WALLET_OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret";
/// The decrypted server config to be passed around and used by the rest of the mwixnet code /// The decrypted server config to be passed around and used by the rest of the mwixnet code
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ServerConfig { pub struct ServerConfig {
@ -22,8 +27,22 @@ pub struct ServerConfig {
pub addr: SocketAddr, pub addr: SocketAddr,
/// foreign api address of the grin node /// foreign api address of the grin node
pub grin_node_url: SocketAddr, pub grin_node_url: SocketAddr,
/// path to file containing api secret for the grin node
pub grin_node_secret_path: Option<String>,
/// owner api address of the grin wallet /// owner api address of the grin wallet
pub wallet_owner_url: SocketAddr, pub wallet_owner_url: SocketAddr,
/// path to file containing secret for the grin wallet's owner api
pub wallet_owner_secret_path: Option<String>,
}
impl ServerConfig {
pub fn node_api_secret(&self) -> Option<String> {
file::get_first_line(self.grin_node_secret_path.clone())
}
pub fn wallet_owner_api_secret(&self) -> Option<String> {
file::get_first_line(self.wallet_owner_secret_path.clone())
}
} }
/// Encrypted server key, for storing on disk and decrypting with a password. /// Encrypted server key, for storing on disk and decrypting with a password.
@ -134,7 +153,9 @@ struct RawConfig {
interval_s: u32, interval_s: u32,
addr: SocketAddr, addr: SocketAddr,
grin_node_url: SocketAddr, grin_node_url: SocketAddr,
grin_node_secret_path: Option<String>,
wallet_owner_url: SocketAddr, wallet_owner_url: SocketAddr,
wallet_owner_secret_path: Option<String>,
} }
/// Writes the server config to the config_path given, encrypting the server_key first. /// Writes the server config to the config_path given, encrypting the server_key first.
@ -152,7 +173,9 @@ pub fn write_config(
interval_s: server_config.interval_s, interval_s: server_config.interval_s,
addr: server_config.addr, addr: server_config.addr,
grin_node_url: server_config.grin_node_url, grin_node_url: server_config.grin_node_url,
grin_node_secret_path: server_config.grin_node_secret_path.clone(),
wallet_owner_url: server_config.wallet_owner_url, wallet_owner_url: server_config.wallet_owner_url,
wallet_owner_secret_path: server_config.wallet_owner_secret_path.clone(),
}; };
let encoded: String = toml::to_string(&raw_config).map_err(|e| { let encoded: String = toml::to_string(&raw_config).map_err(|e| {
error::ErrorKind::SaveConfigError(format!("Error while encoding config as toml: {}", e)) error::ErrorKind::SaveConfigError(format!("Error while encoding config as toml: {}", e))
@ -189,10 +212,34 @@ pub fn load_config(config_path: &PathBuf, password: &ZeroingString) -> Result<Se
interval_s: raw_config.interval_s, interval_s: raw_config.interval_s,
addr: raw_config.addr, addr: raw_config.addr,
grin_node_url: raw_config.grin_node_url, grin_node_url: raw_config.grin_node_url,
grin_node_secret_path: raw_config.grin_node_secret_path,
wallet_owner_url: raw_config.wallet_owner_url, wallet_owner_url: raw_config.wallet_owner_url,
wallet_owner_secret_path: raw_config.wallet_owner_secret_path,
}) })
} }
pub fn get_grin_path() -> PathBuf {
let mut grin_path = match dirs::home_dir() {
Some(p) => p,
None => PathBuf::new(),
};
grin_path.push(GRIN_HOME);
grin_path.push(CHAIN_NAME);
grin_path
}
pub fn node_secret_path() -> PathBuf {
let mut path = get_grin_path();
path.push(NODE_API_SECRET_FILE_NAME);
path
}
pub fn wallet_owner_secret_path() -> PathBuf {
let mut path = get_grin_path();
path.push(WALLET_OWNER_API_SECRET_FILE_NAME);
path
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -1,13 +1,10 @@
use config::ServerConfig; use config::ServerConfig;
use error::{Error, ErrorKind};
use node::HttpGrinNode; use node::HttpGrinNode;
use wallet::HttpWallet; use wallet::HttpWallet;
use clap::App; use clap::App;
use grin_util::{StopState, ZeroingString}; use grin_util::{StopState, ZeroingString};
use rpassword; use rpassword;
use std::env;
use std::io::{self, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
@ -41,9 +38,9 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
let config_path = match args.value_of("config_file") { let config_path = match args.value_of("config_file") {
Some(path) => PathBuf::from(path), Some(path) => PathBuf::from(path),
None => { None => {
let mut current_dir = env::current_dir()?; let mut grin_path = config::get_grin_path();
current_dir.push("mwixnet-config.toml"); grin_path.push("mwixnet-config.toml");
current_dir grin_path
} }
}; };
@ -52,7 +49,9 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
.map(|t| t.parse::<u32>().unwrap()); .map(|t| t.parse::<u32>().unwrap());
let bind_addr = args.value_of("bind_addr"); let bind_addr = args.value_of("bind_addr");
let grin_node_url = args.value_of("grin_node_url"); let grin_node_url = args.value_of("grin_node_url");
let grin_node_secret_path = args.value_of("grin_node_secret_path");
let wallet_owner_url = args.value_of("wallet_owner_url"); let wallet_owner_url = args.value_of("wallet_owner_url");
let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path");
// Write a new config file if init-config command is supplied // Write a new config file if init-config command is supplied
if let ("init-config", Some(_)) = args.subcommand() { if let ("init-config", Some(_)) = args.subcommand() {
@ -68,7 +67,17 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL), interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
addr: bind_addr.unwrap_or("0.0.0.0:3000").parse()?, addr: bind_addr.unwrap_or("0.0.0.0:3000").parse()?,
grin_node_url: grin_node_url.unwrap_or("127.0.0.1:3413").parse()?, grin_node_url: grin_node_url.unwrap_or("127.0.0.1:3413").parse()?,
grin_node_secret_path: match grin_node_secret_path {
Some(p) => Some(p.to_owned()),
None => config::node_secret_path().to_str().map(|p| p.to_owned()),
},
wallet_owner_url: wallet_owner_url.unwrap_or("127.0.0.1:3420").parse()?, wallet_owner_url: wallet_owner_url.unwrap_or("127.0.0.1:3420").parse()?,
wallet_owner_secret_path: match wallet_owner_secret_path {
Some(p) => Some(p.to_owned()),
None => config::wallet_owner_secret_path()
.to_str()
.map(|p| p.to_owned()),
},
}; };
let password = prompt_password_confirm(); let password = prompt_password_confirm();
@ -93,31 +102,41 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
server_config.grin_node_url = grin_node_url.parse()?; server_config.grin_node_url = grin_node_url.parse()?;
} }
// Override grin_node_secret_path, if supplied
if let Some(grin_node_secret_path) = grin_node_secret_path {
server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned());
}
// Override wallet_owner_url, if supplied // Override wallet_owner_url, if supplied
if let Some(wallet_owner_url) = wallet_owner_url { if let Some(wallet_owner_url) = wallet_owner_url {
server_config.wallet_owner_url = wallet_owner_url.parse()?; server_config.wallet_owner_url = wallet_owner_url.parse()?;
} }
// Override wallet_owner_secret_path, if supplied
if let Some(wallet_owner_secret_path) = wallet_owner_secret_path {
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
}
// Open wallet // Open wallet
let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass")); let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass"));
let wallet = HttpWallet::open_wallet(&server_config.wallet_owner_url, &wallet_pass)?; let wallet = HttpWallet::open_wallet(
&server_config.wallet_owner_url,
&server_config.wallet_owner_api_secret(),
&wallet_pass,
)?;
// Create GrinNode // Create GrinNode
let node = HttpGrinNode::new(&server_config.grin_node_url, &None); let node = HttpGrinNode::new(
&server_config.grin_node_url,
&server_config.node_api_secret(),
);
let stop_state = Arc::new(StopState::new()); let stop_state = Arc::new(StopState::new());
let stop_state_clone = stop_state.clone(); let stop_state_clone = stop_state.clone();
let rt = Runtime::new()?; let rt = Runtime::new()?;
rt.spawn(async move { rt.spawn(async move {
let shutdown_signal = async move { futures::executor::block_on(build_signals_fut());
// Wait for the CTRL+C signal
tokio::signal::ctrl_c()
.await
.expect("failed to install CTRL+C signal handler");
};
futures::executor::block_on(shutdown_signal);
stop_state_clone.stop(); stop_state_clone.stop();
}); });
@ -130,6 +149,30 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
) )
} }
async fn build_signals_fut() {
if cfg!(unix) {
use tokio::signal::unix::{signal, SignalKind};
// Listen for SIGINT, SIGQUIT, and SIGTERM
let mut terminate_signal =
signal(SignalKind::terminate()).expect("failed to create terminate signal");
let mut quit_signal = signal(SignalKind::quit()).expect("failed to create quit signal");
let mut interrupt_signal =
signal(SignalKind::interrupt()).expect("failed to create interrupt signal");
futures::future::select_all(vec![
Box::pin(terminate_signal.recv()),
Box::pin(quit_signal.recv()),
Box::pin(interrupt_signal.recv()),
])
.await;
} else {
tokio::signal::ctrl_c()
.await
.expect("failed to install CTRL+C signal handler");
}
}
fn prompt_password() -> ZeroingString { fn prompt_password() -> ZeroingString {
ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap()) ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap())
} }

View file

@ -116,15 +116,14 @@ pub mod comsig_serde {
} }
/// Creates a ComSignature from a hex string /// Creates a ComSignature from a hex string
pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<ComSignature, D::Error> pub fn deserialize<'de, D>(deserializer: D) -> Result<ComSignature, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
use serde::de::Error; use serde::de::Error;
let bytes = String::deserialize(deserializer) let bytes = String::deserialize(deserializer)
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?; .and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?;
let sig: ComSignature = let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?;
grin_core::ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?;
Ok(sig) Ok(sig)
} }
} }

View file

@ -319,7 +319,9 @@ mod tests {
interval_s: 1, interval_s: 1,
addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?, addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
grin_node_url: "127.0.0.1:3413".parse()?, grin_node_url: "127.0.0.1:3413".parse()?,
grin_node_secret_path: None,
wallet_owner_url: "127.0.0.1:3420".parse()?, wallet_owner_url: "127.0.0.1:3420".parse()?,
wallet_owner_secret_path: None,
}; };
let threaded_rt = runtime::Runtime::new()?; let threaded_rt = runtime::Runtime::new()?;

View file

@ -85,6 +85,7 @@ pub fn assemble_tx(
#[derive(Clone)] #[derive(Clone)]
pub struct HttpWallet { pub struct HttpWallet {
wallet_owner_url: SocketAddr, wallet_owner_url: SocketAddr,
wallet_owner_secret: Option<String>,
shared_key: SecretKey, shared_key: SecretKey,
token: Token, token: Token,
} }
@ -104,6 +105,7 @@ impl HttpWallet {
/// Calls the 'open_wallet' using the RPC API. /// Calls the 'open_wallet' using the RPC API.
pub fn open_wallet( pub fn open_wallet(
wallet_owner_url: &SocketAddr, wallet_owner_url: &SocketAddr,
wallet_owner_secret: &Option<String>,
wallet_pass: &ZeroingString, wallet_pass: &ZeroingString,
) -> Result<HttpWallet> { ) -> Result<HttpWallet> {
println!("Opening wallet at {}", wallet_owner_url); println!("Opening wallet at {}", wallet_owner_url);
@ -115,6 +117,7 @@ impl HttpWallet {
}); });
let token: Token = HttpWallet::send_enc_request( let token: Token = HttpWallet::send_enc_request(
&wallet_owner_url, &wallet_owner_url,
&wallet_owner_secret,
"open_wallet", "open_wallet",
&open_wallet_params, &open_wallet_params,
&shared_key, &shared_key,
@ -123,6 +126,7 @@ impl HttpWallet {
Ok(HttpWallet { Ok(HttpWallet {
wallet_owner_url: wallet_owner_url.clone(), wallet_owner_url: wallet_owner_url.clone(),
wallet_owner_secret: wallet_owner_secret.clone(),
shared_key: shared_key.clone(), shared_key: shared_key.clone(),
token: token.clone(), token: token.clone(),
}) })
@ -152,6 +156,7 @@ impl HttpWallet {
fn send_enc_request<D: serde::de::DeserializeOwned>( fn send_enc_request<D: serde::de::DeserializeOwned>(
wallet_owner_url: &SocketAddr, wallet_owner_url: &SocketAddr,
wallet_owner_secret: &Option<String>,
method: &str, method: &str,
params: &serde_json::Value, params: &serde_json::Value,
shared_key: &SecretKey, shared_key: &SecretKey,
@ -164,8 +169,11 @@ impl HttpWallet {
"jsonrpc": "2.0", "jsonrpc": "2.0",
}); });
let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key)?; let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key)?;
let res = let res = client::post::<EncryptedRequest, EncryptedResponse>(
client::post::<EncryptedRequest, EncryptedResponse>(url.as_str(), None, &enc_req)?; url.as_str(),
wallet_owner_secret.clone(),
&enc_req,
)?;
let decrypted = res.decrypt(&shared_key)?; let decrypted = res.decrypt(&shared_key)?;
let response: Response = serde_json::from_value(decrypted.clone())?; let response: Response = serde_json::from_value(decrypted.clone())?;
let parsed = serde_json::from_value(response.result.unwrap().get("Ok").unwrap().clone())?; let parsed = serde_json::from_value(response.result.unwrap().get("Ok").unwrap().clone())?;
@ -205,6 +213,7 @@ impl Wallet for HttpWallet {
}); });
let output: OutputWithBlind = HttpWallet::send_enc_request( let output: OutputWithBlind = HttpWallet::send_enc_request(
&self.wallet_owner_url, &self.wallet_owner_url,
&self.wallet_owner_secret,
"build_output", "build_output",
&req_json, &req_json,
&self.shared_key, &self.shared_key,