diff --git a/Cargo.lock b/Cargo.lock index d651ca93dd768..a07132e4c57db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,6 +358,7 @@ dependencies = [ "pprof", "regex", "rstack-self", + "sha256", "tokio", "tokio-scoped", "url", @@ -13513,6 +13514,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha256" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2 0.10.6", + "tokio", +] + [[package]] name = "sha3" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index a1226d4a2d8c4..1407d3588b441 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -610,6 +610,7 @@ rusty-fork = "0.3.0" scopeguard = "1.2.0" sha-1 = "0.10.0" sha2 = "0.9.3" +sha256 = "1.4.0" sha2_0_10_6 = { package = "sha2", version = "0.10.6" } sha3 = "0.9.1" siphasher = "0.3.10" diff --git a/config/src/config/admin_service_config.rs b/config/src/config/admin_service_config.rs index 10932a2cfda62..ee5584af3cc81 100644 --- a/config/src/config/admin_service_config.rs +++ b/config/src/config/admin_service_config.rs @@ -18,7 +18,22 @@ pub struct AdminServiceConfig { pub enabled: Option, pub address: String, pub port: u16, - // TODO(grao): Add auth support if necessary. + // If empty, will allow all requests without authentication. (Not allowed on mainnet.) + pub authentication_configs: Vec, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +pub enum AuthenticationConfig { + // This will allow authentication through query parameter. + // e.g. `/profilez?passcode=abc`. + // + // To calculate sha256, use sha256sum tool, or other online tools. + // + // e.g. + // + // printf abc |sha256sum + PasscodeSha256(String), + // TODO(grao): Add SSL support if necessary. } impl Default for AdminServiceConfig { @@ -27,6 +42,7 @@ impl Default for AdminServiceConfig { enabled: None, address: "0.0.0.0".to_string(), port: 9102, + authentication_configs: vec![], } } } @@ -39,10 +55,25 @@ impl AdminServiceConfig { impl ConfigSanitizer for AdminServiceConfig { fn sanitize( - _node_config: &NodeConfig, + node_config: &NodeConfig, _node_type: NodeType, - _chain_id: Option, + chain_id: Option, ) -> Result<(), Error> { + let sanitizer_name = Self::get_sanitizer_name(); + + if node_config.admin_service.enabled == Some(true) { + if let Some(chain_id) = chain_id { + if chain_id.is_mainnet() + && node_config.admin_service.authentication_configs.is_empty() + { + return Err(Error::ConfigSanitizerFailed( + sanitizer_name, + "Must enable authentication for AdminService on mainnet.".into(), + )); + } + } + } + Ok(()) } } diff --git a/crates/aptos-admin-service/Cargo.toml b/crates/aptos-admin-service/Cargo.toml index 8be6d715a8fd9..fbe96a96ff0d4 100644 --- a/crates/aptos-admin-service/Cargo.toml +++ b/crates/aptos-admin-service/Cargo.toml @@ -30,6 +30,7 @@ http = { workspace = true } hyper = { workspace = true } lazy_static = { workspace = true } mime = { workspace = true } +sha256 = { workspace = true } tokio = { workspace = true } tokio-scoped = { workspace = true } url = { workspace = true } diff --git a/crates/aptos-admin-service/src/server/mod.rs b/crates/aptos-admin-service/src/server/mod.rs index 86b6a53f5c6c3..28670c1bc3c8d 100644 --- a/crates/aptos-admin-service/src/server/mod.rs +++ b/crates/aptos-admin-service/src/server/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::server::utils::reply_with_status; -use aptos_config::config::NodeConfig; +use aptos_config::config::{AuthenticationConfig, NodeConfig}; use aptos_consensus::{ persistent_liveness_storage::StorageWriteProxy, quorum_store::quorum_store_db::QuorumStoreDB, }; @@ -14,6 +14,7 @@ use hyper::{ Body, Request, Response, Server, StatusCode, }; use std::{ + collections::HashMap, convert::Infallible, net::{SocketAddr, ToSocketAddrs}, sync::Arc, @@ -29,6 +30,8 @@ mod utils; #[derive(Default)] pub struct Context { + authentication_configs: Vec, + aptos_db: RwLock>>, consensus_db: RwLock>>, quorum_store_db: RwLock>>, @@ -79,7 +82,10 @@ impl AdminService { let admin_service = Self { runtime, - context: Default::default(), + context: Arc::new(Context { + authentication_configs: node_config.admin_service.authentication_configs.clone(), + ..Default::default() + }), }; // TODO(grao): Consider support enabling the service through an authenticated request. @@ -131,6 +137,36 @@ impl AdminService { "AdminService is not enabled.", )); } + + let mut authenticated = false; + if context.authentication_configs.is_empty() { + authenticated = true; + } else { + for authentication_config in &context.authentication_configs { + match authentication_config { + AuthenticationConfig::PasscodeSha256(passcode_sha256) => { + let query = req.uri().query().unwrap_or(""); + let query_pairs: HashMap<_, _> = + url::form_urlencoded::parse(query.as_bytes()).collect(); + let passcode: Option = + query_pairs.get("passcode").map(|p| p.to_string()); + if let Some(passcode) = passcode { + if sha256::digest(passcode) == *passcode_sha256 { + authenticated = true; + } + } + }, + } + } + }; + + if !authenticated { + return Ok(reply_with_status( + StatusCode::NETWORK_AUTHENTICATION_REQUIRED, + format!("{} endpoint requires authentication.", req.uri().path()), + )); + } + match (req.method().clone(), req.uri().path()) { #[cfg(target_os = "linux")] (hyper::Method::GET, "/profilez") => profiling::handle_cpu_profiling_request(req).await,