diff --git a/config.example.toml b/config.example.toml index 6ea6a1b6..fcacadb5 100644 --- a/config.example.toml +++ b/config.example.toml @@ -188,6 +188,13 @@ jwt_auth_fail_timeout_seconds = 300 # [signer.remote] # URL of the Web3Signer instance # url = "https://remote.signer.url" +# Path to the client certificate for client authentication +# OPTIONAL +# cert_path = "/path/to/client.crt" +# Path to the client key for client authentication +# OPTIONAL +# key_path = "/path/to/client.key" + # For Dirk signer: # [signer.dirk] # Path to the client certificate to authenticate with Dirk diff --git a/crates/cli/src/docker_init.rs b/crates/cli/src/docker_init.rs index b535e54d..e8f289c1 100644 --- a/crates/cli/src/docker_init.rs +++ b/crates/cli/src/docker_init.rs @@ -74,7 +74,9 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re // address for signer API communication let signer_port = cb_config.signer.as_ref().map(|s| s.port).unwrap_or(SIGNER_PORT_DEFAULT); let signer_server = - if let Some(SignerConfig { inner: SignerType::Remote { url }, .. }) = &cb_config.signer { + if let Some(SignerConfig { inner: SignerType::Remote { url, client_auth: _ }, .. }) = + &cb_config.signer + { url.to_string() } else { format!("http://cb_signer:{signer_port}") diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 4e8e0961..15b34108 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -2,7 +2,10 @@ use std::time::{Duration, Instant}; use alloy::primitives::Address; use eyre::WrapErr; -use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue}; +use reqwest::{ + Identity, + header::{AUTHORIZATION, HeaderMap, HeaderValue}, +}; use serde::Deserialize; use url::Url; @@ -16,6 +19,7 @@ use super::{ }; use crate::{ DEFAULT_REQUEST_TIMEOUT, + config::ClientAuthConfig, constants::SIGNER_JWT_EXPIRATION, signer::EcdsaSignature, types::{BlsPublicKey, BlsSignature, Jwt, ModuleId}, @@ -35,7 +39,12 @@ pub struct SignerClient { impl SignerClient { /// Create a new SignerClient - pub fn new(signer_server_url: Url, jwt_secret: Jwt, module_id: ModuleId) -> eyre::Result { + pub fn new( + signer_server_url: Url, + jwt_secret: Jwt, + module_id: ModuleId, + client_auth: Option, + ) -> eyre::Result { let jwt = create_jwt(&module_id, &jwt_secret)?; let mut auth_value = @@ -45,10 +54,18 @@ impl SignerClient { let mut headers = HeaderMap::new(); headers.insert(AUTHORIZATION, auth_value); - let client = reqwest::Client::builder() - .timeout(DEFAULT_REQUEST_TIMEOUT) - .default_headers(headers) - .build()?; + let mut client = + reqwest::Client::builder().timeout(DEFAULT_REQUEST_TIMEOUT).default_headers(headers); + + if let Some(ClientAuthConfig { cert_path, key_path }) = client_auth { + let cert = std::fs::read_to_string(cert_path)?; + let key = std::fs::read_to_string(key_path)?; + let buffer = format!("{cert}\n{key}"); + let identity = Identity::from_pem(buffer.as_bytes())?; + client = client.identity(identity); + } + + let client = client.build()?; Ok(Self { url: signer_server_url, diff --git a/crates/common/src/config/module.rs b/crates/common/src/config/module.rs index 02fa90da..e8e14277 100644 --- a/crates/common/src/config/module.rs +++ b/crates/common/src/config/module.rs @@ -7,6 +7,7 @@ use toml::Table; use crate::{ commit::client::SignerClient, config::{ + SignerConfig, SignerType, constants::{CONFIG_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, SIGNER_URL_ENV}, load_env_var, utils::load_file_from_env, @@ -79,6 +80,7 @@ pub fn load_commit_module_config() -> Result { chain: Chain, modules: Vec>, + signer: Option, } // load module config including the extra data (if any) @@ -101,7 +103,16 @@ pub fn load_commit_module_config() -> Result client_auth, + _ => None, + } + } else { + None + }; + + let signer_client = SignerClient::new(signer_server_url, module_jwt, module_id, client_auth)?; Ok(StartCommitModuleConfig { id: module_config.static_config.id, diff --git a/crates/common/src/config/pbs.rs b/crates/common/src/config/pbs.rs index 7bcf91e3..44d47425 100644 --- a/crates/common/src/config/pbs.rs +++ b/crates/common/src/config/pbs.rs @@ -22,7 +22,7 @@ use crate::{ commit::client::SignerClient, config::{ CONFIG_ENV, MODULE_JWT_ENV, MuxKeysLoader, PBS_MODULE_NAME, PbsMuxes, SIGNER_URL_ENV, - load_env_var, load_file_from_env, + SignerConfig, SignerType, load_env_var, load_file_from_env, }, pbs::{ DEFAULT_PBS_PORT, DEFAULT_REGISTRY_REFRESH_SECONDS, DefaultTimeout, LATE_IN_SLOT_TIME_MS, @@ -323,6 +323,7 @@ pub async fn load_pbs_custom_config() -> Result<(PbsModuleC relays: Vec, pbs: CustomPbsConfig, muxes: Option, + signer: Option, } // load module config including the extra data (if any) @@ -375,13 +376,22 @@ pub async fn load_pbs_custom_config() -> Result<(PbsModuleC let all_relays = all_relays.into_values().collect(); let signer_client = if cb_config.pbs.static_config.with_signer { - // if custom pbs requires a signer client, load jwt + // if custom pbs requires a signer client, load jwt and client auth info let module_jwt = Jwt(load_env_var(MODULE_JWT_ENV)?); let signer_server_url = load_env_var(SIGNER_URL_ENV)?.parse()?; + let client_auth = if let Some(signer) = cb_config.signer { + match signer.inner { + SignerType::Remote { url: _, client_auth } => client_auth, + _ => None, + } + } else { + None + }; Some(SignerClient::new( signer_server_url, module_jwt, ModuleId(PBS_MODULE_NAME.to_string()), + client_auth, )?) } else { None diff --git a/crates/common/src/config/signer.rs b/crates/common/src/config/signer.rs index bc1d2c45..9cb22c7b 100644 --- a/crates/common/src/config/signer.rs +++ b/crates/common/src/config/signer.rs @@ -83,6 +83,16 @@ pub struct DirkHostConfig { pub wallets: Vec, } +/// Client authentication configuration for remote signers +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub struct ClientAuthConfig { + /// Path to the client certificate + pub cert_path: PathBuf, + /// Path to the client key + pub key_path: PathBuf, +} + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] pub enum SignerType { @@ -97,6 +107,9 @@ pub enum SignerType { Remote { /// Complete URL of the base API endpoint url: Url, + /// Client authentication configuration + #[serde(flatten)] + client_auth: Option, }, /// Dirk remote signer module Dirk { diff --git a/docs/docs/get_started/configuration.md b/docs/docs/get_started/configuration.md index 74fb3f24..c9b96cc9 100644 --- a/docs/docs/get_started/configuration.md +++ b/docs/docs/get_started/configuration.md @@ -318,6 +318,15 @@ Web3Signer implements the same API as Commit-Boost, so there's no need to set up url = "https://remote.signer.url" ``` +Optionally, you can also provide a client certificate and corresponding private key if the remote signer requires client authentication: + +```toml +[signer.remote] +url = "https://remote.signer.url" +cert_path = "/path/to/client.crt" +key_path = "/path/to/client.key" +``` + #### Dirk Dirk is a distributed key management system that can be used to sign transactions. In this case the Signer module is needed as an intermediary between the modules and Dirk. The following parameters are needed: