Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auth-server: Add management key and gate key management API #53

Merged
merged 1 commit into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions auth/auth-server-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
28 changes: 24 additions & 4 deletions auth/auth-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -86,6 +91,12 @@ impl ApiError {
pub fn internal<T: ToString>(msg: T) -> Self {
Self::InternalError(msg.to_string())
}

/// Create a new bad request error
#[allow(clippy::needless_pass_by_value)]
pub fn bad_request<T: ToString>(msg: T) -> Self {
Self::BadRequest(msg.to_string())
}
}

// Implement warp::reject::Reject for ApiError
Expand Down Expand Up @@ -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<Server>| async move { server.add_key(request).await });
.and_then(|path, headers, body, server: Arc<Server>| 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::<Uuid>())
.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<Server>| async move { server.expire_key(id).await });
.and_then(|id, path, headers, body, server: Arc<Server>| async move {
server.expire_key(id, path, headers, body).await
});

// --- Proxied Routes --- //

Expand All @@ -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;
}

Expand Down
12 changes: 12 additions & 0 deletions auth/auth-server/src/server/api_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
38 changes: 29 additions & 9 deletions auth/auth-server/src/server/handle_key_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<impl Reply, Rejection> {
pub async fn add_key(
&self,
path: FullPath,
headers: HeaderMap,
body: Bytes,
) -> Result<impl Reply, Rejection> {
// 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<impl Reply, Rejection> {
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<impl Reply, Rejection> {
// 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())
}
}
14 changes: 9 additions & 5 deletions auth/auth-server/src/server/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub fn aes_decrypt(value: &str, key: &[u8]) -> Result<String, AuthServerError> {

#[cfg(test)]
mod tests {
use renegade_common::types::wallet::keychain::HmacKey;

use super::*;

/// Tests AES encryption and decryption
Expand All @@ -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}");
}
}
11 changes: 8 additions & 3 deletions auth/auth-server/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
/// The HTTP client
Expand All @@ -53,18 +55,21 @@ 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)?;

Ok(Self {
db_pool: Arc::new(db_pool),
relayer_url: args.relayer_url,
relayer_admin_key,
management_key,
encryption_key,
client: Client::new(),
})
Expand All @@ -84,7 +89,7 @@ impl Server {
body: Bytes,
) -> Result<Response<Bytes>, 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);
Expand Down
Loading