Bugfix/credential proxy sequencing (#5187)
* using common middleware for all http servers * improved span handling in credential-proxy * ensure increase in sequence number upon making deposit * added explicit connect options for the db * fixed further instances of incorrect span instrumentation * batch deposit requests together to improve concurrency * ignore cancelled requests * updated credential proxy version to 0.1.4 * adjusted Dockerfile with new binary location * log binary version on startup * reduce default log level * guard against unavaiable commit sha * apply review comments: dont exit(0), instead just shutdown normally * add skip_webhook parameter to obtain-async * removing dead code
This commit is contained in:
committed by
GitHub
parent
645be5fa22
commit
feefde9022
@@ -0,0 +1,115 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::http::{header, HeaderValue, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{extract::Request, response::Response};
|
||||
use futures::future::BoxFuture;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tower::{Layer, Service};
|
||||
use tracing::{debug, instrument, trace};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthLayer {
|
||||
bearer_token: Arc<Zeroizing<String>>,
|
||||
}
|
||||
|
||||
impl AuthLayer {
|
||||
pub fn new(bearer_token: Arc<Zeroizing<String>>) -> Self {
|
||||
AuthLayer { bearer_token }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for AuthLayer {
|
||||
type Service = RequireAuth<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
RequireAuth::new(inner, self.bearer_token.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequireAuth<S> {
|
||||
inner: S,
|
||||
bearer_token: Arc<Zeroizing<String>>,
|
||||
}
|
||||
|
||||
impl<S> RequireAuth<S> {
|
||||
pub fn new(inner: S, bearer_token: Arc<Zeroizing<String>>) -> Self {
|
||||
RequireAuth {
|
||||
inner,
|
||||
bearer_token,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_auth_header(&self, header: Option<&HeaderValue>) -> Result<(), &'static str> {
|
||||
let Some(token) = header else {
|
||||
trace!("missing header");
|
||||
return Err("`Authorization` header is missing");
|
||||
};
|
||||
|
||||
let Ok(authorization) = token.to_str() else {
|
||||
trace!("invalid header");
|
||||
return Err("`Authorization` header contains invalid characters");
|
||||
};
|
||||
|
||||
debug!("header value: '{authorization}'");
|
||||
|
||||
let split = authorization.split_once(' ');
|
||||
let bearer_token = match split {
|
||||
// Found proper bearer
|
||||
Some(("Bearer", contents)) => contents,
|
||||
// Found empty bearer;
|
||||
_ if authorization == "Bearer" => "",
|
||||
// Found nothing
|
||||
_ => return Err("`Authorization` header must be a bearer token"),
|
||||
};
|
||||
|
||||
debug!("parsed token: '{bearer_token}'");
|
||||
|
||||
if self.bearer_token.is_empty() && bearer_token.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
if bearer_token.is_empty() {
|
||||
return Err("`Authorization` header must contain non-empty `Bearer` token");
|
||||
}
|
||||
|
||||
if self.bearer_token.as_str() != bearer_token {
|
||||
return Err("`Authorization` header does not contain the correct `Bearer` token");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Service<Request> for RequireAuth<S>
|
||||
where
|
||||
S: Service<Request, Response = Response> + Send + 'static,
|
||||
S: Send + Sync + 'static,
|
||||
S::Future: Send + 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
#[instrument(skip_all, fields(uri = %req.uri()))]
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
debug!("checking the auth");
|
||||
|
||||
let auth_header = req.headers().get(header::AUTHORIZATION);
|
||||
|
||||
match self.check_auth_header(auth_header) {
|
||||
Ok(_authorised) => Box::pin(self.inner.call(req)),
|
||||
Err(err) => {
|
||||
Box::pin(async move { Ok((StatusCode::UNAUTHORIZED, err).into_response()) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::http::header::{HOST, USER_AGENT};
|
||||
use axum::http::HeaderValue;
|
||||
use axum::middleware::Next;
|
||||
use axum::response::IntoResponse;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use colored::Colorize;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
InsecureClientIp(addr): InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
// TODO dz use `OriginalUri` extractor to get full URI even for nested
|
||||
// routers if routes aren't logged correctly in handlers
|
||||
fn header_map(header: Option<&HeaderValue>, msg: String) -> String {
|
||||
header
|
||||
.map(|x| x.to_str().unwrap_or(&msg).to_string())
|
||||
.unwrap_or(msg)
|
||||
}
|
||||
|
||||
let method = request.method().to_string().green();
|
||||
let uri = request.uri().to_string().blue();
|
||||
let agent = header_map(
|
||||
request.headers().get(USER_AGENT),
|
||||
"Unknown User Agent".to_string(),
|
||||
);
|
||||
|
||||
let host = header_map(request.headers().get(HOST), "Unknown Host".to_string());
|
||||
|
||||
let start = Instant::now();
|
||||
// run request through all middleware, incl. extractors
|
||||
let res = next.run(request).await;
|
||||
let time_taken = start.elapsed();
|
||||
let status = res.status();
|
||||
let print_status = if status.is_client_error() || status.is_server_error() {
|
||||
status.to_string().red()
|
||||
} else if status.is_success() {
|
||||
status.to_string().green()
|
||||
} else {
|
||||
status.to_string().yellow()
|
||||
};
|
||||
|
||||
let taken = "time taken".bold();
|
||||
|
||||
let time_taken = match time_taken.as_millis() {
|
||||
ms if ms > 500 => format!("{taken}: {}", format!("{ms}ms").red()),
|
||||
ms if ms > 200 => format!("{taken}: {}", format!("{ms}ms").yellow()),
|
||||
ms if ms > 50 => format!("{taken}: {}", format!("{ms}ms").bright_yellow()),
|
||||
ms => format!("{taken}: {ms}ms"),
|
||||
};
|
||||
|
||||
let agent_str = "agent".bold();
|
||||
|
||||
info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}");
|
||||
|
||||
res
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bearer_auth;
|
||||
pub mod logging;
|
||||
Reference in New Issue
Block a user