Skip to content

Commit

Permalink
[AdminService] Implement a naive passcode authentication.
Browse files Browse the repository at this point in the history
  • Loading branch information
grao1991 committed Nov 21, 2023
1 parent eaad9ac commit 02572f0
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 5 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
37 changes: 34 additions & 3 deletions config/src/config/admin_service_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,22 @@ pub struct AdminServiceConfig {
pub enabled: Option<bool>,
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<AuthenticationConfig>,
}

#[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 {
Expand All @@ -27,6 +42,7 @@ impl Default for AdminServiceConfig {
enabled: None,
address: "0.0.0.0".to_string(),
port: 9102,
authentication_configs: vec![],
}
}
}
Expand All @@ -39,10 +55,25 @@ impl AdminServiceConfig {

impl ConfigSanitizer for AdminServiceConfig {
fn sanitize(
_node_config: &NodeConfig,
node_config: &NodeConfig,
_node_type: NodeType,
_chain_id: Option<ChainId>,
chain_id: Option<ChainId>,
) -> 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(())
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/aptos-admin-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
40 changes: 38 additions & 2 deletions crates/aptos-admin-service/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -14,6 +14,7 @@ use hyper::{
Body, Request, Response, Server, StatusCode,
};
use std::{
collections::HashMap,
convert::Infallible,
net::{SocketAddr, ToSocketAddrs},
sync::Arc,
Expand All @@ -29,6 +30,8 @@ mod utils;

#[derive(Default)]
pub struct Context {
authentication_configs: Vec<AuthenticationConfig>,

aptos_db: RwLock<Option<Arc<DbReaderWriter>>>,
consensus_db: RwLock<Option<Arc<StorageWriteProxy>>>,
quorum_store_db: RwLock<Option<Arc<QuorumStoreDB>>>,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<String> =
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,
Expand Down

0 comments on commit 02572f0

Please sign in to comment.