Skip to content

Commit

Permalink
Adds rpccookie to bitcoind auth methods
Browse files Browse the repository at this point in the history
Currently we are authenticating against bitcoind using user/pass. This adds
the option to use a cookie file instead.
  • Loading branch information
sr-gi committed Mar 28, 2024
1 parent 21acbe0 commit 37a75ec
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 17 deletions.
32 changes: 27 additions & 5 deletions teos/src/bitcoin_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use bitcoin::base64;
use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::hashes::hex::ToHex;
use bitcoin::{Block, Transaction};
use bitcoincore_rpc::Auth;
use lightning::util::ser::Writeable;
use lightning_block_sync::http::{HttpEndpoint, JsonResponse};
use lightning_block_sync::rpc::RpcClient;
Expand All @@ -32,9 +33,9 @@ pub struct BitcoindClient<'a> {
/// The port to connect to.
port: u16,
/// The RPC user `bitcoind` is configured with.
rpc_user: &'a str,
rpc_user: String,
/// The RPC password for the given user.
rpc_password: &'a str,
rpc_password: String,
}

impl BlockSource for &BitcoindClient<'_> {
Expand Down Expand Up @@ -74,12 +75,33 @@ impl<'a> BitcoindClient<'a> {
pub async fn new(
host: &'a str,
port: u16,
rpc_user: &'a str,
rpc_password: &'a str,
auth: Auth,
teos_network: &'a str,
) -> std::io::Result<BitcoindClient<'a>> {
let http_endpoint = HttpEndpoint::for_host(host.to_owned()).with_port(port);
let rpc_credentials = base64::encode(&format!("{rpc_user}:{rpc_password}"));
let (rpc_user, rpc_password) = {
let (user, pass) = auth.get_user_pass().map_err(|e| {
Error::new(
ErrorKind::InvalidInput,
format!("Cannot read cookie file. {}", e),
)
})?;
if user.is_none() {
Err(Error::new(
ErrorKind::InvalidInput,
"Empty btc_rpc_user parsed from rpc_cookie".to_string(),
))
} else if pass.is_none() {
Err(Error::new(
ErrorKind::InvalidInput,
"Empty btc_rpc_password parsed from rpc_cookie",
))
} else {
Ok((user.unwrap(), pass.unwrap()))
}
}?;

let rpc_credentials = base64::encode(&format!("{}:{}", rpc_user, rpc_password));
let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?;

let client = Self {
Expand Down
2 changes: 2 additions & 0 deletions teos/src/conf_template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ rpc_port = 8814
# bitcoind
btc_network = "mainnet"
btc_rpc_user = "CSW"
## Notice only user+password **OR** cookie is allowed as rpc auth, any other combination would be rejected
btc_rpc_password = "NotSatoshi"
btc_rpc_connect = "localhost"
btc_rpc_cookie = "~/.bitcoin/.cookie"
btc_rpc_port = 8332

# Flags
Expand Down
55 changes: 47 additions & 8 deletions teos/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ impl std::fmt::Display for ConfigError {

impl std::error::Error for ConfigError {}

#[derive(PartialEq)]
pub enum AuthMethod {
UserPass,
CookieFile,
Multiple,
Invalid,
}

/// Holds all the command line options.
#[derive(StructOpt, Debug, Clone)]
#[structopt(rename_all = "lowercase")]
Expand All @@ -66,14 +74,18 @@ pub struct Opt {
#[structopt(long)]
pub btc_network: Option<String>,

/// bitcoind rpcuser [default: user]
/// bitcoind rpcuser
#[structopt(long)]
pub btc_rpc_user: Option<String>,

/// bitcoind rpcpassword [default: passwd]
/// bitcoind rpcpassword
#[structopt(long)]
pub btc_rpc_password: Option<String>,

/// bitcoind rpccookie
#[structopt(long)]
pub btc_rpc_cookie: Option<String>,

/// bitcoind rpcconnect [default: localhost]
#[structopt(long)]
pub btc_rpc_connect: Option<String>,
Expand Down Expand Up @@ -136,6 +148,7 @@ pub struct Config {
// Bitcoind
pub btc_network: String,
pub btc_rpc_user: String,
pub btc_rpc_cookie: String,
pub btc_rpc_password: String,
pub btc_rpc_connect: String,
pub btc_rpc_port: u16,
Expand Down Expand Up @@ -164,6 +177,24 @@ pub struct Config {
}

impl Config {
/// The only combinations of valid authentication methods are:
/// - User **AND** password
/// - **OR** Cookie file
//
/// Any other combination will be rejected
pub fn get_auth_method(&self) -> AuthMethod {
match (
self.btc_rpc_user.is_empty(),
self.btc_rpc_password.is_empty(),
self.btc_rpc_cookie.is_empty(),
) {
(false, false, true) => AuthMethod::UserPass,
(true, true, false) => AuthMethod::CookieFile,
(true, true, true) => AuthMethod::Invalid,
_ => AuthMethod::Multiple,
}
}

/// Patches the configuration options with the command line options.
pub fn patch_with_options(&mut self, options: Opt) {
if options.api_bind.is_some() {
Expand All @@ -187,6 +218,9 @@ impl Config {
if options.btc_rpc_password.is_some() {
self.btc_rpc_password = options.btc_rpc_password.unwrap();
}
if options.btc_rpc_cookie.is_some() {
self.btc_rpc_cookie = options.btc_rpc_cookie.unwrap();
}
if options.btc_rpc_connect.is_some() {
self.btc_rpc_connect = options.btc_rpc_connect.unwrap();
}
Expand Down Expand Up @@ -216,11 +250,14 @@ impl Config {
/// This will also assign the default `btc_rpc_port` depending on the network if it has not
/// been overwritten at this point.
pub fn verify(&mut self) -> Result<(), ConfigError> {
if self.btc_rpc_user == String::new() {
return Err(ConfigError("btc_rpc_user must be set".to_owned()));
}
if self.btc_rpc_password == String::new() {
return Err(ConfigError("btc_rpc_password must be set".to_owned()));
let auth_method = self.get_auth_method();
if auth_method == AuthMethod::Invalid {
return Err(ConfigError("No valid bitcoind auth provided. Set either both btc_rpc_user/btc_rpc_password or btc_rpc_cookie".to_owned()));
} else if auth_method == AuthMethod::Multiple {
return Err(ConfigError(
"Multiple bitcoind auth provided. Pick a single one (either btc_rpc_user/btc_rpc_password or btc_rpc_cookie)"
.to_owned(),
));
}

// Normalize the network option to the ones used by bitcoind.
Expand Down Expand Up @@ -291,6 +328,7 @@ impl Default for Config {
btc_network: "mainnet".into(),
btc_rpc_user: String::new(),
btc_rpc_password: String::new(),
btc_rpc_cookie: String::new(),
btc_rpc_connect: "localhost".into(),
btc_rpc_port: 0,

Expand Down Expand Up @@ -326,6 +364,7 @@ mod tests {
btc_network: None,
btc_rpc_user: None,
btc_rpc_password: None,
btc_rpc_cookie: None,
btc_rpc_connect: None,
btc_rpc_port: None,
data_dir: String::from("~/.teos"),
Expand Down Expand Up @@ -363,7 +402,7 @@ mod tests {
// required to be updated by the user.
let mut config = Config::default();
assert!(
matches!(config.verify(), Err(ConfigError(e)) if e.contains("btc_rpc_user must be set"))
matches!(config.verify(), Err(ConfigError(e)) if e.contains("No valid bitcoind auth provided"))
);
}

Expand Down
16 changes: 12 additions & 4 deletions teos/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use teos::api::{http, tor::TorAPI};
use teos::bitcoin_cli::BitcoindClient;
use teos::carrier::Carrier;
use teos::chain_monitor::ChainMonitor;
use teos::config::{self, Config, Opt};
use teos::config::{self, AuthMethod, Config, Opt};
use teos::dbm::DBM;
use teos::gatekeeper::Gatekeeper;
use teos::protos as msgs;
Expand Down Expand Up @@ -142,12 +142,20 @@ async fn main() {
};
log::info!("tower_id: {tower_pk}");

let btc_rpc_auth = match conf.get_auth_method() {
AuthMethod::CookieFile => {
Auth::CookieFile(config::data_dir_absolute_path(conf.btc_rpc_cookie))
}
AuthMethod::UserPass => Auth::UserPass(conf.btc_rpc_user, conf.btc_rpc_password),
// Notice an invalid conf would have failed on `Config::verify()`
_ => unreachable!("A verified conf will only have one of these two auth methods"),
};

// Initialize our bitcoind client
let (bitcoin_cli, bitcoind_reachable) = match BitcoindClient::new(
&conf.btc_rpc_connect,
conf.btc_rpc_port,
&conf.btc_rpc_user,
&conf.btc_rpc_password,
btc_rpc_auth.clone(),
&conf.btc_network,
)
.await
Expand Down Expand Up @@ -176,7 +184,7 @@ async fn main() {
let rpc = Arc::new(
Client::new(
&format!("{schema}{}:{}", conf.btc_rpc_connect, conf.btc_rpc_port),
Auth::UserPass(conf.btc_rpc_user.clone(), conf.btc_rpc_password.clone()),
btc_rpc_auth,
)
.unwrap(),
);
Expand Down

0 comments on commit 37a75ec

Please sign in to comment.