Skip to content
Open
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
58 changes: 58 additions & 0 deletions crates/node/core/src/args/rpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ pub struct RpcServerArgs {
#[arg(long = "http.corsdomain")]
pub http_corsdomain: Option<String>,

/// Comma separated list of virtual hostnames from which to accept HTTP requests (server
/// enforced). Accepts '*' wildcard.
#[arg(long = "http.vhosts", default_value = "localhost")]
pub http_vhosts: String,

/// Enable the WS-RPC server
#[arg(long)]
pub ws: bool,
Expand All @@ -83,6 +88,11 @@ pub struct RpcServerArgs {
#[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain")]
pub ws_allowed_origins: Option<String>,

/// Comma separated list of virtual hostnames from which to accept `WebSocket` requests (server
/// enforced). Accepts '*' wildcard.
#[arg(long = "ws.vhosts", default_value = "localhost")]
pub ws_vhosts: String,

/// Rpc Modules to be configured for the WS server
#[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default())]
pub ws_api: Option<RpcModuleSelection>,
Expand Down Expand Up @@ -394,10 +404,12 @@ impl Default for RpcServerArgs {
http_disable_compression: false,
http_api: None,
http_corsdomain: None,
http_vhosts: "localhost".to_string(),
ws: false,
ws_addr: Ipv4Addr::LOCALHOST.into(),
ws_port: constants::DEFAULT_WS_RPC_PORT,
ws_allowed_origins: None,
ws_vhosts: "localhost".to_string(),
ws_api: None,
ipcdisable: false,
ipcpath: constants::DEFAULT_IPC_ENDPOINT.to_string(),
Expand Down Expand Up @@ -542,4 +554,50 @@ mod tests {
let expected = 1_000_000_000_000_000_000u128;
assert_eq!(args.rpc_tx_fee_cap, expected); // 1 ETH default cap
}

#[test]
fn test_http_vhosts_default() {
let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
assert_eq!(args.http_vhosts, "localhost");
}

#[test]
fn test_http_vhosts_custom() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--http.vhosts",
"localhost,*.example.com",
])
.args;
assert_eq!(args.http_vhosts, "localhost,*.example.com");
}

#[test]
fn test_ws_vhosts_default() {
let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
assert_eq!(args.ws_vhosts, "localhost");
}

#[test]
fn test_ws_vhosts_custom() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--ws.vhosts",
"localhost,*.example.com",
])
.args;
assert_eq!(args.ws_vhosts, "localhost,*.example.com");
}

#[test]
fn test_http_vhosts_wildcard() {
let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--http.vhosts", "*"]).args;
assert_eq!(args.http_vhosts, "*");
}

#[test]
fn test_ws_vhosts_wildcard() {
let args = CommandParser::<RpcServerArgs>::parse_from(["reth", "--ws.vhosts", "*"]).args;
assert_eq!(args.ws_vhosts, "*");
}
}
4 changes: 3 additions & 1 deletion crates/rpc/rpc-builder/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ impl RethRpcServerConfig for RpcServerArgs {
.with_http_address(socket_address)
.with_http(self.http_ws_server_builder())
.with_http_cors(self.http_corsdomain.clone())
.with_http_vhosts(Some(self.http_vhosts.clone()))
.with_http_disable_compression(self.http_disable_compression);
}

Expand All @@ -212,7 +213,8 @@ impl RethRpcServerConfig for RpcServerArgs {
config = config
.with_ws_address(socket_address)
.with_ws(self.http_ws_server_builder())
.with_ws_cors(self.ws_allowed_origins.clone());
.with_ws_cors(self.ws_allowed_origins.clone())
.with_ws_vhosts(Some(self.ws_vhosts.clone()));
}

