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 | |
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')
-rw-r--r-- | src/api/Cargo.toml | 1 | ||||
-rw-r--r-- | src/api/admin/api_server.rs | 59 | ||||
-rw-r--r-- | src/api/signature/payload.rs | 6 |
3 files changed, 46 insertions, 20 deletions
diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index 43167fdb..6a9cef8b 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -20,6 +20,7 @@ garage_block.workspace = true garage_util.workspace = true garage_rpc.workspace = true +argon2 = "0.5" async-trait = "0.1.7" base64 = "0.21" bytes = "1.0" 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(()) +} diff --git a/src/api/signature/payload.rs b/src/api/signature/payload.rs index b50fb3bb..4a84610c 100644 --- a/src/api/signature/payload.rs +++ b/src/api/signature/payload.rs @@ -350,9 +350,9 @@ pub async fn verify_v4( ) .ok_or_internal_error("Unable to build signing HMAC")?; hmac.update(payload); - let our_signature = hex::encode(hmac.finalize().into_bytes()); - if signature != our_signature { - return Err(Error::forbidden("Invalid signature".to_string())); + let signature = hex::decode(&signature).map_err(|_| Error::forbidden("Invalid signature"))?; + if hmac.verify_slice(&signature).is_err() { + return Err(Error::forbidden("Invalid signature")); } Ok(key) |