Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
17 changes: 17 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ typenum = "1.17.0"
unicode-normalization = "0.1.24"
url = { version = "2.5.0", features = ["serde"] }
uuid = { version = "1.8.0", features = ["fast-rng", "serde", "v4"] }
jsonwebtoken = { version = "9.3.1", default-features = false }
1 change: 1 addition & 0 deletions bin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod prelude {
load_builder_module_config, load_commit_module_config, load_pbs_config,
load_pbs_custom_config, LogsSettings, StartCommitModuleConfig, PBS_MODULE_NAME,
},
constants::SIGNER_JWT_EXPIRATION,
pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent},
signer::{BlsPublicKey, BlsSignature, EcdsaSignature},
types::Chain,
Expand Down
4 changes: 3 additions & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ url = "http://0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09f
# - Dirk: a remote Dirk instance
# - Local: a local Signer module
# More details on the docs (https://commit-boost.github.io/commit-boost-client/get_started/configuration/#signer-module)
# [signer]
# Docker image to use for the Signer module.
# OPTIONAL, DEFAULT: ghcr.io/commit-boost/signer:latest
# [signer]
# docker_image = "ghcr.io/commit-boost/signer:latest"
# Secret to use for JWT authentication with the Signer module. This can also be set in the `CB_SIGNER_JWT_SECRET` env var.
# jwt_secret = "secret"
# For Remote signer:
# [signer.remote]
# URL of the Web3Signer instance
Expand Down
65 changes: 33 additions & 32 deletions crates/cli/src/docker_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,27 @@ use std::{

use cb_common::{
config::{
CommitBoostConfig, LogsSettings, ModuleKind, SignerConfig, SignerType, BUILDER_PORT_ENV,
BUILDER_URLS_ENV, CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV, DIRK_CA_CERT_DEFAULT,
DIRK_CA_CERT_ENV, DIRK_CERT_DEFAULT, DIRK_CERT_ENV, DIRK_DIR_SECRETS_DEFAULT,
DIRK_DIR_SECRETS_ENV, DIRK_KEY_DEFAULT, DIRK_KEY_ENV, JWTS_ENV, LOGS_DIR_DEFAULT,
LOGS_DIR_ENV, METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, PBS_ENDPOINT_ENV,
PBS_MODULE_NAME, PROXY_DIR_DEFAULT, PROXY_DIR_ENV, PROXY_DIR_KEYS_DEFAULT,
PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_DEFAULT, PROXY_DIR_SECRETS_ENV, SIGNER_DEFAULT,
SIGNER_DIR_KEYS_DEFAULT, SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS_DEFAULT,
SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS_ENV, SIGNER_MODULE_NAME, SIGNER_PORT_ENV,
SIGNER_URL_ENV,
load_optional_env_var, CommitBoostConfig, LogsSettings, ModuleKind, SignerConfig,
SignerType, BUILDER_PORT_ENV, BUILDER_URLS_ENV, CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV,
DIRK_CA_CERT_DEFAULT, DIRK_CA_CERT_ENV, DIRK_CERT_DEFAULT, DIRK_CERT_ENV,
DIRK_DIR_SECRETS_DEFAULT, DIRK_DIR_SECRETS_ENV, DIRK_KEY_DEFAULT, DIRK_KEY_ENV,
LOGS_DIR_DEFAULT, LOGS_DIR_ENV, METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV,
PBS_ENDPOINT_ENV, PBS_MODULE_NAME, PROXY_DIR_DEFAULT, PROXY_DIR_ENV,
PROXY_DIR_KEYS_DEFAULT, PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_DEFAULT,
PROXY_DIR_SECRETS_ENV, SIGNER_DEFAULT, SIGNER_DIR_KEYS_DEFAULT, SIGNER_DIR_KEYS_ENV,
SIGNER_DIR_SECRETS_DEFAULT, SIGNER_DIR_SECRETS_ENV, SIGNER_JWT_SECRET_ENV, SIGNER_KEYS_ENV,
SIGNER_MODULE_NAME, SIGNER_PORT_ENV, SIGNER_URL_ENV,
},
pbs::{BUILDER_API_PATH, GET_STATUS_PATH},
signer::{ProxyStore, SignerLoader},
types::ModuleId,
utils::random_jwt,
utils::create_jwt,
};
use docker_compose_types::{
Compose, DependsCondition, DependsOnOptions, EnvFile, Environment, Healthcheck,
HealthcheckTest, MapOrEmpty, NetworkSettings, Networks, Ports, Service, Services, SingleValue,
Volumes,
};
use eyre::Result;
use eyre::{bail, Result};
use indexmap::IndexMap;

/// Name of the docker compose file
Expand Down Expand Up @@ -66,7 +65,6 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
Some(get_env_val(CHAIN_SPEC_ENV, &format!("/{file_name}")))
});

let mut jwts = IndexMap::new();
// envs to write in .env file
let mut envs = IndexMap::new();
// targets to pass to prometheus
Expand All @@ -86,7 +84,20 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re

