Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Cargo.lock

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

21 changes: 20 additions & 1 deletion ldk-server-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ struct Cli {
#[arg(short, long, required(true))]
api_key: String,

#[arg(
short,
long,
required(true),
help = "Path to the server's TLS certificate file (PEM format). Found at <server_storage_dir>/tls_cert.pem"
)]
tls_cert: String,

#[command(subcommand)]
command: Commands,
}
Expand Down Expand Up @@ -217,7 +225,18 @@ enum Commands {
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let client = LdkServerClient::new(cli.base_url, cli.api_key);

// Load server certificate for TLS verification
let server_cert_pem = std::fs::read(&cli.tls_cert).unwrap_or_else(|e| {
eprintln!("Failed to read server certificate file '{}': {}", cli.tls_cert, e);
std::process::exit(1);
});

let client =
LdkServerClient::new(cli.base_url, cli.api_key, &server_cert_pem).unwrap_or_else(|e| {
eprintln!("Failed to create client: {e}");
std::process::exit(1);
});

match cli.command {
Commands::GetNodeInfo => {
Expand Down
54 changes: 35 additions & 19 deletions ldk-server-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ use ldk_server_protos::endpoints::{
};
use ldk_server_protos::error::{ErrorCode, ErrorResponse};
use reqwest::header::CONTENT_TYPE;
use reqwest::Client;
use reqwest::{Certificate, Client};
use std::time::{SystemTime, UNIX_EPOCH};

const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";

/// Client to access a hosted instance of LDK Server.
///
/// The client requires the server's TLS certificate to be provided for verification.
/// This certificate can be found at `<server_storage_dir>/tls_cert.pem` after the
/// server generates it on first startup.
#[derive(Clone)]
pub struct LdkServerClient {
base_url: String,
Expand All @@ -48,9 +52,21 @@ pub struct LdkServerClient {

impl LdkServerClient {
/// Constructs a [`LdkServerClient`] using `base_url` as the ldk-server endpoint.
///
/// `base_url` should not include the scheme, e.g., `localhost:3000`.
/// `api_key` is used for HMAC-based authentication.
pub fn new(base_url: String, api_key: String) -> Self {
Self { base_url, client: Client::new(), api_key }
/// `server_cert_pem` is the server's TLS certificate in PEM format. This can be
/// found at `<server_storage_dir>/tls_cert.pem` after the server starts.
pub fn new(base_url: String, api_key: String, server_cert_pem: &[u8]) -> Result<Self, String> {
let cert = Certificate::from_pem(server_cert_pem)
.map_err(|e| format!("Failed to parse server certificate: {e}"))?;

let client = Client::builder()
.add_root_certificate(cert)
.build()
.map_err(|e| format!("Failed to build HTTP client: {e}"))?;

Ok(Self { base_url, client, api_key })
}

/// Computes the HMAC-SHA256 authentication header value.
Expand All @@ -75,7 +91,7 @@ impl LdkServerClient {
pub async fn get_node_info(
&self, request: GetNodeInfoRequest,
) -> Result<GetNodeInfoResponse, LdkServerError> {
let url = format!("http://{}/{GET_NODE_INFO_PATH}", self.base_url);
let url = format!("https://{}/{GET_NODE_INFO_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -84,7 +100,7 @@ impl LdkServerClient {
pub async fn get_balances(
&self, request: GetBalancesRequest,
) -> Result<GetBalancesResponse, LdkServerError> {
let url = format!("http://{}/{GET_BALANCES_PATH}", self.base_url);
let url = format!("https://{}/{GET_BALANCES_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -93,7 +109,7 @@ impl LdkServerClient {
pub async fn onchain_receive(
&self, request: OnchainReceiveRequest,
) -> Result<OnchainReceiveResponse, LdkServerError> {
let url = format!("http://{}/{ONCHAIN_RECEIVE_PATH}", self.base_url);
let url = format!("https://{}/{ONCHAIN_RECEIVE_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -102,7 +118,7 @@ impl LdkServerClient {
pub async fn onchain_send(
&self, request: OnchainSendRequest,
) -> Result<OnchainSendResponse, LdkServerError> {
let url = format!("http://{}/{ONCHAIN_SEND_PATH}", self.base_url);
let url = format!("https://{}/{ONCHAIN_SEND_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -111,7 +127,7 @@ impl LdkServerClient {
pub async fn bolt11_receive(
&self, request: Bolt11ReceiveRequest,
) -> Result<Bolt11ReceiveResponse, LdkServerError> {
let url = format!("http://{}/{BOLT11_RECEIVE_PATH}", self.base_url);
let url = format!("https://{}/{BOLT11_RECEIVE_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -120,7 +136,7 @@ impl LdkServerClient {
pub async fn bolt11_send(
&self, request: Bolt11SendRequest,
) -> Result<Bolt11SendResponse, LdkServerError> {
let url = format!("http://{}/{BOLT11_SEND_PATH}", self.base_url);
let url = format!("https://{}/{BOLT11_SEND_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -129,7 +145,7 @@ impl LdkServerClient {
pub async fn bolt12_receive(
&self, request: Bolt12ReceiveRequest,
) -> Result<Bolt12ReceiveResponse, LdkServerError> {
let url = format!("http://{}/{BOLT12_RECEIVE_PATH}", self.base_url);
let url = format!("https://{}/{BOLT12_RECEIVE_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -138,7 +154,7 @@ impl LdkServerClient {
pub async fn bolt12_send(
&self, request: Bolt12SendRequest,
) -> Result<Bolt12SendResponse, LdkServerError> {
let url = format!("http://{}/{BOLT12_SEND_PATH}", self.base_url);
let url = format!("https://{}/{BOLT12_SEND_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -147,7 +163,7 @@ impl LdkServerClient {
pub async fn open_channel(
&self, request: OpenChannelRequest,
) -> Result<OpenChannelResponse, LdkServerError> {
let url = format!("http://{}/{OPEN_CHANNEL_PATH}", self.base_url);
let url = format!("https://{}/{OPEN_CHANNEL_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -156,7 +172,7 @@ impl LdkServerClient {
pub async fn splice_in(
&self, request: SpliceInRequest,
) -> Result<SpliceInResponse, LdkServerError> {
let url = format!("http://{}/{SPLICE_IN_PATH}", self.base_url);
let url = format!("https://{}/{SPLICE_IN_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -165,7 +181,7 @@ impl LdkServerClient {
pub async fn splice_out(
&self, request: SpliceOutRequest,
) -> Result<SpliceOutResponse, LdkServerError> {
let url = format!("http://{}/{SPLICE_OUT_PATH}", self.base_url);
let url = format!("https://{}/{SPLICE_OUT_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -174,7 +190,7 @@ impl LdkServerClient {
pub async fn close_channel(
&self, request: CloseChannelRequest,
) -> Result<CloseChannelResponse, LdkServerError> {
let url = format!("http://{}/{CLOSE_CHANNEL_PATH}", self.base_url);
let url = format!("https://{}/{CLOSE_CHANNEL_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -183,7 +199,7 @@ impl LdkServerClient {
pub async fn force_close_channel(
&self, request: ForceCloseChannelRequest,
) -> Result<ForceCloseChannelResponse, LdkServerError> {
let url = format!("http://{}/{FORCE_CLOSE_CHANNEL_PATH}", self.base_url);
let url = format!("https://{}/{FORCE_CLOSE_CHANNEL_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -192,7 +208,7 @@ impl LdkServerClient {
pub async fn list_channels(
&self, request: ListChannelsRequest,
) -> Result<ListChannelsResponse, LdkServerError> {
let url = format!("http://{}/{LIST_CHANNELS_PATH}", self.base_url);
let url = format!("https://{}/{LIST_CHANNELS_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -201,7 +217,7 @@ impl LdkServerClient {
pub async fn list_payments(
&self, request: ListPaymentsRequest,
) -> Result<ListPaymentsResponse, LdkServerError> {
let url = format!("http://{}/{LIST_PAYMENTS_PATH}", self.base_url);
let url = format!("https://{}/{LIST_PAYMENTS_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand All @@ -210,7 +226,7 @@ impl LdkServerClient {
pub async fn update_channel_config(
&self, request: UpdateChannelConfigRequest,
) -> Result<UpdateChannelConfigResponse, LdkServerError> {
let url = format!("http://{}/{UPDATE_CHANNEL_CONFIG_PATH}", self.base_url);
let url = format!("https://{}/{UPDATE_CHANNEL_CONFIG_PATH}", self.base_url);
self.post_request(&request, &url).await
}

Expand Down
2 changes: 2 additions & 0 deletions ldk-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ hyper = { version = "1", default-features = false, features = ["server", "http1"
http-body-util = { version = "0.1", default-features = false }
hyper-util = { version = "0.1", default-features = false, features = ["server-graceful"] }
tokio = { version = "1.38.0", default-features = false, features = ["time", "signal", "rt-multi-thread"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
rcgen = { version = "0.13", default-features = false, features = ["ring"] }
prost = { version = "0.11.6", default-features = false, features = ["std"] }
ldk-server-protos = { path = "../ldk-server-protos" }
bytes = { version = "1.4.0", default-features = false }
Expand Down
5 changes: 5 additions & 0 deletions ldk-server/ldk-server-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ dir_path = "/tmp/ldk-server/" # Path for LDK and BDK data persis
level = "Debug" # Log level (Error, Warn, Info, Debug, Trace)
file_path = "/tmp/ldk-server/ldk-server.log" # Log file path

[tls]
#cert_path = "/path/to/tls.crt" # Path to TLS certificate, by default uses dir_path/tls.crt
#key_path = "/path/to/tls.key" # Path to TLS private key, by default uses dir_path/tls.key
hosts = ["example.com"] # Allowed hosts for TLS, will always include "localhost" and "127.0.0.1"

# Must set either bitcoind or esplora settings, but not both

# Bitcoin Core settings
Expand Down
34 changes: 28 additions & 6 deletions ldk-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use crate::io::persist::{
use crate::util::config::{load_config, ChainSource};
use crate::util::logger::ServerLogger;
use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto};
use crate::util::tls::get_or_generate_tls_config;
use hex::DisplayHex;
use ldk_node::config::Config;
use ldk_node::lightning::ln::channelmanager::PaymentId;
Expand Down Expand Up @@ -155,14 +156,15 @@ fn main() {
},
};

let paginated_store: Arc<dyn PaginatedKVStore> =
Arc::new(match SqliteStore::new(PathBuf::from(config_file.storage_dir_path), None, None) {
let paginated_store: Arc<dyn PaginatedKVStore> = Arc::new(
match SqliteStore::new(PathBuf::from(&config_file.storage_dir_path), None, None) {
Ok(store) => store,
Err(e) => {
error!("Failed to create SqliteStore: {e:?}");
std::process::exit(-1);
},
});
},
);

#[cfg(not(feature = "events-rabbitmq"))]
let event_publisher: Arc<dyn EventPublisher> =
Expand Down Expand Up @@ -213,6 +215,20 @@ fn main() {
let rest_svc_listener = TcpListener::bind(config_file.rest_service_addr)
.await
.expect("Failed to bind listening port");

let server_config = match get_or_generate_tls_config(
config_file.tls_config,
&config_file.storage_dir_path,
) {
Ok(config) => config,
Err(e) => {
error!("Failed to set up TLS: {e}");
std::process::exit(-1);
}
};
let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config));
info!("TLS enabled for REST service on {}", config_file.rest_service_addr);

loop {
select! {
event = event_node.next_event_async() => {
Expand Down Expand Up @@ -355,11 +371,17 @@ fn main() {
res = rest_svc_listener.accept() => {
match res {
Ok((stream, _)) => {
let io_stream = TokioIo::new(stream);
let node_service = NodeService::new(Arc::clone(&node), Arc::clone(&paginated_store), config_file.api_key.clone());
let acceptor = tls_acceptor.clone();
runtime.spawn(async move {
if let Err(err) = http1::Builder::new().serve_connection(io_stream, node_service).await {
error!("Failed to serve connection: {}", err);
match acceptor.accept(stream).await {
Ok(tls_stream) => {
let io_stream = TokioIo::new(tls_stream);
if let Err(err) = http1::Builder::new().serve_connection(io_stream, node_service).await {
error!("Failed to serve TLS connection: {err}");
}
},
Err(e) => error!("TLS handshake failed: {e}"),
}
});
},
Expand Down
Loading