fix(install): generate bitcoin RPC password before orchestrator install
Bitcoin containers were exiting in ms after start because the orchestrator install path skipped the credential-materialisation step the legacy path did. resolve_secret_env then failed to read /var/lib/archipelago/secrets/bitcoin-rpc-password, the container started with no password, and bitcoind crashed before logs were useful. Two changes: 1. install.rs — call bitcoin_rpc_credentials() for bitcoin/bitcoin-core/ bitcoin-knots before any install branch runs. The function generates + persists on first call (OnceCell-cached), so this is idempotent. 2. manifest.rs::resolve_secret_env — return ManifestError::Invalid when a resolved secret trims to empty, instead of silently producing `KEY=` env vars that crash auth. Adds a unit test for the empty-secret rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f9e34fd0c6
commit
27ff1d5b52
@ -115,6 +115,13 @@ impl RpcHandler {
|
|||||||
check_bitcoin_implementation_conflict(package_id).await?;
|
check_bitcoin_implementation_conflict(package_id).await?;
|
||||||
let repaired_bitcoin_conf =
|
let repaired_bitcoin_conf =
|
||||||
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
|
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
|
||||||
|
// Materialise the RPC password file before any install path
|
||||||
|
// runs. The orchestrator path resolves secret_env from
|
||||||
|
// /var/lib/archipelago/secrets/bitcoin-rpc-password at start
|
||||||
|
// time; if the file is missing, bitcoind exits within ms.
|
||||||
|
// bitcoin_rpc_credentials() generates + persists on first
|
||||||
|
// call (OnceCell-cached), so this is idempotent.
|
||||||
|
let _ = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
|
||||||
ensure_bitcoin_rpc_bindings().await?
|
ensure_bitcoin_rpc_bindings().await?
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|||||||
@ -619,6 +619,14 @@ impl ContainerConfig {
|
|||||||
let mut out = Vec::with_capacity(self.secret_env.len());
|
let mut out = Vec::with_capacity(self.secret_env.len());
|
||||||
for e in &self.secret_env {
|
for e in &self.secret_env {
|
||||||
let v = provider.read(&e.secret_file)?;
|
let v = provider.read(&e.secret_file)?;
|
||||||
|
// An empty secret produces e.g. `-rpcpassword=` and crashes
|
||||||
|
// the container on auth before logs are useful. Fail loud.
|
||||||
|
if v.trim().is_empty() {
|
||||||
|
return Err(ManifestError::Invalid(format!(
|
||||||
|
"secret_env {} resolved to empty value (file: {})",
|
||||||
|
e.key, e.secret_file
|
||||||
|
)));
|
||||||
|
}
|
||||||
out.push(format!("{}={}", e.key, v));
|
out.push(format!("{}={}", e.key, v));
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@ -1051,6 +1059,36 @@ app:
|
|||||||
assert_eq!(out[1], "FM_GATEWAY_PASSWORD=supersecret2");
|
assert_eq!(out[1], "FM_GATEWAY_PASSWORD=supersecret2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_secret_env_rejects_empty_value() {
|
||||||
|
let c = ContainerConfig {
|
||||||
|
image: Some("x:latest".to_string()),
|
||||||
|
image_signature: None,
|
||||||
|
pull_policy: "if-not-present".to_string(),
|
||||||
|
build: None,
|
||||||
|
network: None,
|
||||||
|
custom_args: vec![],
|
||||||
|
entrypoint: None,
|
||||||
|
derived_env: vec![],
|
||||||
|
secret_env: vec![SecretEnv {
|
||||||
|
key: "BITCOIN_RPC_PASS".to_string(),
|
||||||
|
secret_file: "bitcoin-rpc-password".to_string(),
|
||||||
|
}],
|
||||||
|
data_uid: None,
|
||||||
|
};
|
||||||
|
let p = MapSecretsProvider {
|
||||||
|
data: HashMap::from([("bitcoin-rpc-password".to_string(), " \n".to_string())]),
|
||||||
|
};
|
||||||
|
let err = c.resolve_secret_env(&p).unwrap_err();
|
||||||
|
match err {
|
||||||
|
ManifestError::Invalid(msg) => assert!(
|
||||||
|
msg.contains("BITCOIN_RPC_PASS") && msg.contains("bitcoin-rpc-password"),
|
||||||
|
"msg should name the env key + file: {msg}"
|
||||||
|
),
|
||||||
|
other => panic!("expected Invalid, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_every_real_manifest() {
|
fn parse_every_real_manifest() {
|
||||||
let app_manifests = list_repo_manifests();
|
let app_manifests = list_repo_manifests();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user