let mut warnings = Vec::new();

let mut needs_signer_module = cb_config.pbs.with_signer;
let needs_signer_module = cb_config.pbs.with_signer ||
cb_config.modules.as_ref().is_some_and(|modules| {
modules.iter().any(|module| matches!(module.kind, ModuleKind::Commit))
});

let secret = load_optional_env_var(SIGNER_JWT_SECRET_ENV)
.or(cb_config.signer.as_ref().and_then(|signer_config| signer_config.jwt_secret.clone()));
let jwt_secret = match (needs_signer_module, secret) {
(true, Some(secret)) => secret,
(true, None) => {
bail!("No JWT secret provided for the signer. Set it in the environment or config")
}
(false, _) => String::new(), // not needed
};

// setup modules
if let Some(modules_config) = cb_config.modules {
Expand All @@ -97,9 +108,8 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
// a commit module needs a JWT and access to the signer network
ModuleKind::Commit => {
let mut ports = vec![];
needs_signer_module = true;

let jwt = random_jwt();
let jwt = create_jwt(&module.id, &jwt_secret)?;
let jwt_name = format!("CB_JWT_{}", module.id.to_uppercase());

// module ids are assumed unique, so envs dont override each other
Expand Down Expand Up @@ -146,8 +156,7 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
module_envs.insert(key, val);
}

envs.insert(jwt_name.clone(), jwt.clone());
jwts.insert(module.id.clone(), jwt);
envs.insert(jwt_name.clone(), jwt.to_string());

// networks
let module_networks = vec![SIGNER_NETWORK.to_owned()];
Expand Down Expand Up @@ -326,11 +335,13 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
panic!("Signer module required but no signer config provided");
};

envs.insert(SIGNER_JWT_SECRET_ENV.to_string(), jwt_secret.clone());

match signer_config.inner {
SignerType::Local { loader, store } => {
let mut signer_envs = IndexMap::from([
get_env_same(SIGNER_JWT_SECRET_ENV),
get_env_val(CONFIG_ENV, CONFIG_DEFAULT),
get_env_same(JWTS_ENV),
get_env_uval(SIGNER_PORT_ENV, signer_port as u64),
]);

Expand All @@ -355,9 +366,6 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
signer_envs.insert(key, val);
}

// write jwts to env
envs.insert(JWTS_ENV.into(), format_comma_separated(&jwts));

// volumes
let mut volumes = vec![config_volume.clone()];
volumes.extend(chain_spec_volume.clone());
Expand Down Expand Up @@ -455,8 +463,8 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
SignerType::Dirk { cert_path, key_path, secrets_path, ca_cert_path, store, .. } => {
let mut signer_envs = IndexMap::from([
get_env_val(CONFIG_ENV, CONFIG_DEFAULT),
get_env_same(JWTS_ENV),
get_env_uval(SIGNER_PORT_ENV, signer_port as u64),
get_env_same(SIGNER_JWT_SECRET_ENV),
get_env_val(DIRK_CERT_ENV, DIRK_CERT_DEFAULT),
get_env_val(DIRK_KEY_ENV, DIRK_KEY_DEFAULT),
get_env_val(DIRK_DIR_SECRETS_ENV, DIRK_DIR_SECRETS_DEFAULT),
Expand All @@ -483,9 +491,6 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
signer_envs.insert(key, val);
}

// write jwts to env
envs.insert(JWTS_ENV.into(), format_comma_separated(&jwts));

// volumes
let mut volumes = vec![
config_volume.clone(),
Expand Down Expand Up @@ -632,6 +637,7 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
}

/// FOO=${FOO}
#[allow(dead_code)]
fn get_env_same(k: &str) -> (String, Option<SingleValue>) {
get_env_interp(k, k)
}
Expand Down Expand Up @@ -664,8 +670,3 @@ fn get_log_volume(config: &LogsSettings, module_id: &str) -> Option<Volumes> {
))
})
}

/// Formats as a comma separated list of key=value
fn format_comma_separated(map: &IndexMap<ModuleId, String>) -> String {
map.iter().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(",")
}
1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ tree_hash.workspace = true
tree_hash_derive.workspace = true
unicode-normalization.workspace = true
url.workspace = true
jsonwebtoken.workspace = true
33 changes: 32 additions & 1 deletion crates/common/src/commit/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use serde::Deserialize;
use url::Url;

