From 27374babf500d397ed25900efe55108f7c68fd84 Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Wed, 23 Oct 2024 17:43:19 -0700 Subject: [PATCH] auth-server: Add management key and gate key management API --- auth/auth-server-api/src/lib.rs | 4 +- auth/auth-server/src/main.rs | 28 ++++++++++++-- auth/auth-server/src/server/api_auth.rs | 12 ++++++ .../src/server/handle_key_management.rs | 38 ++++++++++++++----- auth/auth-server/src/server/helpers.rs | 14 ++++--- auth/auth-server/src/server/mod.rs | 11 ++++-- 6 files changed, 84 insertions(+), 23 deletions(-) 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..2e682e3 100644 --- a/auth/auth-server/src/main.rs +++ b/auth/auth-server/src/main.rs @@ -9,6 +9,7 @@ #![deny(clippy::missing_docs_in_private_items)] #![deny(unsafe_code)] #![deny(clippy::needless_pass_by_ref_mut)] +#![deny(clippy::needless_pass_by_value)] #![feature(trivial_bounds)] pub(crate) mod error; @@ -48,6 +49,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 +91,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 +137,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 +175,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..529d544 100644 --- a/auth/auth-server/src/server/api_auth.rs +++ b/auth/auth-server/src/server/api_auth.rs @@ -5,12 +5,24 @@ use http::HeaderMap; use renegade_api::auth::validate_expiring_auth; use renegade_common::types::wallet::keychain::HmacKey; 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..f59cc50 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,22 +16,40 @@ 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 { - self.expire_key_query(key_id) - .await - .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; + 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?; Ok(empty_json_reply()) } } diff --git a/auth/auth-server/src/server/helpers.rs b/auth/auth-server/src/server/helpers.rs index 95d5056..d224129 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,13 @@ mod tests { assert_eq!(value, decrypted); } - /// Generate an encryption key, base64 encode it, and print it + /// Generate a management key + /// + /// Useful for local testing #[test] - pub fn generate_encryption_key() { - let key = Aes128Gcm::generate_key(&mut thread_rng()); - let encoded = general_purpose::STANDARD.encode(&key); - println!("{}", encoded); + fn test_generate_management_key() { + let key = HmacKey::random(); + let encoded = general_purpose::STANDARD.encode(key.0); + println!("management key: {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);