Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use clap::{App, Arg};
use dirs::home_dir;
use std::fmt;
use std::fs;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
Expand All @@ -17,6 +18,33 @@ use bitcoin::Network as BNetwork;

const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Clone)]
pub struct SensitiveAuth(String);

impl SensitiveAuth {
pub fn new(value: String) -> Self {
Self(value)
}

fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}

impl fmt::Debug for SensitiveAuth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let username = self
.0
.split_once(':')
.map(|(username, _)| username)
.unwrap_or("<invalid>");
f.debug_tuple("UserPass")
.field(&username)
.field(&"<sensitive>")
.finish()
}
}

#[derive(Debug, Clone)]
pub struct Config {
// See below for the documentation of each field:
Expand All @@ -29,7 +57,7 @@ pub struct Config {
pub daemon_rpc_fallback_addr: Option<SocketAddr>,
pub daemon_parallelism: usize,
pub daemon_conn_max_age: Option<Duration>,
pub cookie: Option<String>,
pub cookie: Option<SensitiveAuth>,
pub electrum_rpc_addr: SocketAddr,
pub electrum_rpc_conn_max_age: Option<Duration>,
pub http_addr: SocketAddr,
Expand Down Expand Up @@ -496,7 +524,9 @@ impl Config {
.value_of("blocks_dir")
.map(PathBuf::from)
.unwrap_or_else(|| daemon_dir.join("blocks"));
let cookie = m.value_of("cookie").map(|s| s.to_owned());
let cookie = m
.value_of("cookie")
.map(|s| SensitiveAuth::new(s.to_owned()));

let electrum_banner = m.value_of("electrum_banner").map_or_else(
|| format!("Welcome to electrs-esplora {}", ELECTRS_VERSION),
Expand Down Expand Up @@ -573,7 +603,14 @@ impl Config {
#[cfg(feature = "electrum-discovery")]
tor_proxy: m.value_of("tor_proxy").map(|s| s.parse().unwrap()),
};
eprintln!("{:?}", config);
match &config.cookie {
Some(auth) => log::debug!("daemon authentication: {:?}", auth),
None => log::debug!(
"daemon authentication: CookieFile({:?})",
config.daemon_dir.join(".cookie")
),
}
log::debug!("configuration: {:?}", config);
config
}

Expand Down Expand Up @@ -650,3 +687,18 @@ impl CookieGetter for CookieFile {
Ok(contents)
}
}

#[cfg(test)]
mod tests {
use super::SensitiveAuth;

#[test]
fn sensitive_auth_debug_redacts_password() {
let password = "poc-PASSWORD-123";
let auth = SensitiveAuth::new(format!("poc-user:{}", password));
let rendered = format!("{:?}", auth);

assert_eq!(rendered, r#"UserPass("poc-user", "<sensitive>")"#);
assert!(!rendered.contains(password));
}
}
74 changes: 74 additions & 0 deletions tests/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::net::TcpListener;
use std::path::Path;
use std::process::{Command, Output};

fn run_electrs(temp_dir: &Path, extra_args: &[&str]) -> Output {
let monitoring_listener = TcpListener::bind("127.0.0.1:0").unwrap();
let monitoring_addr = monitoring_listener.local_addr().unwrap().to_string();

Command::new(env!("CARGO_BIN_EXE_electrs"))
.args([
"--db-dir",
temp_dir.join("db").to_str().unwrap(),
"--daemon-dir",
temp_dir.to_str().unwrap(),
"--daemon-rpc-addr",
"127.0.0.1:1",
"--monitoring-addr",
monitoring_addr.as_str(),
])
.args(extra_args)
.output()
.expect("failed to run electrs")
}

#[test]
fn startup_never_logs_static_auth_password() {
let password = "poc-PASSWORD-123";
let cookie = format!("poc-user:{}", password);

for verbosity in [None, Some("-v"), Some("-vv")] {
let temp_dir = tempfile::tempdir().unwrap();
let mut extra_args = vec!["--cookie", cookie.as_str()];
if let Some(verbosity) = verbosity {
extra_args.push(verbosity);
}

let output = run_electrs(temp_dir.path(), &extra_args);
let stderr = String::from_utf8(output.stderr).unwrap();

assert!(!output.status.success(), "electrs unexpectedly succeeded");
assert!(
!stderr.contains(password),
"password was logged at verbosity {:?}: {}",
verbosity,
stderr
);

if verbosity.is_some() {
assert!(
stderr.contains(r#"daemon authentication: UserPass("poc-user", "<sensitive>")"#),
"redacted authentication mode missing from stderr: {}",
stderr
);
}
}
}

#[test]
fn startup_debug_log_identifies_cookie_file() {
let temp_dir = tempfile::tempdir().unwrap();
let output = run_electrs(temp_dir.path(), &["-v"]);
let stderr = String::from_utf8(output.stderr).unwrap();
let expected = format!(
"daemon authentication: CookieFile({:?})",
temp_dir.path().join(".cookie")
);

assert!(!output.status.success(), "electrs unexpectedly succeeded");
assert!(
stderr.contains(&expected),
"cookie-file authentication mode missing from stderr: {}",
stderr
);
}
Loading