Skip to content

Commit

Permalink
Merge pull request fedimint#6482 from tvolk131/gw_auth_cleanup
Browse files Browse the repository at this point in the history
feat: gateway auth redesign
  • Loading branch information
m1sterc001guy authored Dec 5, 2024
2 parents 475bc5f + 921f63b commit 6e82c25
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 211 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ async-trait = "0.1.83"
axum = "0.7.9"
base64 = "0.22.1"
base64-url = "3.0.0"
bcrypt = "0.16.0"
bincode = "1.3.3"
bitcoin = { version = "0.32.4", features = ["serde"] }
bitcoincore-rpc = "0.19.0"
Expand Down
13 changes: 0 additions & 13 deletions devimint/src/gatewayd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,19 +162,6 @@ impl Gatewayd {
)
}

pub fn change_password(&self, old_password: &str, new_password: &str) -> Command {
cmd!(
crate::util::get_gateway_cli_path(),
"--rpcpassword",
old_password,
"-a",
&self.addr,
"set-configuration",
"--password",
new_password,
)
}

pub async fn get_info(&self) -> Result<serde_json::Value> {
retry(
"Getting gateway info via gateway-cli info",
Expand Down
6 changes: 5 additions & 1 deletion devimint/src/vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,13 @@ declare_vars! {
FM_LND_TLS_CERT: PathBuf = FM_LND_DIR.join("tls.cert"); env: "FM_LND_TLS_CERT";
FM_LND_MACAROON: PathBuf = FM_LND_DIR.join("data/chain/bitcoin/regtest/admin.macaroon"); env: "FM_LND_MACAROON";

// TODO(support:v0.5): Remove this. It was used prior to `FM_GATEWAY_BCRYPT_PASSWORD_HASH` to provide a plaintext password to the gateway.
FM_GATEWAY_PASSWORD: String = "theresnosecondbest"; env: "FM_GATEWAY_PASSWORD";

// Enable to us to make an unbounded number of payments
// Bcrypt hash of "theresnosecondbest" with a cost of 10.
FM_GATEWAY_BCRYPT_PASSWORD_HASH: String = "$2y$10$Q/UTDeO84VGG1mRncxw.Nubqyi/HsNRJ40k0TSexFy9eVess1yi/u"; env: "FM_GATEWAY_BCRYPT_PASSWORD_HASH";

// Enable to us to make an unbounded number of payments
FM_DEFAULT_GATEWAY_FEES: String = "0,0"; env: "FM_DEFAULT_GATEWAY_FEES";
FM_GATEWAY_SKIP_WAIT_FOR_SYNC: String = "1"; env: "FM_GATEWAY_SKIP_WAIT_FOR_SYNC";

Expand Down
1 change: 1 addition & 0 deletions fedimint-testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ path = "src/lib.rs"
anyhow = { workspace = true }
async-stream = { workspace = true }
async-trait = { workspace = true }
bcrypt = { workspace = true }
bitcoin = { workspace = true }
bitcoincore-rpc = { workspace = true }
fedimint-api-client = { workspace = true }
Expand Down
6 changes: 5 additions & 1 deletion fedimint-testing/src/fixtures.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::env;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

Expand Down Expand Up @@ -206,7 +207,10 @@ impl Fixtures {
client_builder,
listen,
address.clone(),
Some(DEFAULT_GATEWAY_PASSWORD.to_string()),
bcrypt::HashParts::from_str(
&bcrypt::hash(DEFAULT_GATEWAY_PASSWORD, bcrypt::DEFAULT_COST).unwrap(),
)
.unwrap(),
Some(bitcoin::Network::Regtest),
RoutingFees {
base_msat: 0,
Expand Down
5 changes: 0 additions & 5 deletions gateway/cli/src/general_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ pub enum GeneralCommands {
Seed,
/// Set or update the gateway configuration.
SetConfiguration {
#[clap(long)]
password: Option<String>,

#[clap(long)]
num_route_hints: Option<u32>,

Expand Down Expand Up @@ -168,7 +165,6 @@ impl GeneralCommands {
print_response(response);
}
Self::SetConfiguration {
password,
num_route_hints,
routing_fees,
network,
Expand All @@ -178,7 +174,6 @@ impl GeneralCommands {
.map(|input| input.into_iter().map(Into::into).collect());
create_client()
.set_configuration(SetConfigurationPayload {
password,
num_route_hints,
routing_fees,
network,
Expand Down
13 changes: 2 additions & 11 deletions gateway/integration_tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,21 +388,12 @@ async fn config_test(gw_type: LightningNodeType) -> anyhow::Result<()> {
);
info!("Verified per-federation routing fees changed");

// Change password for gateway
gw.change_password("theresnosecondbest", "newpassword")
.run()
.await?;
get_gateway_info(gw)
.await
.expect_err("Expected info to return error since the password has changed");
gw.change_password("newpassword", "theresnosecondbest")
.run()
.await?;
// Try to change the network while connected to a lightning node
cmd!(gw, "set-configuration", "--network", "regtest")
.run()
.await
.expect_err("Cannot change the network while connected to a federation");
info!("Verified password change and network cannot be changed.");
info!("Verified network cannot be changed.");

// Get the federation's config and verify it parses correctly
let config_val = cmd!(gw, "config", "--federation-id", fed_id)
Expand Down
1 change: 1 addition & 0 deletions gateway/ln-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ aquamarine = { workspace = true }
async-stream = { workspace = true }
async-trait = { workspace = true }
axum = { version = "0.7.9", features = [ "json" ] }
bcrypt = { workspace = true }
bitcoin = { workspace = true }
clap = { workspace = true }
erased-serde = { workspace = true }
Expand Down
15 changes: 9 additions & 6 deletions gateway/ln-gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ pub struct GatewayOpts {
#[arg(long = "api-addr", env = envs::FM_GATEWAY_API_ADDR_ENV)]
api_addr: SafeUrl,

/// Gateway webserver authentication password
#[arg(long = "password", env = envs::FM_GATEWAY_PASSWORD_ENV)]
password: Option<String>,
/// Gateway webserver authentication bcrypt password hash
#[arg(long = "bcrypt-password-hash", env = envs::FM_GATEWAY_BCRYPT_PASSWORD_HASH_ENV)]
bcrypt_password_hash: String,

/// Bitcoin network this gateway will be running on
#[arg(long = "network", env = envs::FM_GATEWAY_NETWORK_ENV)]
Expand Down Expand Up @@ -68,10 +68,13 @@ impl GatewayOpts {
api_addr = self.api_addr,
)
})?;

let bcrypt_password_hash = bcrypt::HashParts::from_str(&self.bcrypt_password_hash)?;

Ok(GatewayParameters {
listen: self.listen,
versioned_api,
password: self.password.clone(),
bcrypt_password_hash,
network: self.network,
num_route_hints: self.num_route_hints,
fees: self.default_fees.clone(),
Expand All @@ -86,11 +89,11 @@ impl GatewayOpts {
///
/// If `GatewayConfiguration is set in the database, that takes precedence and
/// the optional parameters will have no affect.
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct GatewayParameters {
pub listen: SocketAddr,
pub versioned_api: SafeUrl,
pub password: Option<String>,
pub bcrypt_password_hash: bcrypt::HashParts,
pub network: Option<Network>,
pub num_route_hints: u32,
pub fees: Option<GatewayFee>,
Expand Down
56 changes: 49 additions & 7 deletions gateway/ln-gateway/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;

use bitcoin::hashes::sha256;
use bitcoin::hashes::{sha256, Hash};
use fedimint_api_client::api::net::Connector;
use fedimint_core::config::FederationId;
use fedimint_core::db::{
Expand All @@ -22,8 +22,6 @@ use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;

use crate::rpc::rpc_server::hash_password;

pub trait GatewayDbtxNcExt {
async fn save_federation_config(&mut self, config: &FederationConfig);
async fn load_federation_configs_v0(&mut self) -> BTreeMap<FederationId, FederationConfigV0>;
Expand Down Expand Up @@ -330,10 +328,10 @@ struct GatewayConfigurationV0 {
}

#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
pub struct GatewayConfigurationKey;
pub struct GatewayConfigurationKeyV1;

#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
pub struct GatewayConfiguration {
pub struct GatewayConfigurationV1 {
pub hashed_password: sha256::Hash,
pub num_route_hints: u32,
#[serde(with = "serde_routing_fees")]
Expand All @@ -342,12 +340,29 @@ pub struct GatewayConfiguration {
pub password_salt: [u8; 16],
}

#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
pub struct GatewayConfigurationKey;

#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
pub struct GatewayConfiguration {
pub num_route_hints: u32,
#[serde(with = "serde_routing_fees")]
pub routing_fees: RoutingFees,
pub network: NetworkLegacyEncodingWrapper,
}

impl_db_record!(
key = GatewayConfigurationKeyV0,
value = GatewayConfigurationV0,
db_prefix = DbKeyPrefix::GatewayConfiguration,
);

impl_db_record!(
key = GatewayConfigurationKeyV1,
value = GatewayConfigurationV1,
db_prefix = DbKeyPrefix::GatewayConfiguration,
);

impl_db_record!(
key = GatewayConfigurationKey,
value = GatewayConfiguration,
Expand Down Expand Up @@ -378,24 +393,34 @@ pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, CoreMigra
let mut migrations: BTreeMap<DatabaseVersion, CoreMigrationFn> = BTreeMap::new();
migrations.insert(DatabaseVersion(0), |ctx| migrate_to_v1(ctx).boxed());
migrations.insert(DatabaseVersion(1), |ctx| migrate_to_v2(ctx).boxed());
migrations.insert(DatabaseVersion(2), |ctx| migrate_to_v3(ctx).boxed());
migrations
}

async fn migrate_to_v1(mut ctx: MigrationContext<'_>) -> Result<(), anyhow::Error> {
/// Creates a password hash by appending a 4 byte salt to the plaintext
/// password.
fn hash_password(plaintext_password: &str, salt: [u8; 16]) -> sha256::Hash {
let mut bytes = Vec::new();
bytes.append(&mut plaintext_password.consensus_encode_to_vec());
bytes.append(&mut salt.consensus_encode_to_vec());
sha256::Hash::hash(&bytes)
}

let mut dbtx = ctx.dbtx();

// If there is no old gateway configuration, there is nothing to do.
if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
let password_salt: [u8; 16] = rand::thread_rng().gen();
let hashed_password = hash_password(&old_gateway_config.password, password_salt);
let new_gateway_config = GatewayConfiguration {
let new_gateway_config = GatewayConfigurationV1 {
hashed_password,
num_route_hints: old_gateway_config.num_route_hints,
routing_fees: old_gateway_config.routing_fees,
network: old_gateway_config.network,
password_salt,
};
dbtx.insert_entry(&GatewayConfigurationKey, &new_gateway_config)
dbtx.insert_entry(&GatewayConfigurationKeyV1, &new_gateway_config)
.await;
}

Expand Down Expand Up @@ -430,6 +455,23 @@ async fn migrate_to_v2(mut ctx: MigrationContext<'_>) -> Result<(), anyhow::Erro
Ok(())
}

async fn migrate_to_v3(mut ctx: MigrationContext<'_>) -> Result<(), anyhow::Error> {
let mut dbtx = ctx.dbtx();

// If there is no old gateway configuration, there is nothing to do.
if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV1).await {
let new_gateway_config = GatewayConfiguration {
num_route_hints: old_gateway_config.num_route_hints,
routing_fees: old_gateway_config.routing_fees,
network: old_gateway_config.network,
};
dbtx.insert_entry(&GatewayConfigurationKey, &new_gateway_config)
.await;
}

Ok(())
}

#[derive(Debug, Encodable, Decodable)]
struct RegisteredIncomingContractKey(pub PaymentImage);

Expand Down
5 changes: 2 additions & 3 deletions gateway/ln-gateway/src/envs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ pub const FM_GATEWAY_LISTEN_ADDR_ENV: &str = "FM_GATEWAY_LISTEN_ADDR";
/// requests to the gateway.
pub const FM_GATEWAY_API_ADDR_ENV: &str = "FM_GATEWAY_API_ADDR";

/// Environment variable that specifies the password. This is only applied if
/// there is no password set, otherwise it is ignored.
pub const FM_GATEWAY_PASSWORD_ENV: &str = "FM_GATEWAY_PASSWORD";
/// Environment variable that specifies the bcrypt password hash.
pub const FM_GATEWAY_BCRYPT_PASSWORD_HASH_ENV: &str = "FM_GATEWAY_BCRYPT_PASSWORD_HASH";

/// Environment variable that specifies that Bitcoin network that the gateway
/// should use. Must match the network of the Lightning node.
Expand Down
Loading

0 comments on commit 6e82c25

Please sign in to comment.