refactor: move TLS utilities from server to main module

Move TLS certificate generation, reading, and related utilities from
server.rs to main.rs where they are actually used. This improves
separation of concerns by keeping application startup logic in main
and focusing server.rs on gRPC service implementation.
This commit is contained in:
Ignacio Porte 2025-09-07 19:25:29 -03:00
parent e44d422108
commit bc0668f3ab
2 changed files with 111 additions and 113 deletions

View file

@ -13,26 +13,110 @@ use home::home_dir;
use internal::*;
use lndk::lnd::{build_seed_from_lnd_node, get_lnd_client, validate_lnd_creds, LndCfg};
use lndk::offers::handler::OfferHandler;
use lndk::server::{generate_tls_creds, read_tls, LNDKServer};
use lndk::server::LNDKServer;
use lndk::{
lndkrpc, setup_logger, Cfg, LifecycleSignals, LndkOnionMessenger, DEFAULT_CONFIG_FILE_NAME,
DEFAULT_DATA_DIR, DEFAULT_LNDK_DIR, DEFAULT_LOG_FILE, DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT,
TLS_CERT_FILENAME, TLS_KEY_FILENAME,
};
use lndkrpc::offers_server::OffersServer;
use log::{error, info};
use rcgen::{generate_simple_self_signed, CertifiedKey, Error as RcgenError};
use std::error::Error;
use std::ffi::OsString;
use std::fs::create_dir_all;
use std::fmt::Display;
use std::fs::{create_dir_all, metadata, set_permissions, File};
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::Arc;
use tokio::select;
use tokio::signal::unix::SignalKind;
use tonic::transport::{Server, ServerTlsConfig};
use tonic::transport::{Identity, Server, ServerTlsConfig};
use tonic_lnd::lnrpc::GetInfoRequest;
#[macro_use]
extern crate configure_me;
/// An error that occurs when generating TLS credentials.
#[derive(Debug)]
pub enum CertificateGenFailure {
RcgenError(RcgenError),
IoError(std::io::Error),
}
impl Display for CertificateGenFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CertificateGenFailure::RcgenError(e) => {
write!(f, "Error generating TLS certificate: {e:?}")
}
CertificateGenFailure::IoError(e) => write!(f, "IO error: {e:?}"),
}
}
}
impl Error for CertificateGenFailure {}
// If a tls cert/key pair doesn't already exist, generate_tls_creds creates the tls cert/key pair
// required to secure connections to LNDK's gRPC server. By default they are stored in ~/.lndk.
pub fn generate_tls_creds(
data_dir: PathBuf,
tls_ips_string: Option<String>,
) -> Result<(), CertificateGenFailure> {
let cert_path = data_dir.join(TLS_CERT_FILENAME);
let key_path = data_dir.join(TLS_KEY_FILENAME);
let tls_ips = collect_tls_ips(tls_ips_string);
// Did we have to generate a new key? In that case we also need to regenerate the certificate.
if !key_path.exists() || !cert_path.exists() {
log::debug!("Generating fresh TLS credentials in {data_dir:?}");
let mut subject_alt_names = vec!["localhost".to_string()];
if let Some(ips) = tls_ips {
for ip in ips {
subject_alt_names.push(ip);
}
};
let CertifiedKey {
cert,
signing_key: key_pair,
} = generate_simple_self_signed(subject_alt_names)
.map_err(CertificateGenFailure::RcgenError)?;
// Create the tls files. Make sure the key is user-readable only:
let mut file = File::create(&key_path).map_err(CertificateGenFailure::IoError)?;
let mut perms = metadata(&key_path)
.map_err(CertificateGenFailure::IoError)?
.permissions();
perms.set_mode(0o600);
set_permissions(&key_path, perms).map_err(CertificateGenFailure::IoError)?;
file.write_all(key_pair.serialize_pem().as_bytes())
.map_err(CertificateGenFailure::IoError)?;
drop(file);
std::fs::write(&cert_path, cert.pem()).map_err(CertificateGenFailure::IoError)?;
};
Ok(())
}
// Read the existing tls credentials from disk.
pub fn read_tls(data_dir: PathBuf) -> Result<Identity, std::io::Error> {
let cert = std::fs::read_to_string(data_dir.join(TLS_CERT_FILENAME))?;
let key = std::fs::read_to_string(data_dir.join(TLS_KEY_FILENAME))?;
Ok(Identity::from_pem(cert, key))
}
// The user first passes in the tls ips as a comma-deliminated string into LNDK. Here we turn that
// string into a Vec.
fn collect_tls_ips(tls_ips_str: Option<String>) -> Option<Vec<String>> {
tls_ips_str.map(|tls_ips_str| tls_ips_str.split(',').map(|str| str.to_owned()).collect())
}
#[tokio::main]
async fn main() -> Result<(), ()> {
let paths = get_conf_file_paths();
@ -199,3 +283,26 @@ fn get_conf_file_paths() -> Vec<OsString> {
let paths = vec![current_dir_conf_file, default_lndk_config_path];
paths
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collect_tls_ips() {
// Test that it returns a vector of one element if only one ip is provided.
let tls_ips_str = Some("192.168.0.1".to_string());
let tls_ips = collect_tls_ips(tls_ips_str);
assert!(tls_ips.is_some());
assert!(tls_ips.as_ref().unwrap().len() == 1);
// If no ip is provided, collect_tls_ips should return None.
assert!(collect_tls_ips(None).is_none());
// If two ips are provided, a vector of length two should be returned.
let tls_ips_str = Some("192.168.0.1,192.168.0.3".to_string());
let tls_ips = collect_tls_ips(tls_ips_str);
assert!(tls_ips.is_some());
assert!(tls_ips.as_ref().unwrap().len() == 2);
}
}

View file

@ -4,7 +4,7 @@ use crate::offers::get_destination;
use crate::offers::handler::{CreateOfferParams, PayOfferParams};
use crate::offers::validate_amount;
use crate::offers::OfferError;
use crate::{lndkrpc, Bolt12InvoiceString, OfferHandler, TLS_CERT_FILENAME, TLS_KEY_FILENAME};
use crate::{lndkrpc, Bolt12InvoiceString, OfferHandler};
use bitcoin::secp256k1::PublicKey;
use lightning::blinded_path::payment::BlindedPaymentPath;
use lightning::blinded_path::{Direction, IntroductionNode};
@ -20,19 +20,11 @@ use lndkrpc::{
PayInvoiceRequest, PayInvoiceResponse, PayOfferRequest, PayOfferResponse, PaymentHash,
PaymentPaths,
};
use rcgen::{generate_simple_self_signed, CertifiedKey, Error as RcgenError};
use std::error::Error;
use std::fmt::Display;
use std::fs::{metadata, set_permissions, File};
use std::io::Write;
use std::num::NonZeroU64;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tonic::metadata::MetadataMap;
use tonic::transport::Identity;
use tonic::{Request, Response, Status};
use tonic_lnd::lnrpc::GetInfoRequest;
@ -362,84 +354,6 @@ impl AuthError {
}
}
/// An error that occurs when generating TLS credentials.
#[derive(Debug)]
pub enum CertificateGenFailure {
RcgenError(RcgenError),
IoError(std::io::Error),
}
impl Display for CertificateGenFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CertificateGenFailure::RcgenError(e) => {
write!(f, "Error generating TLS certificate: {e:?}")
}
CertificateGenFailure::IoError(e) => write!(f, "IO error: {e:?}"),
}
}
}
impl Error for CertificateGenFailure {}
// If a tls cert/key pair doesn't already exist, generate_tls_creds creates the tls cert/key pair
// required to secure connections to LNDK's gRPC server. By default they are stored in ~/.lndk.
pub fn generate_tls_creds(
data_dir: PathBuf,
tls_ips_string: Option<String>,
) -> Result<(), CertificateGenFailure> {
let cert_path = data_dir.join(TLS_CERT_FILENAME);
let key_path = data_dir.join(TLS_KEY_FILENAME);
let tls_ips = collect_tls_ips(tls_ips_string);
// Did we have to generate a new key? In that case we also need to regenerate the certificate.
if !key_path.exists() || !cert_path.exists() {
log::debug!("Generating fresh TLS credentials in {data_dir:?}");
let mut subject_alt_names = vec!["localhost".to_string()];
if let Some(ips) = tls_ips {
for ip in ips {
subject_alt_names.push(ip);
}
};
let CertifiedKey {
cert,
signing_key: key_pair,
} = generate_simple_self_signed(subject_alt_names)
.map_err(CertificateGenFailure::RcgenError)?;
// Create the tls files. Make sure the key is user-readable only:
let mut file = File::create(&key_path).map_err(CertificateGenFailure::IoError)?;
let mut perms = metadata(&key_path)
.map_err(CertificateGenFailure::IoError)?
.permissions();
perms.set_mode(0o600);
set_permissions(&key_path, perms).map_err(CertificateGenFailure::IoError)?;
file.write_all(key_pair.serialize_pem().as_bytes())
.map_err(CertificateGenFailure::IoError)?;
drop(file);
std::fs::write(&cert_path, cert.pem()).map_err(CertificateGenFailure::IoError)?;
};
Ok(())
}
// Read the existing tls credentials from disk.
pub fn read_tls(data_dir: PathBuf) -> Result<Identity, std::io::Error> {
let cert = std::fs::read_to_string(data_dir.join(TLS_CERT_FILENAME))?;
let key = std::fs::read_to_string(data_dir.join(TLS_KEY_FILENAME))?;
Ok(Identity::from_pem(cert, key))
}
// The user first passes in the tls ips as a comma-deliminated string into LNDK. Here we turn that
// string into a Vec.
fn collect_tls_ips(tls_ips_str: Option<String>) -> Option<Vec<String>> {
tls_ips_str.map(|tls_ips_str| tls_ips_str.split(',').map(|str| str.to_owned()).collect())
}
fn create_fee_limit(
fee_limit: Option<u32>,
fee_limit_percent: Option<u32>,
@ -554,26 +468,3 @@ fn convert_blinded_path(native_info: &BlindedPaymentPath) -> lndkrpc::BlindedPat
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collect_tls_ips() {
// Test that it returns a vector of one element if only one ip is provided.
let tls_ips_str = Some("192.168.0.1".to_string());
let tls_ips = collect_tls_ips(tls_ips_str);
assert!(tls_ips.is_some());
assert!(tls_ips.as_ref().unwrap().len() == 1);
// If no ip is provided, collect_tls_ips should return None.
assert!(collect_tls_ips(None).is_none());
// If two ips are provided, a vector of length two should be returned.
let tls_ips_str = Some("192.168.0.1,192.168.0.3".to_string());
let tls_ips = collect_tls_ips(tls_ips_str);
assert!(tls_ips.is_some());
assert!(tls_ips.as_ref().unwrap().len() == 2);
}
}