use super::{
constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH},
constants::{
GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REFRESH_TOKEN_PATH, REQUEST_SIGNATURE_PATH,
},
error::SignerClientError,
request::{
EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, ProxyId, SignConsensusRequest,
Expand Down Expand Up @@ -151,4 +153,33 @@ impl SignerClient {

Ok(ecdsa_signed_proxy_delegation)
}

pub async fn refresh_token(&mut self) -> Result<(), SignerClientError> {
let url = self.url.join(REFRESH_TOKEN_PATH)?;
let res = self.client.get(url).send().await?;

let status = res.status();
let response_bytes = res.bytes().await?;

if !status.is_success() {
return Err(SignerClientError::FailedRequest {
status: status.as_u16(),
error_msg: String::from_utf8_lossy(&response_bytes).into_owned(),
});
}

let new_jwt: String = serde_json::from_slice(&response_bytes)?;
let mut headers = HeaderMap::new();
let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", new_jwt))
.map_err(SignerClientError::InvalidHeader)?;
auth_value.set_sensitive(true);
headers.insert(AUTHORIZATION, auth_value);

self.client = reqwest::Client::builder()
.timeout(DEFAULT_REQUEST_TIMEOUT)
.default_headers(headers)
.build()?;

Ok(())
}
}
1 change: 1 addition & 0 deletions crates/common/src/commit/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature";
pub const GENERATE_PROXY_KEY_PATH: &str = "/signer/v1/generate_proxy_key";
pub const STATUS_PATH: &str = "/status";
pub const RELOAD_PATH: &str = "/reload";
pub const REFRESH_TOKEN_PATH: &str = "/refresh_token";
4 changes: 2 additions & 2 deletions crates/common/src/config/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ pub const SIGNER_MODULE_NAME: &str = "signer";
/// Where the signer module should open the server
pub const SIGNER_PORT_ENV: &str = "CB_SIGNER_PORT";

/// Comma separated list module_id=jwt_secret
pub const JWTS_ENV: &str = "CB_JWTS";
/// The JWT secret for the signer to validate the modules requests
pub const SIGNER_JWT_SECRET_ENV: &str = "CB_SIGNER_JWT_SECRET";

/// Path to json file with plaintext keys (testing only)
pub const SIGNER_KEYS_ENV: &str = "CB_SIGNER_LOADER_FILE";
Expand Down
32 changes: 20 additions & 12 deletions crates/common/src/config/signer.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
use std::path::PathBuf;
use std::{collections::HashSet, path::PathBuf};

use bimap::BiHashMap;
use eyre::{bail, OptionExt, Result};
use serde::{Deserialize, Serialize};
use tonic::transport::{Certificate, Identity};
use url::Url;

use super::{
constants::SIGNER_IMAGE_DEFAULT,
utils::{load_env_var, load_jwts},
CommitBoostConfig, SIGNER_PORT_ENV,
constants::SIGNER_IMAGE_DEFAULT, load_optional_env_var, utils::load_env_var, CommitBoostConfig,
SIGNER_JWT_SECRET_ENV, SIGNER_PORT_ENV,
};
use crate::{
config::{DIRK_CA_CERT_ENV, DIRK_CERT_ENV, DIRK_DIR_SECRETS_ENV, DIRK_KEY_ENV},
signer::{ProxyStore, SignerLoader},
types::{Chain, Jwt, ModuleId},
types::{Chain, ModuleId},
};

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand All @@ -23,6 +21,8 @@ pub struct SignerConfig {
/// Docker image of the module
#[serde(default = "default_signer")]
pub docker_image: String,
/// Secret used to sign JWTs
pub jwt_secret: Option<String>,
/// Inner type-specific configuration
#[serde(flatten)]
pub inner: SignerType,
Expand Down Expand Up @@ -90,27 +90,34 @@ pub struct StartSignerConfig {
pub loader: Option<SignerLoader>,
pub store: Option<ProxyStore>,
pub server_port: u16,
pub jwts: BiHashMap<ModuleId, Jwt>,
pub modules: HashSet<ModuleId>,
pub dirk: Option<DirkConfig>,
pub jwt_secret: String,
}

impl StartSignerConfig {
pub fn load_from_env() -> Result<Self> {
let config = CommitBoostConfig::from_env_path()?;

let jwts = load_jwts()?;
let modules =
config.modules.unwrap_or(Vec::new()).iter().map(|module| module.id.clone()).collect();
let server_port = load_env_var(SIGNER_PORT_ENV)?.parse()?;

let signer = config.signer.ok_or_eyre("Signer config is missing")?.inner;
let signer = config.signer.ok_or_eyre("Signer config is missing")?;
let jwt_secret =
load_optional_env_var(SIGNER_JWT_SECRET_ENV).or(signer.jwt_secret).ok_or_eyre(
"No JWT secret provided for the signer. Set it in the environment or config",
)?;

match signer {
match signer.inner {
SignerType::Local { loader, store, .. } => Ok(StartSignerConfig {
chain: config.chain,
loader: Some(loader),
server_port,
jwts,
modules,
store,
dirk: None,
jwt_secret,
}),

SignerType::Dirk {
Expand All @@ -136,7 +143,7 @@ impl StartSignerConfig {
Ok(StartSignerConfig {
chain: config.chain,
server_port,
jwts,
modules,
loader: None,
store,
dirk: Some(DirkConfig {
Expand All @@ -153,6 +160,7 @@ impl StartSignerConfig {
None => None,
},
}),
jwt_secret,
})
}

Expand Down
Loading
Loading