use axum::Router;
use axum::extract::Request;
use axum::http::StatusCode;
use axum::middleware::{self, Next};
use axum::response::Response;
use std::net::SocketAddr;
use super::{FilterStore, client_ip, policy_allows};
pub fn apply<S>(router: Router<S>, store: FilterStore) -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
router.layer(middleware::from_fn(move |req: Request, next: Next| {
let store = store.clone();
async move { filter_gate(req, next, store).await }
}))
}
async fn filter_gate(req: Request, next: Next, store: FilterStore) -> Result<Response, StatusCode> {
let peer = req
.extensions()
.get::<axum::extract::ConnectInfo<SocketAddr>>()
.map(|c| c.0);
let ip = client_ip(&req, peer);
let path = req.uri().path();
let allowed = store.read(|policy| policy_allows(policy, ip, path));
if allowed {
Ok(next.run(req).await)
} else {
crate::metrics::record_filter_denied();
Err(StatusCode::FORBIDDEN)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::{FilterPolicy, FilterRule};
use axum::body::Body;
use axum::http::Request;
use axum::routing::get;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tower::ServiceExt;
async fn ok_handler() -> &'static str {
"ok"
}
fn policy_deny_ip(ip: IpAddr) -> FilterStore {
FilterStore::new(FilterPolicy {
deny: vec![FilterRule::Ip(ip)],
allow: Vec::new(),
})
}
#[tokio::test]
async fn blocks_denied_ip() {
let denied = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 50));
let app = apply(
Router::new().route("/", get(ok_handler)),
policy_deny_ip(denied),
);
let mut req = Request::get("/").body(Body::empty()).unwrap();
req.extensions_mut()
.insert(axum::extract::ConnectInfo(SocketAddr::new(denied, 1234)));
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn allows_other_ip() {
let denied = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 50));
let allowed = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1));
let app = apply(
Router::new().route("/", get(ok_handler)),
policy_deny_ip(denied),
);
let mut req = Request::get("/").body(Body::empty()).unwrap();
req.extensions_mut()
.insert(axum::extract::ConnectInfo(SocketAddr::new(allowed, 1234)));
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
}