Skip to content

Commit

Permalink
funds-manager: middleware: Add symmetric key auth for withdrawal
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Jul 24, 2024
1 parent 06a6747 commit 4725c47
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 22 deletions.
3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ debug = true
opt-level = 3 # Full optimizations
lto = true

[http]
check-revoke = false

[workspace.dependencies]
# === Renegade Dependencies === #
arbitrum-client = { git = "https://github.com/renegade-fi/renegade.git", features = [
Expand Down
2 changes: 0 additions & 2 deletions bin/build_and_push.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
#!/bin/sh
REGION=${REGION:-us-east-2}
ENVIRONMENT=testnet
ECR_REGISTRY=377928551571.dkr.ecr.$REGION.amazonaws.com

# Parse command line arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--dockerfile) DOCKERFILE="$2"; shift ;;
--ecr-repo) ECR_REPO="$2"; shift ;;
--environment) ENVIRONMENT="$2"; shift ;;
--region) REGION="$2"; shift ;;
*) echo "Unknown parameter: $1"; exit 1 ;;
esac
Expand Down
12 changes: 7 additions & 5 deletions funds-manager/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ COPY ./funds-manager ./funds-manager
# === Builder === #
# Pull the sources into their own layer
FROM chef AS builder

# Disable compiler warnings and enable backtraces for panic debugging
ENV RUSTFLAGS=-Awarnings
ENV RUST_BACKTRACE=1
ENV CARGO_HTTP_CHECK_REVOKE=false

COPY --from=sources /build /build
WORKDIR /build

# Install protoc, openssl, and pkg-config
RUN apt-get update && \
apt-get install -y pkg-config libssl-dev libclang-dev libpq-dev
apt-get install -y pkg-config libssl-dev libclang-dev libpq-dev ca-certificates

# Update Cargo.toml to include only "funds-manager/funds-manager-server" in workspace members
RUN sed -i '/members[[:space:]]*=[[:space:]]*\[/,/\]/c\members = ["funds-manager/funds-manager-server"]' Cargo.toml
Expand All @@ -33,10 +39,6 @@ RUN cargo chef prepare --recipe-path recipe.json --bin funds-manager
# Build only the dependencies to cache them in this layer
RUN cargo chef cook --release --recipe-path recipe.json

# Disable compiler warnings and enable backtraces for panic debugging
ENV RUSTFLAGS=-Awarnings
ENV RUST_BACKTRACE=1

COPY --from=sources /build/funds-manager /build/funds-manager
WORKDIR /build

Expand Down
2 changes: 1 addition & 1 deletion funds-manager/funds-manager-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct DepositAddressResponse {
}

/// The request body for withdrawing funds from custody
#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WithdrawFundsRequest {
/// The mint of the asset to withdraw
pub mint: String,
Expand Down
11 changes: 11 additions & 0 deletions funds-manager/funds-manager-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ description = "Manages custody of funds for protocol operator"
version = "0.1.0"
edition = "2021"

[net]
git-fetch-with-cli = true

[http]
check-revoke = false

[dependencies]
# === CLI + Server === #
clap = { version = "4.5.3", features = ["derive", "env"] }
funds-manager-api = { path = "../funds-manager-api" }
hex = "0.4.3"
hmac = "0.12.1"
http-body-util = "0.1.0"
sha2 = "0.10.6"
tokio = { version = "1.10", features = ["full"] }
warp = "0.3"

Expand Down Expand Up @@ -39,8 +48,10 @@ renegade-util = { package = "util", workspace = true }
# === Misc Dependencies === #
base64 = "0.22"
bigdecimal = { version = "0.4", features = ["serde"] }
bytes = "1.5.0"
futures = "0.3"
http = "1.1"
itertools = "0.13"
num-bigint = "0.4"
reqwest = { version = "0.12", features = ["json"] }
serde = "1.0"
Expand Down
3 changes: 3 additions & 0 deletions funds-manager/funds-manager-server/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ pub enum ApiError {
InternalError(String),
/// Bad request error
BadRequest(String),
/// Unauthenticated error
Unauthenticated(String),
}

impl Reject for ApiError {}
Expand All @@ -113,6 +115,7 @@ impl Display for ApiError {
ApiError::RedemptionError(e) => write!(f, "Redemption error: {}", e),
ApiError::InternalError(e) => write!(f, "Internal error: {}", e),
ApiError::BadRequest(e) => write!(f, "Bad request: {}", e),
ApiError::Unauthenticated(e) => write!(f, "Unauthenticated: {}", e),
}
}
}
Expand Down
71 changes: 60 additions & 11 deletions funds-manager/funds-manager-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod db;
pub mod error;
pub mod fee_indexer;
pub mod handlers;
pub mod middleware;
pub mod relayer_client;

