Skip to content

Commit

Permalink
auth-server: Use api keys to authenticate proxy requests
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Oct 23, 2024
1 parent 83d5065 commit f0494ae
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 88 deletions.
356 changes: 280 additions & 76 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions auth/auth-server-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use serde::Deserialize;
use uuid::Uuid;

/// The Renegade API key header
pub const RENEGADE_API_KEY_HEADER: &str = "X-Renegade-Api-Key";

// ----------------------
// | API Key Management |
// ----------------------
Expand Down
6 changes: 5 additions & 1 deletion auth/auth-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ rand = "0.8.5"

# === Renegade Dependencies === #
auth-server-api = { path = "../auth-server-api" }
renegade-utils = { package = "util", git = "https://github.com/renegade-fi/renegade" }
renegade-common = { package = "common", git = "https://github.com/renegade-fi/renegade.git" }
renegade-util = { package = "util", git = "https://github.com/renegade-fi/renegade.git" }
renegade-api = { package = "external-api", git = "https://github.com/renegade-fi/renegade.git", features = [
"auth",
] }

# === Misc Dependencies === #
base64 = "0.22.1"
Expand Down
34 changes: 32 additions & 2 deletions auth/auth-server/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@

use thiserror::Error;

use crate::ApiError;

/// Custom error type for server errors
#[derive(Error, Debug)]
pub enum AuthServerError {
/// API key inactive
#[error("API key inactive")]
ApiKeyInactive,
/// Database connection error
#[error("Database connection error: {0}")]
DatabaseConnection(String),

/// Encryption error
#[error("Encryption error: {0}")]
Encryption(String),

/// Decryption error
#[error("Decryption error: {0}")]
Decryption(String),
/// Error serializing or deserializing a stored value
#[error("Error serializing/deserializing a stored value: {0}")]
Serde(String),
/// Unauthorized
#[error("Unauthorized: {0}")]
Unauthorized(String),
}

impl AuthServerError {
Expand All @@ -33,6 +42,27 @@ impl AuthServerError {
pub fn decryption<T: ToString>(msg: T) -> Self {
Self::Decryption(msg.to_string())
}

/// Create a new serde error
pub fn serde<T: ToString>(msg: T) -> Self {
Self::Serde(msg.to_string())
}

/// Create a new unauthorized error
pub fn unauthorized<T: ToString>(msg: T) -> Self {
Self::Unauthorized(msg.to_string())
}
}

impl warp::reject::Reject for AuthServerError {}

impl From<AuthServerError> for ApiError {
fn from(err: AuthServerError) -> Self {
match err {
AuthServerError::ApiKeyInactive | AuthServerError::Unauthorized(_) => {
ApiError::Unauthorized
},
_ => ApiError::InternalError(err.to_string()),
}
}
}
6 changes: 5 additions & 1 deletion auth/auth-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod server;

use auth_server_api::{API_KEYS_PATH, DEACTIVATE_API_KEY_PATH};
use clap::Parser;
use renegade_utils::telemetry::configure_telemetry;
use renegade_util::telemetry::configure_telemetry;
use reqwest::StatusCode;
use serde_json::json;
use std::net::SocketAddr;
Expand Down Expand Up @@ -75,6 +75,9 @@ pub enum ApiError {
/// A bad request error
#[error("Bad request: {0}")]
BadRequest(String),
/// An unauthorized error
#[error("Unauthorized")]
Unauthorized,
}

// Implement warp::reject::Reject for ApiError
Expand Down Expand Up @@ -159,6 +162,7 @@ async fn handle_rejection(err: Rejection) -> Result<impl Reply, Rejection> {
(StatusCode::INTERNAL_SERVER_ERROR, DEFAULT_INTERNAL_SERVER_ERROR_MESSAGE)
},
ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
ApiError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
};

Ok(json_error(message, code))
Expand Down
10 changes: 7 additions & 3 deletions auth/auth-server/src/models.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
//! DB model types for the auth server
#![allow(missing_docs, clippy::missing_docs_in_private_items)]
#![allow(trivial_bounds)]

use std::time::SystemTime;

use crate::schema::api_keys;
use diesel::prelude::*;
use diesel::sql_types::Timestamp;
use uuid::Uuid;