if self.is_ipc_enabled() {
Expand Down
37 changes: 35 additions & 2 deletions crates/rpc/rpc-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ use reth_rpc_eth_api::{
RpcNodeCore, RpcReceipt, RpcTransaction, RpcTxReq,
};
use reth_rpc_eth_types::{receipt::EthReceiptConverter, EthConfig, EthSubscriptionIdProvider};
use reth_rpc_layer::{AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret};
use reth_rpc_layer::{
AllowedVHosts, AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret, VHostLayer,
};
use reth_storage_api::{
AccountReader, BlockReader, ChangeSetReader, FullRpcProvider, ProviderBlock,
StateProviderFactory,
Expand Down Expand Up @@ -1022,6 +1024,8 @@ pub struct RpcServerConfig<RpcMiddleware = Identity> {
http_server_config: Option<ServerConfigBuilder>,
/// Allowed CORS Domains for http
http_cors_domains: Option<String>,
/// Allowed virtual hostnames for http
http_vhosts: Option<String>,
/// Address where to bind the http server to
http_addr: Option<SocketAddr>,
/// Control whether http responses should be compressed
Expand All @@ -1030,6 +1034,8 @@ pub struct RpcServerConfig<RpcMiddleware = Identity> {
ws_server_config: Option<ServerConfigBuilder>,
/// Allowed CORS Domains for ws.
ws_cors_domains: Option<String>,
/// Allowed virtual hostnames for ws
ws_vhosts: Option<String>,
/// Address where to bind the ws server to
ws_addr: Option<SocketAddr>,
/// Configs for JSON-RPC IPC server
Expand All @@ -1050,10 +1056,12 @@ impl Default for RpcServerConfig<Identity> {
Self {
http_server_config: None,
http_cors_domains: None,
http_vhosts: None,
http_addr: None,
http_disable_compression: false,
ws_server_config: None,
ws_cors_domains: None,
ws_vhosts: None,
ws_addr: None,
ipc_server_config: None,
ipc_endpoint: None,
Expand Down Expand Up @@ -1114,10 +1122,12 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
RpcServerConfig {
http_server_config: self.http_server_config,
http_cors_domains: self.http_cors_domains,
http_vhosts: self.http_vhosts,
http_addr: self.http_addr,
http_disable_compression: self.http_disable_compression,
ws_server_config: self.ws_server_config,
ws_cors_domains: self.ws_cors_domains,
ws_vhosts: self.ws_vhosts,
ws_addr: self.ws_addr,
ipc_server_config: self.ipc_server_config,
ipc_endpoint: self.ipc_endpoint,
Expand Down Expand Up @@ -1149,6 +1159,18 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
self
}

/// Configure the virtual hostnames for HTTP
pub fn with_http_vhosts(mut self, vhosts: Option<String>) -> Self {
self.http_vhosts = vhosts;
self
}

/// Configure the virtual hostnames for WS
pub fn with_ws_vhosts(mut self, vhosts: Option<String>) -> Self {
self.ws_vhosts = vhosts;
self
}

/// Configures the [`SocketAddr`] of the http server
///
/// Default is [`Ipv4Addr::LOCALHOST`] and
Expand Down Expand Up @@ -1262,6 +1284,11 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
}
}

/// Creates the [`VHostLayer`] if vhosts are configured
fn maybe_vhost_layer(vhosts: Option<String>) -> Option<VHostLayer<AllowedVHosts>> {
vhosts.map(|vhosts| VHostLayer::new(AllowedVHosts::parse(&vhosts)))
}

/// Builds and starts the configured server(s): http, ws, ipc.
///
/// If both http and ws are on the same port, they are combined into one server.
Expand Down Expand Up @@ -1316,6 +1343,9 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
}
.cloned();

// Merge vhosts for same port (prefer http, fallback to ws)
let vhosts = self.http_vhosts.as_ref().or(self.ws_vhosts.as_ref()).cloned();

// we merge this into one server using the http setup
modules.config.ensure_ws_http_identical()?;

Expand All @@ -1325,6 +1355,7 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
tower::ServiceBuilder::new()
.option_layer(Self::maybe_cors_layer(cors)?)
.option_layer(Self::maybe_jwt_layer(self.jwt_secret))
.option_layer(Self::maybe_vhost_layer(vhosts))
.option_layer(Self::maybe_compression_layer(
self.http_disable_compression,
)),
Expand Down Expand Up @@ -1378,7 +1409,8 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
.set_http_middleware(
tower::ServiceBuilder::new()
.option_layer(Self::maybe_cors_layer(self.ws_cors_domains.clone())?)
.option_layer(Self::maybe_jwt_layer(self.jwt_secret)),
.option_layer(Self::maybe_jwt_layer(self.jwt_secret))
.option_layer(Self::maybe_vhost_layer(self.ws_vhosts.clone())),
)
.set_rpc_middleware(
RpcServiceBuilder::default()
Expand All @@ -1404,6 +1436,7 @@ impl<RpcMiddleware> RpcServerConfig<RpcMiddleware> {
tower::ServiceBuilder::new()
.option_layer(Self::maybe_cors_layer(self.http_cors_domains.clone())?)
.option_layer(Self::maybe_jwt_layer(self.jwt_secret))
.option_layer(Self::maybe_vhost_layer(self.http_vhosts.clone()))
.option_layer(Self::maybe_compression_layer(self.http_disable_compression)),
)
.set_rpc_middleware(
Expand Down
2 changes: 2 additions & 0 deletions crates/rpc/rpc-layer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod auth_client_layer;
mod auth_layer;
mod compression_layer;
mod jwt_validator;
mod vhost_layer;

pub use auth_layer::{AuthService, ResponseFuture};
pub use compression_layer::CompressionLayer;
Expand All @@ -25,6 +26,7 @@ pub use alloy_rpc_types_engine::{Claims, JwtError, JwtSecret};
pub use auth_client_layer::{secret_to_bearer_header, AuthClientLayer, AuthClientService};
pub use auth_layer::AuthLayer;
pub use jwt_validator::JwtAuthValidator;
pub use vhost_layer::{AllowedVHosts, VHostLayer, VHostValidator};

/// General purpose trait to validate Http Authorization headers. It's supposed to be integrated as
/// a validator trait into an [`AuthLayer`].
Expand Down
Loading
Loading