fix(lnd): read admin macaroon via sudo fallback
LND's admin.macaroon is owned by a rootless-podman subordinate UID (typically 100000) with mode 640. The archipelago server runs as UID 1000 and cannot read the file directly, which caused every dashboard LND RPC (getinfo, connect-info, export-channel-backup) and lnd_client to fail with "Failed to read LND admin macaroon". Add a read_lnd_admin_macaroon() helper that first tries a direct read (for operators who have relaxed permissions) then falls back to `sudo -n cat`, mirroring the pattern already used for Tor hidden service hostnames in handle_lnd_connect_info. Centralise the canonical macaroon path as LND_ADMIN_MACAROON_PATH and route all four callers through the helper. Verified on .228: GET /lnd-connect-info now returns 200 with cert, macaroon, and tor_onion fields. Dashboard QR/connect-string UI unblocked.
This commit is contained in:
parent
4b8ef0a098
commit
be96002372
@ -3,7 +3,7 @@ use anyhow::{Context, Result};
|
||||
use base64::Engine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{LndAmount, LndBalanceResponse};
|
||||
use super::{LndAmount, LndBalanceResponse, read_lnd_admin_macaroon};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct LndInfo {
|
||||
@ -34,11 +34,7 @@ struct LndChannelBalanceResponse {
|
||||
|
||||
impl RpcHandler {
|
||||
pub(in crate::api::rpc) async fn handle_lnd_getinfo(&self) -> Result<serde_json::Value> {
|
||||
let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
|
||||
let macaroon_bytes = tokio::fs::read(macaroon_path)
|
||||
.await
|
||||
.context("Failed to read LND admin macaroon — is LND installed?")?;
|
||||
let macaroon_bytes = read_lnd_admin_macaroon().await?;
|
||||
let macaroon_hex = hex::encode(&macaroon_bytes);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
@ -114,7 +110,6 @@ impl RpcHandler {
|
||||
/// for building lndconnect:// URIs in the frontend.
|
||||
pub(crate) async fn handle_lnd_connect_info(&self) -> Result<serde_json::Value> {
|
||||
let cert_path = "/var/lib/archipelago/lnd/tls.cert";
|
||||
let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
|
||||
// Read and encode TLS cert (PEM -> DER -> base64url)
|
||||
let cert_pem = tokio::fs::read_to_string(cert_path)
|
||||
@ -130,9 +125,7 @@ impl RpcHandler {
|
||||
let cert_b64url = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cert_der);
|
||||
|
||||
// Read and encode macaroon (binary -> base64url)
|
||||
let macaroon_bytes = tokio::fs::read(macaroon_path)
|
||||
.await
|
||||
.context("Failed to read LND admin macaroon")?;
|
||||
let macaroon_bytes = read_lnd_admin_macaroon().await?;
|
||||
let macaroon_b64url =
|
||||
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&macaroon_bytes);
|
||||
|
||||
@ -183,10 +176,7 @@ impl RpcHandler {
|
||||
pub(in crate::api::rpc) async fn handle_lnd_export_channel_backup(
|
||||
&self,
|
||||
) -> Result<serde_json::Value> {
|
||||
let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
let macaroon_bytes = tokio::fs::read(macaroon_path)
|
||||
.await
|
||||
.context("Failed to read LND admin macaroon")?;
|
||||
let macaroon_bytes = read_lnd_admin_macaroon().await?;
|
||||
let macaroon_hex = hex::encode(&macaroon_bytes);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
|
||||
@ -4,7 +4,11 @@ mod payments;
|
||||
mod wallet;
|
||||
|
||||
use crate::api::rpc::RpcHandler;
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
/// Canonical on-host path for LND's admin macaroon.
|
||||
pub(crate) const LND_ADMIN_MACAROON_PATH: &str =
|
||||
"/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
|
||||
// Shared LND response types used by multiple submodules
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
@ -17,15 +21,45 @@ pub(super) struct LndAmount {
|
||||
pub sat: Option<String>,
|
||||
}
|
||||
|
||||
/// Read LND's admin macaroon from disk.
|
||||
///
|
||||
/// The macaroon lives inside LND's container data dir and is owned by a
|
||||
/// rootless-podman subordinate UID (typically 100000), mode 640. The
|
||||
/// archipelago server runs as UID 1000 and therefore cannot read it
|
||||
/// directly. We first try a plain read (works if an operator has relaxed
|
||||
/// permissions), then fall back to `sudo cat` — mirroring the pattern
|
||||
/// already used for Tor hidden-service hostnames.
|
||||
pub(crate) async fn read_lnd_admin_macaroon() -> Result<Vec<u8>> {
|
||||
match tokio::fs::read(LND_ADMIN_MACAROON_PATH).await {
|
||||
Ok(bytes) => Ok(bytes),
|
||||
Err(direct_err) => {
|
||||
let output = tokio::process::Command::new("sudo")
|
||||
.args(["-n", "cat", LND_ADMIN_MACAROON_PATH])
|
||||
.output()
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to read LND admin macaroon (direct: {direct_err}); sudo fallback also failed"
|
||||
)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(anyhow!(
|
||||
"Failed to read LND admin macaroon — is LND installed? (direct: {direct_err}; sudo: {})",
|
||||
stderr.trim()
|
||||
));
|
||||
}
|
||||
Ok(output.stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcHandler {
|
||||
/// Helper: create an authenticated LND REST client.
|
||||
/// Returns an HTTP client configured for LND's self-signed TLS and the
|
||||
/// hex-encoded admin macaroon for request headers.
|
||||
pub(crate) async fn lnd_client(&self) -> Result<(reqwest::Client, String)> {
|
||||
let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
let macaroon_bytes = tokio::fs::read(macaroon_path)
|
||||
.await
|
||||
.context("Failed to read LND admin macaroon — is LND installed?")?;
|
||||
let macaroon_bytes = read_lnd_admin_macaroon().await?;
|
||||
let macaroon_hex = hex::encode(&macaroon_bytes);
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user