From d37719da022879b4e2ef7947f5c9d2187f666ae7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 9 May 2024 09:23:59 +0200 Subject: [PATCH] rpc: add option to `whitelist ips` in rate limiting (#3701) This PR adds two new CLI options to disable rate limiting for certain ip addresses and whether to trust "proxy header". After going back in forth I decided to use ip addr instead host because we don't want rely on the host header which can be spoofed but another solution is to resolve the ip addr from the socket to host name. Example: ```bash $ polkadot --rpc-rate-limit 10 --rpc-rate-limit-whitelisted-ips 127.0.0.1/8 --rpc-rate-limit-trust-proxy-headers ``` The ip addr is read from the HTTP proxy headers `Forwarded`, `X-Forwarded-For` `X-Real-IP` if `--rpc-rate-limit-trust-proxy-headers` is enabled if that is not enabled or the headers are not found then the ip address is read from the socket. //cc @BulatSaif can you test this and give some feedback on it? --- Cargo.lock | 18 ++ cumulus/test/service/src/lib.rs | 2 + polkadot/node/test/service/src/lib.rs | 2 + prdoc/pr_3701.prdoc | 11 + .../bin/node/cli/benches/block_production.rs | 2 + .../bin/node/cli/benches/transaction_pool.rs | 2 + substrate/client/cli/src/commands/run_cmd.rs | 28 ++- substrate/client/cli/src/config.rs | 14 +- substrate/client/cli/src/runner.rs | 2 + substrate/client/rpc-servers/Cargo.toml | 14 +- substrate/client/rpc-servers/src/lib.rs | 94 +++------ substrate/client/rpc-servers/src/utils.rs | 189 ++++++++++++++++++ substrate/client/service/src/config.rs | 5 + substrate/client/service/src/lib.rs | 2 + substrate/client/service/test/src/lib.rs | 2 + 15 files changed, 318 insertions(+), 69 deletions(-) create mode 100644 prdoc/pr_3701.prdoc create mode 100644 substrate/client/rpc-servers/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 32c50ea350d2..d43088704b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5585,6 +5585,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror", +] + [[package]] name = "fraction" version = "0.13.1" @@ -9051,6 +9061,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "nonzero_ext" version = "0.3.0" @@ -17302,10 +17318,12 @@ dependencies = [ name = "sc-rpc-server" version = "11.0.0" dependencies = [ + "forwarded-header-value", "futures", "governor", "http", "hyper", + "ip_network", "jsonrpsee", "log", "serde_json", diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 11aa2e5b9f35..f2a612803861 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -824,6 +824,8 @@ pub fn node_config( rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index 87fbc7c20f31..35156a3a9372 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -216,6 +216,8 @@ pub fn node_config( rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/prdoc/pr_3701.prdoc b/prdoc/pr_3701.prdoc new file mode 100644 index 000000000000..6f9fcc92ad30 --- /dev/null +++ b/prdoc/pr_3701.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: add option to whitelist peers in rpc rate limiting + +doc: + - audience: Node Operator + description: | + This PR adds two new CLI options to disable rate limiting for certain ip addresses and whether to trust "proxy headers". + +crates: [ ] diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index f60610873d8c..ef7ae4fdf263 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -86,6 +86,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index 1906ae697e90..c4488415b983 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -82,6 +82,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index 221c32affd5a..c1288b502c95 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -30,7 +30,9 @@ use crate::{ use clap::Parser; use regex::Regex; use sc_service::{ - config::{BasePath, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions}, + config::{ + BasePath, IpNetwork, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions, + }, ChainSpec, Role, }; use sc_telemetry::TelemetryEndpoints; @@ -94,6 +96,22 @@ pub struct RunCmd { #[arg(long)] pub rpc_rate_limit: Option, + /// Disable RPC rate limiting for certain ip addresses. + /// + /// Each IP address must be in CIDR notation such as `1.2.3.4/24`. + #[arg(long, num_args = 1..)] + pub rpc_rate_limit_whitelisted_ips: Vec, + + /// Trust proxy headers for disable rate limiting. + /// + /// By default the rpc server will not trust headers such `X-Real-IP`, `X-Forwarded-For` and + /// `Forwarded` and this option will make the rpc server to trust these headers. + /// + /// For instance this may be secure if the rpc server is behind a reverse proxy and that the + /// proxy always sets these headers. + #[arg(long)] + pub rpc_rate_limit_trust_proxy_headers: bool, + /// 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, @@ -439,6 +457,14 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_rate_limit) } + fn rpc_rate_limit_whitelisted_ips(&self) -> Result> { + Ok(self.rpc_rate_limit_whitelisted_ips.clone()) + } + + fn rpc_rate_limit_trust_proxy_headers(&self) -> Result { + Ok(self.rpc_rate_limit_trust_proxy_headers) + } + fn transaction_pool(&self, is_dev: bool) -> Result { Ok(self.pool_config.transaction_pool(is_dev)) } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 70a4885e5eef..783c9313121f 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -26,7 +26,7 @@ use log::warn; use names::{Generator, Name}; use sc_service::{ config::{ - BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, + BasePath, Configuration, DatabaseSource, IpNetwork, KeystoreConfig, NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, OutputFormat, PrometheusConfig, PruningMode, Role, RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, @@ -349,6 +349,16 @@ pub trait CliConfiguration: Sized { Ok(None) } + /// RPC rate limit whitelisted ip addresses. + fn rpc_rate_limit_whitelisted_ips(&self) -> Result> { + Ok(vec![]) + } + + /// RPC rate limit trust proxy headers. + fn rpc_rate_limit_trust_proxy_headers(&self) -> Result { + Ok(false) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. @@ -523,6 +533,8 @@ pub trait CliConfiguration: Sized { rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?, rpc_batch_config: self.rpc_batch_config()?, rpc_rate_limit: self.rpc_rate_limit()?, + rpc_rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips()?, + rpc_rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index 4201a0f4062f..3bf276807840 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -273,6 +273,8 @@ mod tests { rpc_port: 9944, rpc_batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index bc21b5b1582f..7837c852a1c9 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -16,14 +16,16 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +forwarded-header-value = "0.1.1" +futures = "0.3.30" +governor = "0.6.0" +http = "0.2.8" +hyper = "0.14.27" +ip_network = "0.4.1" jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } serde_json = { workspace = true, default-features = true } tokio = { version = "1.22.0", features = ["parking_lot"] } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } -tower-http = { version = "0.4.0", features = ["cors"] } tower = { version = "0.4.13", features = ["util"] } -http = "0.2.8" -hyper = "0.14.27" -futures = "0.3.30" -governor = "0.6.0" +tower-http = { version = "0.4.0", features = ["cors"] } diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index ad4b444c7ff4..ba1fcf5e3677 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -21,27 +21,28 @@ #![warn(missing_docs)] pub mod middleware; +pub mod utils; use std::{ convert::Infallible, error::Error as StdError, net::SocketAddr, num::NonZeroU32, time::Duration, }; -use http::header::HeaderValue; use hyper::{ server::conn::AddrStream, service::{make_service_fn, service_fn}, }; use jsonrpsee::{ server::{ - middleware::http::{HostFilterLayer, ProxyGetRequestLayer}, - stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, + middleware::http::ProxyGetRequestLayer, stop_channel, ws, PingConfig, StopHandle, + TowerServiceBuilder, }, Methods, RpcModule, }; use tokio::net::TcpListener; use tower::Service; -use tower_http::cors::{AllowOrigin, CorsLayer}; +use utils::{build_rpc_api, format_cors, get_proxy_ip, host_filtering, try_into_cors}; +pub use ip_network::IpNetwork; pub use jsonrpsee::{ core::{ id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, @@ -85,6 +86,10 @@ pub struct Config<'a, M: Send + Sync + 'static> { pub batch_config: BatchRequestConfig, /// Rate limit calls per minute. pub rate_limit: Option, + /// Disable rate limit for certain ips. + pub rate_limit_whitelisted_ips: Vec, + /// Trust proxy headers for rate limiting. + pub rate_limit_trust_proxy_headers: bool, } #[derive(Debug, Clone)] @@ -117,11 +122,13 @@ where tokio_handle, rpc_api, rate_limit, + rate_limit_whitelisted_ips, + rate_limit_trust_proxy_headers, } = config; let std_listener = TcpListener::bind(addrs.as_slice()).await?.into_std()?; let local_addr = std_listener.local_addr().ok(); - let host_filter = hosts_filtering(cors.is_some(), local_addr); + let host_filter = host_filtering(cors.is_some(), local_addr); let http_middleware = tower::ServiceBuilder::new() .option_layer(host_filter) @@ -160,20 +167,39 @@ where stop_handle: stop_handle.clone(), }; - let make_service = make_service_fn(move |_conn: &AddrStream| { + let make_service = make_service_fn(move |addr: &AddrStream| { let cfg = cfg.clone(); + let rate_limit_whitelisted_ips = rate_limit_whitelisted_ips.clone(); + let ip = addr.remote_addr().ip(); async move { let cfg = cfg.clone(); + let rate_limit_whitelisted_ips = rate_limit_whitelisted_ips.clone(); Ok::<_, Infallible>(service_fn(move |req| { + let proxy_ip = + if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; + + let rate_limit_cfg = if rate_limit_whitelisted_ips + .iter() + .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) + { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); + None + } else { + if !rate_limit_whitelisted_ips.is_empty() { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); + } + rate_limit + }; + let PerConnection { service_builder, metrics, tokio_handle, stop_handle, methods } = cfg.clone(); let is_websocket = ws::is_upgrade_request(&req); let transport_label = if is_websocket { "ws" } else { "http" }; - let middleware_layer = match (metrics, rate_limit) { + let middleware_layer = match (metrics, rate_limit_cfg) { (None, None) => None, (Some(metrics), None) => Some( MiddlewareLayer::new().with_metrics(Metrics::new(metrics, transport_label)), @@ -227,57 +253,3 @@ where Ok(server_handle) } - -fn hosts_filtering(enabled: bool, addr: Option) -> Option { - // If the local_addr failed, fallback to wildcard. - let port = addr.map_or("*".to_string(), |p| p.port().to_string()); - - if enabled { - // NOTE: The listening addresses are whitelisted by default. - let hosts = - [format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")]; - Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed")) - } else { - None - } -} - -fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { - let mut available_methods = rpc_api.method_names().collect::>(); - // The "rpc_methods" is defined below and we want it to be part of the reported methods. - available_methods.push("rpc_methods"); - available_methods.sort(); - - rpc_api - .register_method("rpc_methods", move |_, _| { - serde_json::json!({ - "methods": available_methods, - }) - }) - .expect("infallible all other methods have their own address space; qed"); - - rpc_api -} - -fn try_into_cors( - maybe_cors: Option<&Vec>, -) -> Result> { - if let Some(cors) = maybe_cors { - let mut list = Vec::new(); - for origin in cors { - list.push(HeaderValue::from_str(origin)?); - } - Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) - } else { - // allow all cors - Ok(CorsLayer::permissive()) - } -} - -fn format_cors(maybe_cors: Option<&Vec>) -> String { - if let Some(cors) = maybe_cors { - format!("{:?}", cors) - } else { - format!("{:?}", ["*"]) - } -} diff --git a/substrate/client/rpc-servers/src/utils.rs b/substrate/client/rpc-servers/src/utils.rs new file mode 100644 index 000000000000..d99b8e637d9d --- /dev/null +++ b/substrate/client/rpc-servers/src/utils.rs @@ -0,0 +1,189 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Substrate RPC server utils. + +use std::{ + error::Error as StdError, + net::{IpAddr, SocketAddr}, + str::FromStr, +}; + +use forwarded_header_value::ForwardedHeaderValue; +use hyper::{ + header::{HeaderName, HeaderValue}, + Request, +}; +use jsonrpsee::{server::middleware::http::HostFilterLayer, RpcModule}; +use tower_http::cors::{AllowOrigin, CorsLayer}; + +const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); +const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip"); +const FORWARDED: HeaderName = HeaderName::from_static("forwarded"); + +pub(crate) fn host_filtering(enabled: bool, addr: Option) -> Option { + // If the local_addr failed, fallback to wildcard. + let port = addr.map_or("*".to_string(), |p| p.port().to_string()); + + if enabled { + // NOTE: The listening addresses are whitelisted by default. + let hosts = + [format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")]; + Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed")) + } else { + None + } +} + +pub(crate) fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { + let mut available_methods = rpc_api.method_names().collect::>(); + // The "rpc_methods" is defined below and we want it to be part of the reported methods. + available_methods.push("rpc_methods"); + available_methods.sort(); + + rpc_api + .register_method("rpc_methods", move |_, _| { + serde_json::json!({ + "methods": available_methods, + }) + }) + .expect("infallible all other methods have their own address space; qed"); + + rpc_api +} + +pub(crate) fn try_into_cors( + maybe_cors: Option<&Vec>, +) -> Result> { + if let Some(cors) = maybe_cors { + let mut list = Vec::new(); + for origin in cors { + list.push(HeaderValue::from_str(origin)?); + } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) + } else { + // allow all cors + Ok(CorsLayer::permissive()) + } +} + +pub(crate) fn format_cors(maybe_cors: Option<&Vec>) -> String { + if let Some(cors) = maybe_cors { + format!("{:?}", cors) + } else { + format!("{:?}", ["*"]) + } +} + +/// Extracts the IP addr from the HTTP request. +/// +/// It is extracted in the following order: +/// 1. `Forwarded` header. +/// 2. `X-Forwarded-For` header. +/// 3. `X-Real-Ip`. +pub(crate) fn get_proxy_ip(req: &Request) -> Option { + if let Some(ip) = req + .headers() + .get(&FORWARDED) + .and_then(|v| v.to_str().ok()) + .and_then(|v| ForwardedHeaderValue::from_forwarded(v).ok()) + .and_then(|v| v.remotest_forwarded_for_ip()) + { + return Some(ip); + } + + if let Some(ip) = req + .headers() + .get(&X_FORWARDED_FOR) + .and_then(|v| v.to_str().ok()) + .and_then(|v| ForwardedHeaderValue::from_x_forwarded_for(v).ok()) + .and_then(|v| v.remotest_forwarded_for_ip()) + { + return Some(ip); + } + + if let Some(ip) = req + .headers() + .get(&X_REAL_IP) + .and_then(|v| v.to_str().ok()) + .and_then(|v| IpAddr::from_str(v).ok()) + { + return Some(ip); + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + use hyper::header::HeaderValue; + + fn request() -> hyper::Request { + hyper::Request::builder().body(hyper::Body::empty()).unwrap() + } + + #[test] + fn empty_works() { + let req = request(); + let host = get_proxy_ip(&req); + assert!(host.is_none()) + } + + #[test] + fn host_from_x_real_ip() { + let mut req = request(); + + req.headers_mut().insert(&X_REAL_IP, HeaderValue::from_static("127.0.0.1")); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("127.0.0.1").unwrap()), ip); + } + + #[test] + fn ip_from_forwarded_works() { + let mut req = request(); + + req.headers_mut().insert( + &FORWARDED, + HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43;host=example.com"), + ); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("192.0.2.60").unwrap()), ip); + } + + #[test] + fn ip_from_forwarded_multiple() { + let mut req = request(); + + req.headers_mut().append(&FORWARDED, HeaderValue::from_static("for=127.0.0.1")); + req.headers_mut().append(&FORWARDED, HeaderValue::from_static("for=192.0.2.60")); + req.headers_mut().append(&FORWARDED, HeaderValue::from_static("for=192.0.2.61")); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("127.0.0.1").unwrap()), ip); + } + + #[test] + fn ip_from_x_forwarded_works() { + let mut req = request(); + + req.headers_mut() + .insert(&X_FORWARDED_FOR, HeaderValue::from_static("127.0.0.1,192.0.2.60,0.0.0.1")); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("127.0.0.1").unwrap()), ip); + } +} diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index 59e307d7f93b..187e18aa3cac 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -34,6 +34,7 @@ pub use sc_network::{ }, Multiaddr, }; +pub use sc_rpc_server::IpNetwork; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -108,6 +109,10 @@ pub struct Configuration { pub rpc_batch_config: RpcBatchRequestConfig, /// RPC rate limit per minute. pub rpc_rate_limit: Option, + /// RPC rate limit whitelisted ip addresses. + pub rpc_rate_limit_whitelisted_ips: Vec, + /// RPC rate limit trust proxy headers. + pub rpc_rate_limit_trust_proxy_headers: bool, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index d0f315c30c89..444cb4a06eb9 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -407,6 +407,8 @@ where cors: config.rpc_cors.as_ref(), tokio_handle: config.tokio_handle.clone(), rate_limit: config.rpc_rate_limit, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, }; // TODO: https://github.com/paritytech/substrate/issues/13773 diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs index b9abd8446f7d..f19b5a19739e 100644 --- a/substrate/client/service/test/src/lib.rs +++ b/substrate/client/service/test/src/lib.rs @@ -252,6 +252,8 @@ fn node_config< rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None,