fix(01-11): remove every shipped Fedimint gateway credential (FED-07)
Six code paths configured the Lightning gateway with a bcrypt hash committed to this repository — and one deploy path with a plaintext password literal — whenever the per-install secret was missing. Anyone holding a copy of the repo held the admin credential for every gateway that ever took a fallback. container::secrets now owns the credential end to end: ensure_gateway_credential (idempotent, delegates to ensure_one's bcrypt arm) and gateway_bcrypt_hash, which returns Err when the secret is missing/empty and when the stored value is on the KNOWN_DEFAULT_GATEWAY_HASHES denylist — so this codebase cannot hand back the compromised value even to a node already carrying it. get_app_config was widened to Result so a credential-less install cannot reach podman run at all; configure_fedimint_lnd takes the resolved hash instead of re-reading with its own fallback. The four shell paths stop generating credentials entirely (dropping the htpasswd host dependency) and skip container creation with a printed reason rather than substituting anything. Naming converges on the manifest's fedimint-gateway-hash/.pw, with legacy fedimint-gateway-password values copied forward rather than regenerated so no node loses a working unique credential. Plan 01-16 owns rotation of installs already carrying the default. Verified: cargo build clean; cargo test -p archipelago 999 passed (2 boot_reconciler timing tests failed under concurrent load, green in isolation, untouched by this diff); bash -n clean on all five scripts; the compromised literal now appears exactly once in the tree, as the denylist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5faf1a3c5f
commit
4265254700
@@ -646,18 +646,18 @@ pub(super) async fn get_app_config(
|
||||
allocator: &mut PortAllocator,
|
||||
rpc_user: &str,
|
||||
rpc_pass: &str,
|
||||
) -> (
|
||||
) -> Result<(
|
||||
Vec<String>,
|
||||
Vec<String>,
|
||||
Vec<String>,
|
||||
Option<String>,
|
||||
Option<Vec<String>>,
|
||||
) {
|
||||
)> {
|
||||
if let Some(config) = dynamic_app_config(app_id).await {
|
||||
return config;
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
match app_id {
|
||||
Ok(match app_id {
|
||||
"homeassistant" | "home-assistant" => (
|
||||
vec!["8123:8123".to_string()],
|
||||
vec!["/var/lib/archipelago/home-assistant:/config".to_string()],
|
||||
@@ -1049,10 +1049,13 @@ pub(super) async fn get_app_config(
|
||||
]),
|
||||
),
|
||||
"fedimint-gateway" => {
|
||||
let fedi_hash = read_secret(
|
||||
"fedimint-gateway-hash",
|
||||
"$2y$10$t9YjjxkiktrlYvjajB/zgOMDnSNVg4HqrbDqh47u7Jf42whNdxNqC",
|
||||
);
|
||||
// FED-07: no fallback literal. A fresh install self-provisions its
|
||||
// own credential; a node that can't obtain one fails this install
|
||||
// outright (propagated via `?`) rather than start an
|
||||
// unauthenticated/default-credentialed gateway.
|
||||
let gateway_secrets_dir = std::path::Path::new("/var/lib/archipelago/secrets");
|
||||
crate::container::secrets::ensure_gateway_credential(gateway_secrets_dir)?;
|
||||
let fedi_hash = crate::container::secrets::gateway_bcrypt_hash(gateway_secrets_dir)?;
|
||||
(
|
||||
vec!["8176:8176".to_string(), "9737:9737".to_string()],
|
||||
vec!["/var/lib/archipelago/fedimint-gateway:/data".to_string()],
|
||||
@@ -1196,5 +1199,5 @@ pub(super) async fn get_app_config(
|
||||
tracing::warn!("No catalog runtime config found for app: {} — using minimal defaults", app_id);
|
||||
(vec![], vec![], vec![], None, None)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -717,6 +717,11 @@ fn order_present_containers(package_id: &str, containers: Vec<String>) -> Vec<St
|
||||
|
||||
/// Configure Fedimint Gateway to use LND instead of LDK.
|
||||
/// Modifies ports, volumes, and command args in place when LND credentials exist.
|
||||
///
|
||||
/// `fedi_hash` is the already-resolved per-install gateway credential
|
||||
/// (`container::secrets::gateway_bcrypt_hash`) — this function does not read
|
||||
/// the secrets file itself, so there is exactly one read site and one
|
||||
/// failure point for that credential (FED-07).
|
||||
pub(super) fn configure_fedimint_lnd(
|
||||
host_ip: &str,
|
||||
ports: &mut Vec<String>,
|
||||
@@ -724,20 +729,13 @@ pub(super) fn configure_fedimint_lnd(
|
||||
custom_args: &mut Option<Vec<String>>,
|
||||
rpc_user: &str,
|
||||
rpc_pass: &str,
|
||||
fedi_hash: &str,
|
||||
) {
|
||||
let lnd_cert = "/var/lib/archipelago/lnd/tls.cert";
|
||||
let lnd_macaroon = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
if std::path::Path::new(lnd_cert).exists() && std::path::Path::new(lnd_macaroon).exists() {
|
||||
info!("LND detected with credentials — configuring gateway in lnd mode");
|
||||
|
||||
// Read bcrypt hash from secrets file, fall back to default
|
||||
let fedi_hash =
|
||||
std::fs::read_to_string("/var/lib/archipelago/secrets/fedimint-gateway-hash")
|
||||
.map(|s| s.trim().to_string())
|
||||
.unwrap_or_else(|_| {
|
||||
"$2y$10$t9YjjxkiktrlYvjajB/zgOMDnSNVg4HqrbDqh47u7Jf42whNdxNqC".to_string()
|
||||
});
|
||||
|
||||
ports.retain(|p| p != "9737:9737");
|
||||
volumes.push(format!("{}:/lnd/tls.cert:ro", lnd_cert));
|
||||
volumes.push(format!("{}:/lnd/admin.macaroon:ro", lnd_macaroon));
|
||||
@@ -748,7 +746,7 @@ pub(super) fn configure_fedimint_lnd(
|
||||
"--listen".to_string(),
|
||||
"0.0.0.0:8176".to_string(),
|
||||
"--bcrypt-password-hash".to_string(),
|
||||
fedi_hash,
|
||||
fedi_hash.to_string(),
|
||||
"--network".to_string(),
|
||||
"bitcoin".to_string(),
|
||||
"--bitcoind-url".to_string(),
|
||||
|
||||
@@ -589,11 +589,18 @@ impl RpcHandler {
|
||||
&rpc_user,
|
||||
&rpc_pass,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
};
|
||||
|
||||
// Fedimint Gateway: auto-detect LND and switch to lnd mode
|
||||
if package_id == "fedimint-gateway" && deps.has_lnd {
|
||||
// get_app_config's "fedimint-gateway" arm already called
|
||||
// ensure_gateway_credential above, so the secret is guaranteed to
|
||||
// exist here; re-reading it (rather than threading the value
|
||||
// through) keeps one canonical read site in container::secrets.
|
||||
let fedi_hash = crate::container::secrets::gateway_bcrypt_hash(
|
||||
std::path::Path::new("/var/lib/archipelago/secrets"),
|
||||
)?;
|
||||
configure_fedimint_lnd(
|
||||
&self.config.host_ip,
|
||||
&mut ports,
|
||||
@@ -601,6 +608,7 @@ impl RpcHandler {
|
||||
&mut custom_args,
|
||||
&rpc_user,
|
||||
&rpc_pass,
|
||||
&fedi_hash,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user