use bytes::Bytes;
use http_body_util::Full;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{body::Incoming as IncomingBody, Method, Request, Response};
use hyper_util::rt::TokioIo;
use log::error;
use tokio::net::TcpListener;
use tokio::time::timeout;
use crate::cache_manager::CacheManager;
use crate::constants::MAX_BUF_BYTES;
use crate::error::HttpError;
use crate::parse::GSVQuery;
use crate::utils::{get_token, time_out};
use aws_workload_credentials_provider_common::config::types::ValidatedConfig;
use std::net::SocketAddr;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Server {
listener: Arc<TcpListener>,
cache_mgr: Arc<CacheManager>,
ssrf_token: Arc<String>,
ssrf_headers: Arc<Vec<String>>,
path_prefix: Arc<String>,
max_conn: usize,
}
impl Server {
pub async fn new(
listener: TcpListener,
cfg: &ValidatedConfig,
) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self {
listener: Arc::new(listener),
cache_mgr: Arc::new(CacheManager::new(&cfg.secrets_manager).await?),
ssrf_token: Arc::new(get_token(&cfg.secrets_manager.security.ssrf_env_variables)?),
ssrf_headers: Arc::new(cfg.secrets_manager.security.ssrf_headers.clone()),
path_prefix: Arc::new(cfg.secrets_manager.path_prefix.clone()),
max_conn: cfg.secrets_manager.max_conn,
})
}
pub fn cache_manager(&self) -> Arc<CacheManager> {
self.cache_mgr.clone()
}
pub async fn serve_request(&self) -> Result<(), Box<dyn std::error::Error>> {
let (stream, peer_addr) = self.listener.accept().await?;
stream.set_ttl(1)?; let io = TokioIo::new(stream);
let svr_clone = self.clone();
let rq_cnt = Arc::strong_count(&self.cache_mgr); tokio::task::spawn(async move {
let svc_fn = service_fn(|req: Request<IncomingBody>| async {
svr_clone.complete_req(req, rq_cnt, peer_addr).await
});
let mut http = http1::Builder::new();
let http = http.max_buf_size(MAX_BUF_BYTES);
if let Err(err) = timeout(time_out(), http.serve_connection(io, svc_fn)).await {
error!("Failed to serve connection: {:?}", err);
};
});
Ok(())
}
#[doc(hidden)]
async fn complete_req(
&self,
req: Request<IncomingBody>,
count: usize,
peer_addr: SocketAddr,
) -> Result<Response<Full<Bytes>>, hyper::Error> {
let result = self.get_result(&req, count, peer_addr).await;
match result {
Ok(rsp_body) => Ok(Response::builder()
.body(Full::new(Bytes::from(rsp_body)))
.unwrap()),
Err(e) => Ok(Response::builder()
.status(e.0)
.body(Full::new(Bytes::from(e.1)))
.unwrap()),
}
}
#[doc(hidden)]
async fn get_result(
&self,
req: &Request<IncomingBody>,
count: usize,
peer_addr: SocketAddr,
) -> Result<String, HttpError> {
self.validate_max_conn(req, count)?; self.validate_token(req, peer_addr)?; self.validate_method(req)?;
match req.uri().path() {
"/ping" => Ok("healthy".into()),
"/secretsmanager/get" => {
let qry = GSVQuery::try_from_query(&req.uri().to_string())?;
Ok(self
.cache_mgr
.fetch(
&qry.secret_id,
qry.version_id.as_deref(),
qry.version_stage.as_deref(),
qry.refresh_now,
qry.role_arn.as_deref(),
)
.await?)
}
path if path.starts_with(self.path_prefix.as_str()) => {
let qry = GSVQuery::try_from_path_query(&req.uri().to_string(), &self.path_prefix)?;
Ok(self
.cache_mgr
.fetch(
&qry.secret_id,
qry.version_id.as_deref(),
qry.version_stage.as_deref(),
qry.refresh_now,
qry.role_arn.as_deref(),
)
.await?)
}
_ => Err(HttpError(404, "Not found".into())),
}
}
#[doc(hidden)]
fn validate_max_conn(
&self,
req: &Request<IncomingBody>,
count: usize,
) -> Result<(), HttpError> {
let limit = if req.uri().path() == "/ping" {
self.max_conn + 3
} else {
self.max_conn + 1
};
if count <= limit {
return Ok(());
}
Err(HttpError(429, "Connection limit exceeded".into()))
}
#[doc(hidden)]
fn validate_token(
&self,
req: &Request<IncomingBody>,
peer_addr: SocketAddr,
) -> Result<(), HttpError> {
if req.uri().path() == "/ping" {
return Ok(());
}
let headers = req.headers();
let method = req.method();
let path = req.uri().path();
if headers.contains_key("X-Forwarded-For") {
error!(
"Rejecting request with X-Forwarded-For header; \
peer={}, method={}, path={}",
peer_addr, method, path
);
return Err(HttpError(400, "Forwarded".into()));
}
let mut token_header_present = false;
for header in self.ssrf_headers.iter() {
if headers.contains_key(header) {
token_header_present = true;
if headers[header] == self.ssrf_token.as_str() {
return Ok(());
}
}
}
if token_header_present {
error!(
"Rejecting request with incorrect SSRF token; \
reason=token_mismatch, peer={}, method={}, path={}",
peer_addr, method, path
);
} else {
error!(
"Rejecting request with incorrect SSRF token; \
reason=missing_header, peer={}, method={}, path={}",
peer_addr, method, path
);
}
Err(HttpError(403, "Bad Token".into()))
}
#[doc(hidden)]
fn validate_method(&self, req: &Request<IncomingBody>) -> Result<(), HttpError> {
if *req.method() == Method::GET {
return Ok(());
}
Err(HttpError(405, "Not allowed".into()))
}
}