diff --git a/Cargo.lock b/Cargo.lock index f64584eb99..d1b35cb7c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,29 +447,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-extra" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" -dependencies = [ - "axum 0.7.5", - "axum-core 0.4.3", - "bytes", - "futures-util", - "headers", - "http 1.1.0", - "http-body 1.0.0", - "http-body-util", - "mime", - "pin-project-lite", - "serde", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.71" @@ -2738,30 +2715,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "headers" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 1.1.0", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.1.0", -] - [[package]] name = "heck" version = "0.3.3" @@ -5126,11 +5079,9 @@ name = "nym-node-http-api" version = "0.1.0" dependencies = [ "axum 0.7.5", - "axum-extra", "colored", "dashmap", "fastrand 2.1.0", - "headers", "hmac 0.12.1", "hyper 1.3.1", "ipnetwork 0.16.0", diff --git a/common/http-api-common/src/middleware/auth/bearer.rs b/common/http-api-common/src/middleware/auth/bearer.rs index 937b85669c..f7bae2c086 100644 --- a/common/http-api-common/src/middleware/auth/bearer.rs +++ b/common/http-api-common/src/middleware/auth/bearer.rs @@ -14,11 +14,25 @@ use zeroize::Zeroizing; #[derive(Debug, Clone)] pub struct BearerAuthLayer { bearer_token: Arc>, + allow_empty: bool, } impl BearerAuthLayer { pub fn new(bearer_token: Arc>) -> Self { - BearerAuthLayer { bearer_token } + BearerAuthLayer { + bearer_token, + allow_empty: false, + } + } + + pub fn new_raw(bearer_token: impl Into) -> Self { + BearerAuthLayer::new(Arc::new(Zeroizing::new(bearer_token.into()))) + } + + #[must_use] + pub fn with_allow_empty(mut self, allow_empty: bool) -> Self { + self.allow_empty = allow_empty; + self } } @@ -26,7 +40,7 @@ impl Layer for BearerAuthLayer { type Service = RequireBearerAuth; fn layer(&self, inner: S) -> Self::Service { - RequireBearerAuth::new(inner, self.bearer_token.clone()) + RequireBearerAuth::new(inner, self.bearer_token.clone()).with_allow_empty(self.allow_empty) } } @@ -34,6 +48,7 @@ impl Layer for BearerAuthLayer { pub struct RequireBearerAuth { inner: S, bearer_token: Arc>, + allow_empty: bool, } impl RequireBearerAuth { @@ -41,9 +56,16 @@ impl RequireBearerAuth { RequireBearerAuth { inner, bearer_token, + allow_empty: false, } } + #[must_use] + pub fn with_allow_empty(mut self, allow_empty: bool) -> Self { + self.allow_empty = allow_empty; + self + } + fn check_auth_header(&self, header: Option<&HeaderValue>) -> Result<(), &'static str> { let Some(token) = header else { trace!("missing header"); @@ -103,6 +125,16 @@ where fn call(&mut self, req: Request) -> Self::Future { debug!("checking the auth"); + if self.bearer_token.is_empty() && !self.allow_empty { + return Box::pin(async move { + Ok(( + StatusCode::INTERNAL_SERVER_ERROR, + "no valid access token has been specified on the server", + ) + .into_response()) + }); + } + let auth_header = req.headers().get(header::AUTHORIZATION); match self.check_auth_header(auth_header) { diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 0ec34da786..989f6a1880 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -61,6 +61,8 @@ rocket_cors = { workspace = true } rocket_okapi = { workspace = true, features = ["swagger"] } okapi = { workspace = true, features = ["impl_json_schema"] } + + ## ephemera-specific #actix-web = "4" #array-bytes = "6.0.0" diff --git a/nym-node/nym-node-http-api/Cargo.toml b/nym-node/nym-node-http-api/Cargo.toml index 54b5831217..a8c2cd3e63 100644 --- a/nym-node/nym-node-http-api/Cargo.toml +++ b/nym-node/nym-node-http-api/Cargo.toml @@ -8,8 +8,6 @@ license.workspace = true [dependencies] axum.workspace = true -axum-extra = { workspace = true, features = ["typed-header"] } -headers.workspace = true # useful for `#[axum_macros::debug_handler]` #axum-macros = "0.3.8" diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs index 7f3dc9151e..aa81b03fcc 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs @@ -8,24 +8,30 @@ use crate::state::metrics::MetricsAppState; use axum::extract::FromRef; use axum::routing::get; use axum::Router; +use nym_http_api_common::middleware::BearerAuthLayer; use nym_node_requests::routes::api::v1::metrics; pub mod mixing; pub mod prometheus; pub mod verloc; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default, Clone)] pub struct Config { - // + pub prometheus_token: String, } -pub(super) fn routes(_config: Config) -> Router +pub(super) fn routes(config: Config) -> Router where S: Send + Sync + 'static + Clone, MetricsAppState: FromRef, { + let auth_middleware = BearerAuthLayer::new_raw(config.prometheus_token); + Router::new() .route(metrics::MIXING, get(mixing_stats)) .route(metrics::VERLOC, get(verloc_stats)) - .route(metrics::PROMETHEUS, get(prometheus_metrics)) + .route( + metrics::PROMETHEUS, + get(prometheus_metrics).layer(auth_middleware), + ) } diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs index a404cb9871..e336c3690b 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs @@ -1,12 +1,6 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::state::metrics::MetricsAppState; -use axum::extract::State; -use axum::http::StatusCode; -use axum_extra::TypedHeader; -use headers::authorization::Bearer; -use headers::Authorization; use nym_metrics::metrics; /// Returns `prometheus` compatible metrics @@ -25,21 +19,6 @@ use nym_metrics::metrics; ("prometheus_token" = []) ) )] -pub(crate) async fn prometheus_metrics<'a>( - TypedHeader(authorization): TypedHeader>, - State(state): State, -) -> Result { - if authorization.token().is_empty() { - return Err(StatusCode::UNAUTHORIZED); - } - // TODO: is 500 the correct error code here? - let Some(metrics_key) = state.prometheus_access_token else { - return Err(StatusCode::INTERNAL_SERVER_ERROR); - }; - - if metrics_key != authorization.token() { - return Err(StatusCode::UNAUTHORIZED); - } - - Ok(metrics!()) +pub(crate) async fn prometheus_metrics() -> String { + metrics!() } diff --git a/nym-node/nym-node-http-api/src/router/mod.rs b/nym-node/nym-node-http-api/src/router/mod.rs index f61eafad77..1e22f9d72f 100644 --- a/nym-node/nym-node-http-api/src/router/mod.rs +++ b/nym-node/nym-node-http-api/src/router/mod.rs @@ -148,6 +148,12 @@ impl Config { self.api.v1_config.ip_packet_router.details = Some(ip_packet_router); self } + + #[must_use] + pub fn with_metrics_token(mut self, bearer_token: impl Into>) -> Self { + self.api.v1_config.metrics.prometheus_token = bearer_token.into().unwrap_or_default(); + self + } } pub struct NymNodeRouter { diff --git a/nym-node/nym-node-http-api/src/state/metrics.rs b/nym-node/nym-node-http-api/src/state/metrics.rs index d4691727d9..3b0863eae1 100644 --- a/nym-node/nym-node-http-api/src/state/metrics.rs +++ b/nym-node/nym-node-http-api/src/state/metrics.rs @@ -134,8 +134,6 @@ impl VerlocStatsState { #[derive(Debug, Clone, Default)] pub struct MetricsAppState { - pub(crate) prometheus_access_token: Option, - pub(crate) mixing_stats: SharedMixingStats, pub(crate) verloc: SharedVerlocStats, diff --git a/nym-node/nym-node-http-api/src/state/mod.rs b/nym-node/nym-node-http-api/src/state/mod.rs index 077ca782b6..26952fe8da 100644 --- a/nym-node/nym-node-http-api/src/state/mod.rs +++ b/nym-node/nym-node-http-api/src/state/mod.rs @@ -37,10 +37,4 @@ impl AppState { self.metrics.verloc = verloc_stats; self } - - #[must_use] - pub fn with_metrics_key(mut self, bearer_token: impl Into>) -> Self { - self.metrics.prometheus_access_token = bearer_token.into(); - self - } } diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 0b8e6f5da6..3efb3b6996 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -369,6 +369,7 @@ pub struct Http { /// An optional bearer token for accessing certain http endpoints. /// Currently only used for obtaining mixnode's stats. #[serde(default)] + #[serde(deserialize_with = "de_maybe_stringified")] pub access_token: Option, /// Specify whether basic system information should be exposed. diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index a4b42d90a5..eaf5ea9b60 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -540,7 +540,8 @@ impl NymNode { .with_ip_packet_router_details(ipr_details) .with_used_exit_policy(exit_policy_details) .with_description(self.description.clone()) - .with_auxiliary_details(auxiliary_details); + .with_auxiliary_details(auxiliary_details) + .with_metrics_token(self.config.http.access_token.clone()); if self.config.http.expose_system_info { config = config.with_system_info(get_system_info( @@ -560,8 +561,7 @@ impl NymNode { let app_state = AppState::new() .with_mixing_stats(self.mixnode.mixing_stats.clone()) - .with_verloc_stats(self.verloc_stats.clone()) - .with_metrics_key(self.config.http.access_token.clone()); + .with_verloc_stats(self.verloc_stats.clone()); Ok(NymNodeRouter::new(config, Some(app_state), Some(wg_state)) .build_server(&self.config.http.bind_address)