Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds rpccookie to bitcoind auth methods #249

Merged
merged 1 commit into from
Mar 28, 2024
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
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