Skip to content

Commit de61066

Browse files
jclapisManuelBilbaoltitanb
authored
Merge sigp-audit-fixes (#348)
Co-authored-by: Manuel Iñaki Bilbao <manuel.bilbao@lambdaclass.com> Co-authored-by: ltitanb <163874448+ltitanb@users.noreply.github.com>
1 parent ff716a0 commit de61066

18 files changed

Lines changed: 297 additions & 47 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/cli/src/docker_init.rs‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ use std::{
66

77
use cb_common::{
88
config::{
9-
CommitBoostConfig, LogsSettings, ModuleKind, SignerConfig, SignerType, BUILDER_PORT_ENV,
10-
BUILDER_URLS_ENV, CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV, DIRK_CA_CERT_DEFAULT,
11-
DIRK_CA_CERT_ENV, DIRK_CERT_DEFAULT, DIRK_CERT_ENV, DIRK_DIR_SECRETS_DEFAULT,
12-
DIRK_DIR_SECRETS_ENV, DIRK_KEY_DEFAULT, DIRK_KEY_ENV, JWTS_ENV, LOGS_DIR_DEFAULT,
13-
LOGS_DIR_ENV, METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, PBS_ENDPOINT_ENV,
14-
PBS_MODULE_NAME, PROXY_DIR_DEFAULT, PROXY_DIR_ENV, PROXY_DIR_KEYS_DEFAULT,
15-
PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_DEFAULT, PROXY_DIR_SECRETS_ENV, SIGNER_DEFAULT,
16-
SIGNER_DIR_KEYS_DEFAULT, SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS_DEFAULT,
17-
SIGNER_DIR_SECRETS_ENV, SIGNER_ENDPOINT_ENV, SIGNER_KEYS_ENV, SIGNER_MODULE_NAME,
18-
SIGNER_PORT_DEFAULT, SIGNER_URL_ENV,
9+
CommitBoostConfig, LogsSettings, ModuleKind, SignerConfig, SignerType, ADMIN_JWT_ENV,
10+
BUILDER_PORT_ENV, BUILDER_URLS_ENV, CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV,
11+
DIRK_CA_CERT_DEFAULT, DIRK_CA_CERT_ENV, DIRK_CERT_DEFAULT, DIRK_CERT_ENV,
12+
DIRK_DIR_SECRETS_DEFAULT, DIRK_DIR_SECRETS_ENV, DIRK_KEY_DEFAULT, DIRK_KEY_ENV, JWTS_ENV,
13+
LOGS_DIR_DEFAULT, LOGS_DIR_ENV, METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV,
14+
PBS_ENDPOINT_ENV, PBS_MODULE_NAME, PROXY_DIR_DEFAULT, PROXY_DIR_ENV,
15+
PROXY_DIR_KEYS_DEFAULT, PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_DEFAULT,
16+
PROXY_DIR_SECRETS_ENV, SIGNER_DEFAULT, SIGNER_DIR_KEYS_DEFAULT, SIGNER_DIR_KEYS_ENV,
17+
SIGNER_DIR_SECRETS_DEFAULT, SIGNER_DIR_SECRETS_ENV, SIGNER_ENDPOINT_ENV, SIGNER_KEYS_ENV,
18+
SIGNER_MODULE_NAME, SIGNER_PORT_DEFAULT, SIGNER_URL_ENV,
1919
},
2020
pbs::{BUILDER_API_PATH, GET_STATUS_PATH},
2121
signer::{ProxyStore, SignerLoader},
@@ -333,6 +333,7 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
333333
let mut signer_envs = IndexMap::from([
334334
get_env_val(CONFIG_ENV, CONFIG_DEFAULT),
335335
get_env_same(JWTS_ENV),
336+
get_env_same(ADMIN_JWT_ENV),
336337
]);
337338

338339
// Bind the signer API to 0.0.0.0
@@ -366,6 +367,7 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
366367

367368
// write jwts to env
368369
envs.insert(JWTS_ENV.into(), format_comma_separated(&jwts));
370+
envs.insert(ADMIN_JWT_ENV.into(), random_jwt_secret());
369371

370372
// volumes
371373
let mut volumes = vec![config_volume.clone()];

‎crates/common/src/commit/constants.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature";
33
pub const GENERATE_PROXY_KEY_PATH: &str = "/signer/v1/generate_proxy_key";
44
pub const STATUS_PATH: &str = "/status";
55
pub const RELOAD_PATH: &str = "/reload";
6+
pub const REVOKE_MODULE_PATH: &str = "/revoke_jwt";

‎crates/common/src/commit/request.rs‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
collections::HashMap,
23
fmt::{self, Debug, Display},
34
str::FromStr,
45
};
@@ -9,13 +10,17 @@ use alloy::{
910
rpc::types::beacon::BlsSignature,
1011
};
1112
use derive_more::derive::From;
12-
use serde::{Deserialize, Serialize};
13+
use serde::{Deserialize, Deserializer, Serialize};
1314
use tree_hash::TreeHash;
1415
use tree_hash_derive::TreeHash;
1516

1617
use crate::{
17-
constants::COMMIT_BOOST_DOMAIN, error::BlstErrorWrapper, signature::verify_signed_message,
18-
signer::BlsPublicKey, types::Chain,
18+
config::decode_string_to_map,
19+
constants::COMMIT_BOOST_DOMAIN,
20+
error::BlstErrorWrapper,
21+
signature::verify_signed_message,
22+
signer::BlsPublicKey,
23+
types::{Chain, ModuleId},
1924
};
2025

2126
pub trait ProxyId: AsRef<[u8]> + Debug + Clone + Copy + TreeHash + Display {}
@@ -199,6 +204,31 @@ pub struct GetPubkeysResponse {
199204
pub keys: Vec<ConsensusProxyMap>,
200205
}
201206

207+
#[derive(Debug, Clone, Serialize, Deserialize)]
208+
pub struct ReloadRequest {
209+
#[serde(default, deserialize_with = "deserialize_jwt_secrets")]
210+
pub jwt_secrets: Option<HashMap<ModuleId, String>>,
211+
pub admin_secret: Option<String>,
212+
}
213+
214+
pub fn deserialize_jwt_secrets<'de, D>(
215+
deserializer: D,
216+
) -> Result<Option<HashMap<ModuleId, String>>, D::Error>
217+
where
218+
D: Deserializer<'de>,
219+
{
220+
let raw: String = Deserialize::deserialize(deserializer)?;
221+
222+
decode_string_to_map(&raw)
223+
.map(Some)
224+
.map_err(|_| serde::de::Error::custom("Invalid format".to_string()))
225+
}
226+
227+
#[derive(Debug, Clone, Serialize, Deserialize)]
228+
pub struct RevokeModuleRequest {
229+
pub module_id: ModuleId,
230+
}
231+
202232
/// Map of consensus pubkeys to proxies
203233
#[derive(Debug, Clone, Deserialize, Serialize)]
204234
pub struct ConsensusProxyMap {
@@ -289,7 +319,7 @@ mod tests {
289319

290320
let _: SignedProxyDelegationBls = serde_json::from_str(data).unwrap();
291321

292-
let data = r#"{
322+
let data = r#"{
293323
"message": {
294324
"delegator": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050",
295325
"proxy": "0x4ca9939a8311a7cab3dde201b70157285fa81a9d"

‎crates/common/src/config/constants.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub const SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT: u32 = 5 * 60;
4747

4848
/// Comma separated list module_id=jwt_secret
4949
pub const JWTS_ENV: &str = "CB_JWTS";
50+
pub const ADMIN_JWT_ENV: &str = "CB_SIGNER_ADMIN_JWT";
5051

5152
/// Path to json file with plaintext keys (testing only)
5253
pub const SIGNER_KEYS_ENV: &str = "CB_SIGNER_LOADER_FILE";

‎crates/common/src/config/signer.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ pub struct StartSignerConfig {
165165
pub store: Option<ProxyStore>,
166166
pub endpoint: SocketAddr,
167167
pub mod_signing_configs: HashMap<ModuleId, ModuleSigningConfig>,
168+
pub admin_secret: String,
168169
pub jwt_auth_fail_limit: u32,
169170
pub jwt_auth_fail_timeout_seconds: u32,
170171
pub dirk: Option<DirkConfig>,
@@ -174,7 +175,7 @@ impl StartSignerConfig {
174175
pub fn load_from_env() -> Result<Self> {
175176
let config = CommitBoostConfig::from_env_path()?;
176177

177-
let jwt_secrets = load_jwt_secrets()?;
178+
let (admin_secret, jwt_secrets) = load_jwt_secrets()?;
178179

179180
// Load the module signing configs
180181
let mod_signing_configs = load_module_signing_configs(&config, &jwt_secrets)
@@ -213,6 +214,7 @@ impl StartSignerConfig {
213214
loader: Some(loader),
214215
endpoint,
215216
mod_signing_configs,
217+
admin_secret,
216218
jwt_auth_fail_limit,
217219
jwt_auth_fail_timeout_seconds,
218220
store,
@@ -243,6 +245,7 @@ impl StartSignerConfig {
243245
chain: config.chain,
244246
endpoint,
245247
mod_signing_configs,
248+
admin_secret,
246249
jwt_auth_fail_limit,
247250
jwt_auth_fail_timeout_seconds,
248251
loader: None,

‎crates/common/src/config/utils.rs‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use eyre::{bail, Context, Result};
55
use serde::de::DeserializeOwned;
66

77
use crate::{
8-
config::{JWTS_ENV, MUXER_HTTP_MAX_LENGTH},
8+
config::{ADMIN_JWT_ENV, JWTS_ENV, MUXER_HTTP_MAX_LENGTH},
99
types::ModuleId,
1010
utils::read_chunked_body_with_max,
1111
};
@@ -29,9 +29,10 @@ pub fn load_file_from_env<T: DeserializeOwned>(env: &str) -> Result<T> {
2929
}
3030

3131
/// Loads a map of module id -> jwt secret from a json env
32-
pub fn load_jwt_secrets() -> Result<HashMap<ModuleId, String>> {
32+
pub fn load_jwt_secrets() -> Result<(String, HashMap<ModuleId, String>)> {
33+
let admin_jwt = std::env::var(ADMIN_JWT_ENV).wrap_err(format!("{ADMIN_JWT_ENV} is not set"))?;
3334
let jwt_secrets = std::env::var(JWTS_ENV).wrap_err(format!("{JWTS_ENV} is not set"))?;
34-
decode_string_to_map(&jwt_secrets)
35+
decode_string_to_map(&jwt_secrets).map(|secrets| (admin_jwt, secrets))
3536
}
3637

3738
/// Reads an HTTP response safely, erroring out if it failed or if the body is
@@ -74,7 +75,7 @@ pub fn remove_duplicate_keys(keys: Vec<BlsPublicKey>) -> Vec<BlsPublicKey> {
7475
unique_keys
7576
}
7677

77-
fn decode_string_to_map(raw: &str) -> Result<HashMap<ModuleId, String>> {
78+
pub fn decode_string_to_map(raw: &str) -> Result<HashMap<ModuleId, String>> {
7879
// trim the string and split for comma
7980
raw.trim()
8081
.split(',')

‎crates/common/src/types.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ pub struct JwtClaims {
2424
pub module: String,
2525
}
2626

27+
#[derive(Debug, Serialize, Deserialize)]
28+
pub struct JwtAdmin {
29+
pub exp: u64,
30+
pub admin: bool,
31+
}
32+
2733
#[derive(Clone, Copy, PartialEq, Eq)]
2834
pub enum Chain {
2935
Mainnet,

‎crates/common/src/utils.rs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::{
3030
config::LogsSettings,
3131
constants::SIGNER_JWT_EXPIRATION,
3232
pbs::HEADER_VERSION_VALUE,
33-
types::{Chain, Jwt, JwtClaims, ModuleId},
33+
types::{Chain, Jwt, JwtAdmin, JwtClaims, ModuleId},
3434
};
3535

3636
const MILLIS_PER_SECOND: u64 = 1_000;
@@ -405,6 +405,24 @@ pub fn validate_jwt(jwt: Jwt, secret: &str) -> eyre::Result<()> {
405405
.map_err(From::from)
406406
}
407407

408+
/// Validate an admin JWT with the given secret
409+
pub fn validate_admin_jwt(jwt: Jwt, secret: &str) -> eyre::Result<()> {
410+
let mut validation = jsonwebtoken::Validation::default();
411+
validation.leeway = 10;
412+
413+
let token = jsonwebtoken::decode::<JwtAdmin>(
414+
jwt.as_str(),
415+
&jsonwebtoken::DecodingKey::from_secret(secret.as_ref()),
416+
&validation,
417+
)?;
418+
419+
if token.claims.admin {
420+
Ok(())
421+
} else {
422+
eyre::bail!("Token is not admin")
423+
}
424+
}
425+
408426
/// Generates a random string
409427
pub fn random_jwt_secret() -> String {
410428
rand::rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect()

‎crates/signer/src/error.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ pub enum SignerModuleError {
2525
#[error("Dirk signer does not support this operation")]
2626
DirkNotSupported,
2727

28+
#[error("module id not found")]
29+
ModuleIdNotFound,
30+
2831
#[error("internal error: {0}")]
2932
Internal(String),
3033

3134
#[error("rate limited for {0} more seconds")]
3235
RateLimited(f64),
36+
37+
#[error("request error: {0}")]
38+
RequestError(String),
3339
}
3440

3541
impl IntoResponse for SignerModuleError {
@@ -48,9 +54,13 @@ impl IntoResponse for SignerModuleError {
4854
(StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string())
4955
}
5056
SignerModuleError::SignerError(err) => (StatusCode::BAD_REQUEST, err.to_string()),
57+
SignerModuleError::ModuleIdNotFound => (StatusCode::NOT_FOUND, self.to_string()),
5158
SignerModuleError::RateLimited(duration) => {
5259
(StatusCode::TOO_MANY_REQUESTS, format!("rate limited for {duration:?}"))
5360
}
61+
SignerModuleError::RequestError(err) => {
62+
(StatusCode::BAD_REQUEST, format!("bad request: {err}"))
63+
}
5464
}
5565
.into_response()
5666
}

0 commit comments

Comments
 (0)