use aws_config::{BehaviorVersion, Region, SdkConfig};
Expand All @@ -24,6 +25,7 @@ use funds_manager_api::{
use handlers::{
get_deposit_address_handler, index_fees_handler, quoter_withdraw_handler, redeem_fees_handler,
};
use middleware::{identity, with_hmac_auth, with_json_body};
use relayer_client::RelayerClient;
use renegade_circuit_types::elgamal::DecryptionKey;
use renegade_util::{err_str, raw_err_str, telemetry::configure_telemetry};
Expand All @@ -35,7 +37,8 @@ use arbitrum_client::{
constants::Chain,
};
use clap::Parser;
use tracing::error;
use funds_manager_api::WithdrawFundsRequest;
use tracing::{error, warn};
use warp::{filters::query::query, Filter};

use crate::custody_client::CustodyClient;
Expand All @@ -62,8 +65,18 @@ const DUMMY_PRIVATE_KEY: &str =

/// The cli for the fee sweeper
#[rustfmt::skip]
#[derive(Clone, Debug, Parser)]
#[derive(Parser)]
#[clap(about = "Funds manager server")]
struct Cli {
// --- Authentication --- //

/// The HMAC key to use for authentication
#[clap(long, conflicts_with = "disable_auth", env = "HMAC_KEY")]
hmac_key: Option<String>,
/// Whether to disable authentication
#[clap(long, conflicts_with = "hmac_key")]
disable_auth: bool,

// --- Environment Configs --- //

/// The URL of the relayer to use
Expand Down Expand Up @@ -117,6 +130,30 @@ struct Cli {
datadog_logging: bool,
}

impl Cli {
/// Validate the CLI arguments
fn validate(&self) -> Result<(), String> {
if self.hmac_key.is_none() && !self.disable_auth {
Err("Either --hmac-key or --disable-auth must be provided".to_string())
} else {
Ok(())
}
}

/// Get the HMAC key as a 32-byte array
fn get_hmac_key(&self) -> Option<[u8; 32]> {
self.hmac_key.as_ref().map(|key| {
let decoded = hex::decode(key).expect("Invalid HMAC key");
if decoded.len() != 32 {
panic!("HMAC key must be 32 bytes long");
}
let mut array = [0u8; 32];
array.copy_from_slice(&decoded);
array
})
}
}

/// The server
#[derive(Clone)]
struct Server {
Expand All @@ -136,6 +173,8 @@ struct Server {
pub custody_client: CustodyClient,
/// The AWS config
pub aws_config: SdkConfig,
/// The HMAC key for custody endpoint authentication
pub hmac_key: Option<[u8; 32]>,
}

impl Server {
Expand All @@ -160,6 +199,11 @@ impl Server {
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
cli.validate()?;
if cli.hmac_key.is_none() {
warn!("Authentication is disabled. This is not recommended for production use.");
}

configure_telemetry(
cli.datadog_logging, // datadog_enabled
false, // otlp_enabled
Expand All @@ -179,7 +223,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
// Build an Arbitrum client
let wallet = LocalWallet::from_str(DUMMY_PRIVATE_KEY)?;
let conf = ArbitrumClientConfig {
darkpool_addr: cli.darkpool_address,
darkpool_addr: cli.darkpool_address.clone(),
chain: cli.chain,
rpc_url: cli.rpc_url.clone(),
arb_priv_keys: vec![wallet],
Expand All @@ -190,10 +234,11 @@ async fn main() -> Result<(), Box<dyn Error>> {

// Build the indexer
let mut decryption_keys = vec![DecryptionKey::from_hex_str(&cli.relayer_decryption_key)?];
if let Some(protocol_key) = cli.protocol_decryption_key {
decryption_keys.push(DecryptionKey::from_hex_str(&protocol_key)?);
if let Some(protocol_key) = &cli.protocol_decryption_key {
decryption_keys.push(DecryptionKey::from_hex_str(protocol_key)?);
}

let hmac_key = cli.get_hmac_key();
let relayer_client = RelayerClient::new(&cli.relayer_url, &cli.usdc_mint);
let custody_client =
CustodyClient::new(cli.fireblocks_api_key, cli.fireblocks_api_secret, cli.rpc_url);
Expand All @@ -206,38 +251,41 @@ async fn main() -> Result<(), Box<dyn Error>> {
db_url: cli.db_url,
custody_client,
aws_config: config,
hmac_key,
};

// --- Routes --- //

let server = Arc::new(server);
let ping = warp::get()
.and(warp::path(PING_ROUTE))
.map(|| warp::reply::with_status("PONG", warp::http::StatusCode::OK));

let index_fees = warp::post()
.and(warp::path(INDEX_FEES_ROUTE))
.and(with_server(Arc::new(server.clone())))
.and(with_server(server.clone()))
.and_then(index_fees_handler);

let redeem_fees = warp::post()
.and(warp::path(REDEEM_FEES_ROUTE))
.and(with_server(Arc::new(server.clone())))
.and(with_server(server.clone()))
.and_then(redeem_fees_handler);

let withdraw_custody = warp::post()
.and(warp::path("custody"))
.and(warp::path("quoters"))
.and(warp::path(WITHDRAW_CUSTODY_ROUTE))
.and(warp::body::json())
.and(with_server(Arc::new(server.clone())))
.and(with_hmac_auth(server.clone()))
.map(with_json_body::<WithdrawFundsRequest>)
.and_then(identity)
.and(with_server(server.clone()))
.and_then(quoter_withdraw_handler);

let get_deposit_address = warp::get()
.and(warp::path("custody"))
.and(warp::path("quoters"))
.and(warp::path(GET_DEPOSIT_ADDRESS_ROUTE))
.and(query::<HashMap<String, String>>())
.and(with_server(Arc::new(server.clone())))
.and(with_server(server.clone()))
.and_then(get_deposit_address_handler);

let routes = ping
Expand All @@ -263,6 +311,7 @@ async fn handle_rejection(err: warp::Rejection) -> Result<impl warp::Reply, warp
ApiError::RedemptionError(msg) => (warp::http::StatusCode::BAD_REQUEST, msg),
ApiError::InternalError(msg) => (warp::http::StatusCode::INTERNAL_SERVER_ERROR, msg),
ApiError::BadRequest(msg) => (warp::http::StatusCode::BAD_REQUEST, msg),
ApiError::Unauthenticated(msg) => (warp::http::StatusCode::UNAUTHORIZED, msg),
};
error!("API Error: {:?}", api_error);
Ok(warp::reply::with_status(message.clone(), code))
Expand Down
Loading

0 comments on commit 4725c47

Please sign in to comment.