Skip to content

Commit d1fc384

Browse files
authored
middleware/block_traffic: Implement block_by_ip() middleware fn (#7435)
1 parent 078bfc9 commit d1fc384

File tree

6 files changed

+58
-11
lines changed

6 files changed

+58
-11
lines changed

src/config/server.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub struct Server {
3737
pub rate_limiter: HashMap<LimitedAction, RateLimiterConfig>,
3838
pub new_version_rate_limit: Option<u32>,
3939
pub blocked_traffic: Vec<(String, Vec<String>)>,
40+
pub blocked_ips: HashSet<IpAddr>,
4041
pub max_allowed_page_offset: u32,
4142
pub page_offset_ua_blocklist: Vec<String>,
4243
pub page_offset_cidr_blocklist: Vec<IpNetwork>,
@@ -113,6 +114,15 @@ impl Server {
113114

114115
let port = env_optional("PORT").unwrap_or(8888);
115116

117+
let blocked_ips = match env_optional::<String>("BLOCKED_IPS") {
118+
None => HashSet::new(),
119+
Some(s) if s.is_empty() => HashSet::new(),
120+
Some(s) => s
121+
.split(',')
122+
.map(|s| s.trim().parse())
123+
.collect::<Result<_, _>>()?,
124+
};
125+
116126
let allowed_origins = AllowedOrigins::from_default_env()?;
117127
let page_offset_ua_blocklist = match env_optional::<String>("WEB_PAGE_OFFSET_UA_BLOCKLIST")
118128
{
@@ -190,6 +200,7 @@ impl Server {
190200
rate_limiter,
191201
new_version_rate_limit: env_optional("MAX_NEW_VERSIONS_DAILY"),
192202
blocked_traffic: blocked_traffic(),
203+
blocked_ips,
193204
max_allowed_page_offset: env_optional("WEB_MAX_ALLOWED_PAGE_OFFSET").unwrap_or(200),
194205
page_offset_ua_blocklist,
195206
page_offset_cidr_blocklist,

src/middleware.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ pub fn apply_axum_middleware(state: AppState, router: Router<(), TimeoutBody<Bod
6262
state.clone(),
6363
require_user_agent::require_user_agent,
6464
))
65+
.layer(from_fn_with_state(
66+
state.clone(),
67+
block_traffic::block_by_ip,
68+
))
6569
.layer(from_fn_with_state(
6670
state.clone(),
6771
block_traffic::block_by_header,

src/middleware/block_traffic.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
//! Middleware that blocks requests if a header matches the given list
2-
//!
3-
//! To use, set the `BLOCKED_TRAFFIC` environment variable to a comma-separated list of pairs
4-
//! containing a header name, an equals sign, and the name of another environment variable that
5-
//! contains the values of that header that should be blocked. For example, set `BLOCKED_TRAFFIC`
6-
//! to `User-Agent=BLOCKED_UAS,X-Real-Ip=BLOCKED_IPS`, `BLOCKED_UAS` to `curl/7.54.0,cargo 1.36.0
7-
//! (c4fcfb725 2019-05-15)`, and `BLOCKED_IPS` to `192.168.0.1,127.0.0.1` to block requests from
8-
//! the versions of curl or Cargo specified or from either of the IPs (values are nonsensical
9-
//! examples). Values of the headers must match exactly.
10-
111
use crate::app::AppState;
122
use crate::middleware::log_request::RequestLogExt;
3+
use crate::middleware::real_ip::RealIp;
134
use crate::util::errors::RouteBlocked;
14-
use axum::extract::MatchedPath;
5+
use axum::extract::{Extension, MatchedPath};
156
use axum::middleware::Next;
167
use axum::response::IntoResponse;
178
use http::StatusCode;
189

10+
/// Middleware that blocks requests if a header matches the given list
11+
///
12+
/// To use, set the `BLOCKED_TRAFFIC` environment variable to a comma-separated list of pairs
13+
/// containing a header name, an equals sign, and the name of another environment variable that
14+
/// contains the values of that header that should be blocked. For example, set `BLOCKED_TRAFFIC`
15+
/// to `User-Agent=BLOCKED_UAS` and `BLOCKED_UAS` to `curl/7.54.0,cargo 1.36.0 (c4fcfb725 2019-05-15)`
16+
/// to block requests from the versions of curl or Cargo specified (values are nonsensical examples).
17+
/// Values of the headers must match exactly.
1918
pub async fn block_by_header<B>(
2019
state: AppState,
2120
req: http::Request<B>,
@@ -40,6 +39,19 @@ pub async fn block_by_header<B>(
4039
next.run(req).await
4140
}
4241

42+
pub async fn block_by_ip<B>(
43+
Extension(real_ip): Extension<RealIp>,
44+
state: AppState,
45+
req: http::Request<B>,
46+
next: Next<B>,
47+
) -> axum::response::Response {
48+
if state.config.blocked_ips.contains(&real_ip) {
49+
rejection_response_from(&state, &req).into_response()
50+
} else {
51+
next.run(req).await
52+
}
53+
}
54+
4355
fn rejection_response_from<B>(state: &AppState, req: &http::Request<B>) -> impl IntoResponse {
4456
let domain_name = &state.config.domain_name;
4557

src/tests/server.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::builders::*;
22
use crate::util::*;
3+
use std::collections::HashSet;
34

45
use ::insta::assert_display_snapshot;
56
use http::{header, Method, StatusCode};
@@ -76,3 +77,16 @@ fn block_traffic_via_arbitrary_header_and_value() {
7677
let resp = anon.run::<()>(req);
7778
assert_eq!(resp.status(), StatusCode::FOUND);
7879
}
80+
81+
#[test]
82+
fn block_traffic_via_ip() {
83+
let (_app, anon) = TestApp::init()
84+
.with_config(|config| {
85+
config.blocked_ips = HashSet::from(["127.0.0.1".parse().unwrap()]);
86+
})
87+
.empty();
88+
89+
let resp = anon.get::<()>("/api/v1/crates");
90+
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
91+
assert_display_snapshot!(resp.into_text());
92+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: src/tests/server.rs
3+
expression: resp.into_text()
4+
---
5+
We are unable to process your request at this time. This usually means that you are in violation of our crawler policy (https://crates.io/policies#crawlers). Please open an issue at https://github.com/rust-lang/crates.io or email [email protected] and provide the request id

src/tests/util/test_app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ fn simple_config() -> config::Server {
411411
rate_limiter: Default::default(),
412412
new_version_rate_limit: Some(10),
413413
blocked_traffic: Default::default(),
414+
blocked_ips: Default::default(),
414415
max_allowed_page_offset: 200,
415416
page_offset_ua_blocklist: vec![],
416417
page_offset_cidr_blocklist: vec![],

0 commit comments

Comments
 (0)