diff options
author | Alex <alex@adnab.me> | 2024-03-01 11:16:41 +0000 |
---|---|---|
committer | Alex <alex@adnab.me> | 2024-03-01 11:16:41 +0000 |
commit | f01883794e475f5dae3d2d4f621b020e1134fa47 (patch) | |
tree | 8695d17a0c81b5b9120ad7d6e19d4ec1908be724 /src/api/admin | |
parent | 9b44639844fe60add66286a161f69f817a2714cb (diff) | |
parent | 70899b0e378fe671af177d87311568cd88e0fda2 (diff) | |
download | garage-f01883794e475f5dae3d2d4f621b020e1134fa47.tar.gz garage-f01883794e475f5dae3d2d4f621b020e1134fa47.zip |
Merge pull request 'Security: backport #737 to the v0.8.x branch' (#740) from backport-737-0.8.x into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/740
Diffstat (limited to 'src/api/admin')
-rw-r--r-- | src/api/admin/api_server.rs | 59 |
1 files changed, 42 insertions, 17 deletions
diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs index 6f1e44e5..3e4b6acc 100644 --- a/src/api/admin/api_server.rs +++ b/src/api/admin/api_server.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; +use argon2::password_hash::PasswordHash; use async_trait::async_trait; use futures::future::Future; @@ -42,14 +43,8 @@ impl AdminApiServer { #[cfg(feature = "metrics")] exporter: PrometheusExporter, ) -> Self { let cfg = &garage.config.admin; - let metrics_token = cfg - .metrics_token - .as_ref() - .map(|tok| format!("Bearer {}", tok)); - let admin_token = cfg - .admin_token - .as_ref() - .map(|tok| format!("Bearer {}", tok)); + let metrics_token = cfg.metrics_token.as_deref().map(hash_bearer_token); + let admin_token = cfg.admin_token.as_deref().map(hash_bearer_token); Self { garage, #[cfg(feature = "metrics")] @@ -237,11 +232,11 @@ impl ApiHandler for AdminApiServer { req: Request<Body>, endpoint: Endpoint, ) -> Result<Response<Body>, Error> { - let expected_auth_header = + let required_auth_hash = match endpoint.authorization_type() { Authorization::None => None, - Authorization::MetricsToken => self.metrics_token.as_ref(), - Authorization::AdminToken => match &self.admin_token { + Authorization::MetricsToken => self.metrics_token.as_deref(), + Authorization::AdminToken => match self.admin_token.as_deref() { None => return Err(Error::forbidden( "Admin token isn't configured, admin API access is disabled for security.", )), @@ -249,14 +244,11 @@ impl ApiHandler for AdminApiServer { }, }; - if let Some(h) = expected_auth_header { + if let Some(password_hash) = required_auth_hash { match req.headers().get("Authorization") { None => return Err(Error::forbidden("Authorization token must be provided")), - Some(v) => { - let authorized = v.to_str().map(|hv| hv.trim() == h).unwrap_or(false); - if !authorized { - return Err(Error::forbidden("Invalid authorization token provided")); - } + Some(authorization) => { + verify_bearer_token(&authorization, password_hash)?; } } } @@ -326,3 +318,36 @@ impl ApiEndpoint for Endpoint { fn add_span_attributes(&self, _span: SpanRef<'_>) {} } + +fn hash_bearer_token(token: &str) -> String { + use argon2::{ + password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, + Argon2, + }; + + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + argon2 + .hash_password(token.trim().as_bytes(), &salt) + .expect("could not hash API token") + .to_string() +} + +fn verify_bearer_token(token: &hyper::http::HeaderValue, password_hash: &str) -> Result<(), Error> { + use argon2::{password_hash::PasswordVerifier, Argon2}; + + let parsed_hash = PasswordHash::new(&password_hash).unwrap(); + + token + .to_str() + .ok() + .and_then(|header| header.strip_prefix("Bearer ")) + .and_then(|token| { + Argon2::default() + .verify_password(token.trim().as_bytes(), &parsed_hash) + .ok() + }) + .ok_or_else(|| Error::forbidden("Invalid authorization token"))?; + + Ok(()) +} |