admin api: verify tokens using the new admin api token table
All checks were successful
ci/woodpecker/push/debug Pipeline was successful
ci/woodpecker/pr/debug Pipeline was successful

This commit is contained in:
Alex 2025-03-11 13:40:23 +01:00
parent 46f620119b
commit 004eb94e14

View file

@ -1,8 +1,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
use argon2::password_hash::PasswordHash;
use http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, AUTHORIZATION}; use http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, AUTHORIZATION};
use hyper::{body::Incoming as IncomingBody, Request, Response}; use hyper::{body::Incoming as IncomingBody, Request, Response};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -15,10 +13,12 @@ use opentelemetry_prometheus::PrometheusExporter;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use garage_rpc::{Endpoint as RpcEndpoint, *}; use garage_rpc::{Endpoint as RpcEndpoint, *};
use garage_table::EmptyKey;
use garage_util::background::BackgroundRunner; use garage_util::background::BackgroundRunner;
use garage_util::data::Uuid; use garage_util::data::Uuid;
use garage_util::error::Error as GarageError; use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress; use garage_util::socket_address::UnixOrTCPSocketAddress;
use garage_util::time::now_msec;
use garage_api_common::generic_server::*; use garage_api_common::generic_server::*;
use garage_api_common::helpers::*; use garage_api_common::helpers::*;
@ -168,14 +168,13 @@ impl AdminApiServer {
}, },
}; };
if let Some(password_hash) = required_auth_hash { verify_authorization(
match auth_header { &self.garage,
None => return Err(Error::forbidden("Authorization token must be provided")), required_auth_hash,
Some(authorization) => { auth_header,
verify_bearer_token(&authorization, password_hash)?; request.name(),
} )
} .await?;
}
match request { match request {
AdminApiRequest::Options(req) => req.handle(&self.garage, &self).await, AdminApiRequest::Options(req) => req.handle(&self.garage, &self).await,
@ -249,20 +248,65 @@ fn hash_bearer_token(token: &str) -> String {
.to_string() .to_string()
} }
fn verify_bearer_token(token: &hyper::http::HeaderValue, password_hash: &str) -> Result<(), Error> { async fn verify_authorization(
use argon2::{password_hash::PasswordVerifier, Argon2}; garage: &Garage,
required_token_hash: Option<&str>,
auth_header: Option<hyper::http::HeaderValue>,
endpoint_name: &str,
) -> Result<(), Error> {
use argon2::{password_hash::PasswordHash, password_hash::PasswordVerifier, Argon2};
let parsed_hash = PasswordHash::new(&password_hash).unwrap(); let invalid_msg = "Invalid bearer token";
token if let Some(token_hash_str) = required_token_hash {
.to_str()? let token = match &auth_header {
.strip_prefix("Bearer ") None => {
.and_then(|token| { return Err(Error::forbidden(
Argon2::default() "Bearer token must be provided in Authorization header",
.verify_password(token.trim().as_bytes(), &parsed_hash) ))
.ok() }
}) Some(authorization) => authorization
.ok_or_else(|| Error::forbidden("Invalid authorization token"))?; .to_str()?
.strip_prefix("Bearer ")
.ok_or_else(|| Error::forbidden("Invalid Authorization header"))?
.trim(),
};
let token_hash_string = if let Some((prefix, _)) = token.split_once('.') {
garage
.admin_token_table
.get(&EmptyKey, &prefix.to_string())
.await?
.and_then(|k| k.state.into_option())
.filter(|p| {
p.expiration
.get()
.map(|exp| now_msec() < exp)
.unwrap_or(true)
})
.filter(|p| {
p.scope
.get()
.0
.iter()
.any(|x| x == "*" || x == endpoint_name)
})
.ok_or_else(|| Error::forbidden(invalid_msg))?
.token_hash
} else {
token_hash_str.to_string()
};
let token_hash = PasswordHash::new(&token_hash_string)
.ok_or_internal_error("Could not parse token hash")?;
if Argon2::default()
.verify_password(token.as_bytes(), &token_hash)
.is_err()
{
return Err(Error::forbidden(invalid_msg));
}
}
Ok(()) Ok(())
} }