diff --git a/auth/auth-server-api/src/lib.rs b/auth/auth-server-api/src/lib.rs index 0dfae3d..fcffe20 100644 --- a/auth/auth-server-api/src/lib.rs +++ b/auth/auth-server-api/src/lib.rs @@ -6,7 +6,7 @@ #![deny(clippy::needless_pass_by_ref_mut)] #![feature(trivial_bounds)] -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use uuid::Uuid; /// The Renegade API key header @@ -26,7 +26,7 @@ pub const API_KEYS_PATH: &str = "api-keys"; pub const DEACTIVATE_API_KEY_PATH: &str = "/api-keys/{id}/deactivate"; /// A request to create a new API key -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct CreateApiKeyRequest { /// The API key id pub id: Uuid, diff --git a/auth/auth-server/src/main.rs b/auth/auth-server/src/main.rs index 90f706e..59deada 100644 --- a/auth/auth-server/src/main.rs +++ b/auth/auth-server/src/main.rs @@ -17,17 +17,20 @@ pub(crate) mod models; pub(crate) mod schema; mod server; -use auth_server_api::API_KEYS_PATH; +use auth_server_api::{CreateApiKeyRequest, API_KEYS_PATH}; +use bytes::Bytes; use clap::Parser; +use http::HeaderMap; use renegade_util::telemetry::configure_telemetry; use reqwest::StatusCode; +use serde::Deserialize; use serde_json::json; use std::net::SocketAddr; use std::sync::Arc; use thiserror::Error; use tracing::{error, info}; use uuid::Uuid; -use warp::{Filter, Rejection, Reply}; +use warp::{filters::path::FullPath, Filter, Rejection, Reply}; use server::Server; @@ -48,6 +51,10 @@ pub struct Cli { /// The encryption key used to encrypt/decrypt database values #[arg(long, env = "ENCRYPTION_KEY")] pub encryption_key: String, + /// The management key for the auth server, used to authenticate management + /// requests + #[arg(long, env = "MANAGEMENT_KEY")] + pub management_key: String, /// The URL of the relayer #[arg(long, env = "RELAYER_URL")] pub relayer_url: String, @@ -86,6 +93,12 @@ impl ApiError { pub fn internal(msg: T) -> Self { Self::InternalError(msg.to_string()) } + + /// Create a new bad request error + #[allow(clippy::needless_pass_by_value)] + pub fn bad_request(msg: T) -> Self { + Self::BadRequest(msg.to_string()) + } } // Implement warp::reject::Reject for ApiError @@ -126,17 +139,26 @@ async fn main() { // Add an API key let add_api_key = warp::path(API_KEYS_PATH) .and(warp::post()) - .and(warp::body::json()) + .and(warp::path::full()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) .and(with_server(server.clone())) - .and_then(|request, server: Arc| async move { server.add_key(request).await }); + .and_then(|path, headers, body, server: Arc| async move { + server.add_key(path, headers, body).await + }); // Expire an API key let expire_api_key = warp::path(API_KEYS_PATH) .and(warp::path::param::()) .and(warp::path("deactivate")) + .and(warp::path::full()) + .and(warp::header::headers_cloned()) + .and(warp::body::bytes()) .and(warp::post()) .and(with_server(server.clone())) - .and_then(|id: Uuid, server: Arc| async move { server.expire_key(id).await }); + .and_then(|id, path, headers, body, server: Arc| async move { + server.expire_key(id, path, headers, body).await + }); // --- Proxied Routes --- // @@ -155,7 +177,7 @@ async fn main() { // Bind the server and listen info!("Starting auth server on port {}", listen_addr.port()); let routes = - ping.or(add_api_key).or(expire_api_key).or(atomic_match_path).recover(handle_rejection); + ping.or(atomic_match_path).or(expire_api_key).or(add_api_key).recover(handle_rejection); warp::serve(routes).bind(listen_addr).await; } diff --git a/auth/auth-server/src/server/api_auth.rs b/auth/auth-server/src/server/api_auth.rs index 894dfeb..d95b4a0 100644 --- a/auth/auth-server/src/server/api_auth.rs +++ b/auth/auth-server/src/server/api_auth.rs @@ -4,13 +4,26 @@ use auth_server_api::RENEGADE_API_KEY_HEADER; use http::HeaderMap; use renegade_api::auth::validate_expiring_auth; use renegade_common::types::wallet::keychain::HmacKey; +use serde::Serialize; use uuid::Uuid; +use warp::filters::path::FullPath; use crate::{error::AuthServerError, ApiError}; use super::{helpers::aes_decrypt, Server}; impl Server { + /// Authorize a management request + pub fn authorize_management_request( + &self, + path: FullPath, + headers: &HeaderMap, + body: &[u8], + ) -> Result<(), ApiError> { + validate_expiring_auth(path.as_str(), headers, body, &self.management_key) + .map_err(|_| ApiError::Unauthorized) + } + /// Authorize a request pub(crate) async fn authorize_request( &self, diff --git a/auth/auth-server/src/server/handle_key_management.rs b/auth/auth-server/src/server/handle_key_management.rs index 0fa3dee..5a34ad9 100644 --- a/auth/auth-server/src/server/handle_key_management.rs +++ b/auth/auth-server/src/server/handle_key_management.rs @@ -2,8 +2,10 @@ use crate::models::NewApiKey; use auth_server_api::CreateApiKeyRequest; +use bytes::Bytes; +use http::HeaderMap; use uuid::Uuid; -use warp::{reject::Rejection, reply::Reply}; +use warp::{filters::path::FullPath, reject::Rejection, reply::Reply}; use crate::ApiError; @@ -14,18 +16,39 @@ use super::{ impl Server { /// Add a new API key to the database - pub async fn add_key(&self, req: CreateApiKeyRequest) -> Result { + pub async fn add_key( + &self, + path: FullPath, + headers: HeaderMap, + body: Bytes, + ) -> Result { + // Check management auth on the request + self.authorize_management_request(path, &headers, &body)?; + + // Deserialize the request + let req: CreateApiKeyRequest = + serde_json::from_slice(&body).map_err(ApiError::bad_request)?; + + // Add the key to the database let encrypted_secret = aes_encrypt(&req.secret, &self.encryption_key)?; let new_key = NewApiKey::new(req.id, encrypted_secret, req.description); - self.add_key_query(new_key) - .await - .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; + self.add_key_query(new_key).await.map_err(ApiError::internal)?; Ok(empty_json_reply()) } /// Expire an existing API key - pub async fn expire_key(&self, key_id: Uuid) -> Result { + pub async fn expire_key( + &self, + key_id: Uuid, + path: FullPath, + headers: HeaderMap, + body: Bytes, + ) -> Result { + // Check management auth on the request + self.authorize_management_request(path, &headers, &body)?; + + // Expire the key self.expire_key_query(key_id) .await .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; diff --git a/auth/auth-server/src/server/helpers.rs b/auth/auth-server/src/server/helpers.rs index 95d5056..bebc8cc 100644 --- a/auth/auth-server/src/server/helpers.rs +++ b/auth/auth-server/src/server/helpers.rs @@ -52,6 +52,8 @@ pub fn aes_decrypt(value: &str, key: &[u8]) -> Result { #[cfg(test)] mod tests { + use renegade_common::types::wallet::keychain::HmacKey; + use super::*; /// Tests AES encryption and decryption @@ -66,11 +68,11 @@ mod tests { assert_eq!(value, decrypted); } - /// Generate an encryption key, base64 encode it, and print it + /// Generate a management key #[test] - pub fn generate_encryption_key() { - let key = Aes128Gcm::generate_key(&mut thread_rng()); - let encoded = general_purpose::STANDARD.encode(&key); + fn test_generate_management_key() { + let key = HmacKey::random(); + let encoded = general_purpose::STANDARD.encode(key.0); println!("{}", encoded); } } diff --git a/auth/auth-server/src/server/mod.rs b/auth/auth-server/src/server/mod.rs index d0df323..0700880 100644 --- a/auth/auth-server/src/server/mod.rs +++ b/auth/auth-server/src/server/mod.rs @@ -41,6 +41,8 @@ pub struct Server { pub relayer_url: String, /// The admin key for the relayer pub relayer_admin_key: HmacKey, + /// The management key for the auth server + pub management_key: HmacKey, /// The encryption key for storing API secrets pub encryption_key: Vec, /// The HTTP client @@ -53,11 +55,13 @@ impl Server { // Setup the DB connection pool let db_pool = create_db_pool(&args.database_url).await?; - // Parse the decryption key as a base64 encoded string + // Parse the decryption key, management key, and relayer admin key as + // base64 encoded strings let encryption_key = general_purpose::STANDARD .decode(&args.encryption_key) .map_err(AuthServerError::encryption)?; - + let management_key = + HmacKey::from_base64_string(&args.management_key).map_err(AuthServerError::setup)?; let relayer_admin_key = HmacKey::from_base64_string(&args.relayer_admin_key).map_err(AuthServerError::setup)?; @@ -65,6 +69,7 @@ impl Server { db_pool: Arc::new(db_pool), relayer_url: args.relayer_url, relayer_admin_key, + management_key, encryption_key, client: Client::new(), }) @@ -84,7 +89,7 @@ impl Server { body: Bytes, ) -> Result, ApiError> { // Admin authenticate the request - self.admin_authenticate(path, &mut headers, &body).await?; + self.admin_authenticate(path, &mut headers, &body)?; // Forward the request to the relayer let url = format!("{}{}", self.relayer_url, path);