diff --git a/bin/reth/src/args/rpc_server_args.rs b/bin/reth/src/args/rpc_server_args.rs index 567e00d1f2a5..175c73b1ded0 100644 --- a/bin/reth/src/args/rpc_server_args.rs +++ b/bin/reth/src/args/rpc_server_args.rs @@ -116,10 +116,23 @@ pub struct RpcServerArgs { #[arg(long = "authrpc.port", default_value_t = constants::DEFAULT_AUTH_PORT)] pub auth_port: u16, - /// Path to a JWT secret to use for authenticated RPC endpoints + /// Path to a JWT secret to use for the authenticated engine-API RPC server. + /// + /// This will enforce JWT authentication for all requests coming from the consensus layer. + /// + /// If no path is provided, a secret will be generated and stored in the datadir under + /// `//jwt.hex`. For mainnet this would be `~/.reth/mainnet/jwt.hex` by default. #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false)] pub auth_jwtsecret: Option, + /// Hex encoded JWT secret to authenticate the regular RPC server(s), see `--http.api` and + /// `--ws.api`. + /// + /// This is __not__ used for the authenticated engine-API RPC server, see + /// `--authrpc.jwtsecret`. + #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false)] + pub rpc_jwtsecret: Option, + /// Set the maximum RPC request payload size for both HTTP and WS in megabytes. #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)] pub rpc_max_request_size: u32, @@ -397,7 +410,7 @@ impl RethRpcConfig for RpcServerArgs { } fn rpc_server_config(&self) -> RpcServerConfig { - let mut config = RpcServerConfig::default(); + let mut config = RpcServerConfig::default().with_jwt_secret(self.rpc_secret_key()); if self.http { let socket_address = SocketAddr::new(self.http_addr, self.http_port); @@ -427,7 +440,7 @@ impl RethRpcConfig for RpcServerArgs { Ok(AuthServerConfig::builder(jwt_secret).socket_addr(address).build()) } - fn jwt_secret(&self, default_jwt_path: PathBuf) -> Result { + fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result { match self.auth_jwtsecret.as_ref() { Some(fpath) => { debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file"); @@ -444,6 +457,10 @@ impl RethRpcConfig for RpcServerArgs { } } } + + fn rpc_secret_key(&self) -> Option { + self.rpc_jwtsecret.clone() + } } /// clap value parser for [RpcModuleSelection]. diff --git a/bin/reth/src/cli/config.rs b/bin/reth/src/cli/config.rs index 01eb395e9f67..a567d39a2e11 100644 --- a/bin/reth/src/cli/config.rs +++ b/bin/reth/src/cli/config.rs @@ -63,7 +63,12 @@ pub trait RethRpcConfig { /// /// The `default_jwt_path` provided as an argument will be used as the default location for the /// jwt secret in case the `auth_jwtsecret` argument is not provided. - fn jwt_secret(&self, default_jwt_path: PathBuf) -> Result; + fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result; + + /// Returns the configured jwt secret key for the regular rpc servers, if any. + /// + /// Note: this is not used for the auth server (engine API). + fn rpc_secret_key(&self) -> Option; } /// A trait that provides payload builder settings. diff --git a/bin/reth/src/dirs.rs b/bin/reth/src/dirs.rs index c93e8234ba4d..c7bf9dc80398 100644 --- a/bin/reth/src/dirs.rs +++ b/bin/reth/src/dirs.rs @@ -260,26 +260,36 @@ impl ChainPath { } /// Returns the path to the db directory for this chain. + /// + /// `//db` pub fn db_path(&self) -> PathBuf { self.0.join("db").into() } /// Returns the path to the reth p2p secret key for this chain. + /// + /// `//discovery-secret` pub fn p2p_secret_path(&self) -> PathBuf { self.0.join("discovery-secret").into() } /// Returns the path to the known peers file for this chain. + /// + /// `//known-peers.json` pub fn known_peers_path(&self) -> PathBuf { self.0.join("known-peers.json").into() } /// Returns the path to the config file for this chain. + /// + /// `//reth.toml` pub fn config_path(&self) -> PathBuf { self.0.join("reth.toml").into() } /// Returns the path to the jwtsecret file for this chain. + /// + /// `//jwt.hex` pub fn jwt_path(&self) -> PathBuf { self.0.join("jwt.hex").into() } diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 6c7002cc8a7c..0dc54d31611c 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -521,7 +521,7 @@ impl NodeCommand { // extract the jwt secret from the args if possible let default_jwt_path = data_dir.jwt_path(); - let jwt_secret = self.rpc.jwt_secret(default_jwt_path)?; + let jwt_secret = self.rpc.auth_jwt_secret(default_jwt_path)?; // adjust rpc port numbers based on instance number self.adjust_instance_ports(); diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index cf6ffe875819..7108c755f038 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1266,9 +1266,9 @@ impl RpcServerConfig { self } - /// Configures the JWT secret for authentication - pub fn with_jwt_secret(mut self, secret: JwtSecret) -> Self { - self.jwt_secret = Some(secret); + /// Configures the JWT secret for authentication. + pub fn with_jwt_secret(mut self, secret: Option) -> Self { + self.jwt_secret = secret; self } @@ -1336,7 +1336,7 @@ impl RpcServerConfig { } .cloned(); - let secret = self.jwt_secret.take(); + let secret = self.jwt_secret.clone(); // we merge this into one server using the http setup self.ws_server_config.take(); @@ -1369,7 +1369,7 @@ impl RpcServerConfig { builder, ws_socket_addr, self.ws_cors_domains.take(), - self.jwt_secret.take(), + self.jwt_secret.clone(), ServerKind::WS(ws_socket_addr), metrics.clone(), ) @@ -1384,7 +1384,7 @@ impl RpcServerConfig { builder, http_socket_addr, self.http_cors_domains.take(), - self.jwt_secret.take(), + self.jwt_secret.clone(), ServerKind::Http(http_socket_addr), metrics.clone(), ) @@ -1708,14 +1708,14 @@ impl WsHttpServerKind { builder: ServerBuilder, socket_addr: SocketAddr, cors_domains: Option, - auth_secret: Option, + jwt_secret: Option, server_kind: ServerKind, metrics: RpcServerMetrics, ) -> Result<(Self, SocketAddr), RpcError> { if let Some(cors) = cors_domains.as_deref().map(cors::create_cors_layer) { let cors = cors.map_err(|err| RpcError::Custom(err.to_string()))?; - if let Some(secret) = auth_secret { + if let Some(secret) = jwt_secret { // stack cors and auth layers let middleware = tower::ServiceBuilder::new() .layer(cors) @@ -1742,7 +1742,7 @@ impl WsHttpServerKind { let server = WsHttpServerKind::WithCors(server); Ok((server, local_addr)) } - } else if let Some(secret) = auth_secret { + } else if let Some(secret) = jwt_secret { // jwt auth layered service let middleware = tower::ServiceBuilder::new() .layer(AuthLayer::new(JwtAuthValidator::new(secret.clone()))); diff --git a/crates/rpc/rpc/src/layers/jwt_secret.rs b/crates/rpc/rpc/src/layers/jwt_secret.rs index af5d92c3812f..61bb3149f10b 100644 --- a/crates/rpc/rpc/src/layers/jwt_secret.rs +++ b/crates/rpc/rpc/src/layers/jwt_secret.rs @@ -8,6 +8,7 @@ use reth_primitives::{ use serde::{Deserialize, Serialize}; use std::{ path::Path, + str::FromStr, time::{Duration, SystemTime, UNIX_EPOCH}, }; use thiserror::Error; @@ -101,15 +102,7 @@ impl JwtSecret { fs::write(fpath, hex)?; Ok(secret) } -} -impl std::fmt::Debug for JwtSecret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("JwtSecretHash").field(&"{{}}").finish() - } -} - -impl JwtSecret { /// Validates a JWT token along the following rules: /// - The JWT signature is valid. /// - The JWT is signed with the `HMAC + SHA256 (HS256)` algorithm. @@ -169,6 +162,20 @@ impl JwtSecret { } } +impl std::fmt::Debug for JwtSecret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("JwtSecretHash").field(&"{{}}").finish() + } +} + +impl FromStr for JwtSecret { + type Err = JwtError; + + fn from_str(s: &str) -> Result { + JwtSecret::from_hex(s) + } +} + /// Claims in JWT are used to represent a set of information about an entity. /// Claims are essentially key-value pairs that are encoded as JSON objects and included in the /// payload of a JWT. They are used to transmit information such as the identity of the entity, the