#[derive(Queryable)]
#[derive(Queryable, Selectable, Clone)]
#[diesel(table_name = api_keys)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct ApiKey {
pub id: Uuid,
pub encrypted_key: String,
pub description: String,
pub created_at: Timestamp,
pub created_at: SystemTime,
pub is_active: bool,
}

Expand Down
61 changes: 57 additions & 4 deletions auth/auth-server/src/server/handle_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
//! At a high level the server must first authenticate the request, then forward
//! it to the relayer with admin authentication

use auth_server_api::RENEGADE_API_KEY_HEADER;
use bytes::Bytes;
use http::Method;
use http::{HeaderMap, Method};
use renegade_api::auth::validate_expiring_auth;
use renegade_common::types::wallet::keychain::HmacKey;
use tracing::error;
use uuid::Uuid;
use warp::{reject::Rejection, reply::Reply};

use crate::ApiError;
use crate::{error::AuthServerError, ApiError};

use super::Server;
use super::{helpers::aes_decrypt, Server};

/// Handle a proxied request
impl Server {
Expand All @@ -19,9 +23,13 @@ impl Server {
&self,
path: warp::path::FullPath,
method: Method,
headers: warp::hyper::HeaderMap,
mut headers: warp::hyper::HeaderMap,
body: Bytes,
) -> Result<impl Reply, Rejection> {
// Authorize the request
self.authorize_request(path.as_str(), &mut headers, &body).await?;

// Forward the request to the relayer
let url = format!("{}{}", self.relayer_url, path.as_str());
let req = self.client.request(method, &url).headers(headers).body(body);

Expand All @@ -48,4 +56,49 @@ impl Server {
},
}
}

/// Authorize a request
async fn authorize_request(
&self,
path: &str,
headers: &mut HeaderMap,
body: &[u8],
) -> Result<(), ApiError> {
// Check API auth
let api_key = headers
.remove(RENEGADE_API_KEY_HEADER)
.and_then(|h| h.to_str().ok().map(String::from)) // Convert to String
.and_then(|s| Uuid::parse_str(&s).ok()) // Use &s to parse
.ok_or(AuthServerError::unauthorized("Invalid or missing Renegade API key"))?;

self.check_api_key_auth(api_key, path, headers, body).await?;
Ok(())
}

/// Check that a request is authorized with a given API key and an HMAC of
/// the request using the API secret
async fn check_api_key_auth(
&self,
api_key: Uuid,
path: &str,
headers: &HeaderMap,
body: &[u8],
) -> Result<(), AuthServerError> {
let api_secret = self.get_api_secret(api_key).await?;
let key = HmacKey::from_base64_string(&api_secret).map_err(AuthServerError::serde)?;

validate_expiring_auth(path, headers, body, &key).map_err(AuthServerError::unauthorized)
}

/// Get the API secret for a given API key
async fn get_api_secret(&self, api_key: Uuid) -> Result<String, AuthServerError> {
// Fetch the API key entry then decrypt the API secret
let entry = self.get_api_key_entry(api_key).await?;
let decrypted = aes_decrypt(&entry.encrypted_key, &self.encryption_key)?;
if !entry.is_active {
return Err(AuthServerError::ApiKeyInactive);
}

Ok(decrypted)
}
}
21 changes: 20 additions & 1 deletion auth/auth-server/src/server/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,30 @@ use diesel::{ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use uuid::Uuid;

use crate::{models::NewApiKey, schema::api_keys};
use crate::{
models::{ApiKey, NewApiKey},
schema::api_keys,
};

use super::{AuthServerError, Server};

impl Server {
// --- Getters --- //

/// Get the API key entry for a given key
pub async fn get_api_key_entry(&self, api_key: Uuid) -> Result<ApiKey, AuthServerError> {
let mut conn = self.get_db_conn().await?;
api_keys::table
.filter(api_keys::id.eq(api_key))
.limit(1)
.load::<ApiKey>(&mut conn)
.await
.map_err(AuthServerError::db)
.map(|res| res[0].clone())
}

// --- Setters --- //

/// Add a new API key to the database
pub async fn add_key_query(&self, new_key: NewApiKey) -> Result<(), AuthServerError> {
let mut conn = self.get_db_conn().await?;
Expand Down

0 comments on commit f0494ae

Please sign in to comment.