Skip to content

Commit

Permalink
auth-server: Add management key and gate key management API
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Oct 24, 2024
1 parent 0049346 commit 4d1be4a
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 21 deletions.
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
34 changes: 28 additions & 6 deletions auth/auth-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Check failure on line 20 in auth/auth-server/src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `CreateApiKeyRequest`

error: unused import: `CreateApiKeyRequest` --> auth/auth-server/src/main.rs:20:23 | 20 | use auth_server_api::{CreateApiKeyRequest, API_KEYS_PATH}; | ^^^^^^^^^^^^^^^^^^^
use bytes::Bytes;

Check failure on line 21 in auth/auth-server/src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `bytes::Bytes`

error: unused import: `bytes::Bytes` --> auth/auth-server/src/main.rs:21:5 | 21 | use bytes::Bytes; | ^^^^^^^^^^^^
use clap::Parser;
use http::HeaderMap;

Check failure on line 23 in auth/auth-server/src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `http::HeaderMap`

error: unused import: `http::HeaderMap` --> auth/auth-server/src/main.rs:23:5 | 23 | use http::HeaderMap; | ^^^^^^^^^^^^^^^
use renegade_util::telemetry::configure_telemetry;
use reqwest::StatusCode;
use serde::Deserialize;

Check failure on line 26 in auth/auth-server/src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `serde::Deserialize`

error: unused import: `serde::Deserialize` --> auth/auth-server/src/main.rs:26:5 | 26 | 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};

Check failure on line 33 in auth/auth-server/src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `filters::path::FullPath`

error: unused import: `filters::path::FullPath` --> auth/auth-server/src/main.rs:33:12 | 33 | use warp::{filters::path::FullPath, Filter, Rejection, Reply}; | ^^^^^^^^^^^^^^^^^^^^^^^

use server::Server;

Expand All @@ -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,
Expand Down Expand Up @@ -86,6 +93,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 +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<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 +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;
}

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

Check failure on line 7 in auth/auth-server/src/server/api_auth.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `serde::Serialize`

error: unused import: `serde::Serialize` --> auth/auth-server/src/server/api_auth.rs:7:5 | 7 | use serde::Serialize; | ^^^^^^^^^^^^^^^^ | = note: `-D unused-imports` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unused_imports)]`
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,

Check failure on line 19 in auth/auth-server/src/server/api_auth.rs

View workflow job for this annotation

GitHub Actions / clippy

this argument is passed by value, but not consumed in the function body

error: this argument is passed by value, but not consumed in the function body --> auth/auth-server/src/server/api_auth.rs:19:15 | 19 | path: FullPath, | ^^^^^^^^ help: consider taking a reference instead: `&FullPath` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value = note: requested on the command line with `-D clippy::needless-pass-by-value`
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
35 changes: 29 additions & 6 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,18 +16,39 @@ 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> {
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
.map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?;
Expand Down
10 changes: 6 additions & 4 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,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);
}
}
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

0 comments on commit 4d1be4a

Please sign in to comment.