Content-Length: 579466 | pFad | https://github.com/sebadob/rauthy/commit/d69845ed772e1454e8ac902fdffb253416cbac14

47 Merge pull request #99 from sebadob/global-ip-blacklist-middleware · sebadob/rauthy@d69845e · GitHub
Skip to content

Commit

Permalink
Merge pull request #99 from sebadob/global-ip-blacklist-middleware
Browse files Browse the repository at this point in the history
Global ip blacklist middleware
  • Loading branch information
sebadob authored Oct 25, 2023
2 parents 5d19d2d + 64b279f commit d69845e
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 32 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions rauthy-common/src/error_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ impl ResponseError for ErrorResponse {
match self.error {
ErrorResponseType::TooManyRequests(not_before_timestamp) => {
HttpResponseBuilder::new(self.status_code())
.insert_header((HEADER_RETRY_NOT_BEFORE, not_before_timestamp))
.insert_header(HEADER_HTML)
.body(serde_json::to_string(&self.message).unwrap())
.append_header((HEADER_RETRY_NOT_BEFORE, not_before_timestamp))
.append_header(HEADER_HTML)
// TODO we could possibly to a small `unsafe` call here to just take
// the content without cloning it -> more efficient, especially for blocked IPs
.body(self.message.clone())
}
_ => HttpResponseBuilder::new(self.status_code())
.content_type(APPLICATION_JSON)
Expand Down
47 changes: 45 additions & 2 deletions rauthy-common/src/ip_blacklist_handler.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::time::Duration;
use tokio::sync::oneshot;
use tracing::error;
use tracing::{debug, error};

#[derive(Debug, PartialEq)]
pub enum IpBlacklistReq {
CheckExp,
Blacklist(IpBlacklist),
BlacklistCheck(IpBlacklistCheck),
BlacklistCleanup(IpBlacklistCleanup),
Expand Down Expand Up @@ -50,15 +52,45 @@ pub struct IpBlacklistCleanup {
}

//github.com/ Handles blacklisted IP's and IP's with failed logins
pub async fn run(rx: flume::Receiver<IpBlacklistReq>) {
pub async fn run(tx: flume::Sender<IpBlacklistReq>, rx: flume::Receiver<IpBlacklistReq>) {
let mut data_blacklist: HashMap<String, DateTime<Utc>> = HashMap::with_capacity(2);
let mut data_failed_logins: HashMap<String, u32> = HashMap::with_capacity(5);

let mut exp_checker_handle = tokio::spawn(spawn_exp_checker(tx.clone()));

loop {
match rx.recv_async().await {
Ok(req) => match req {
IpBlacklistReq::CheckExp => {
debug!("Running IpBlacklistReq::CheckExp");
let now = Utc::now();
let mut remove = Vec::default();
for (k, v) in data_blacklist.iter() {
if &now > v {
remove.push(k.clone());
}
}

debug!("Removing {} IPs in IpBlacklistReq::CheckExp", remove.len());
for key in remove {
data_blacklist.remove(&key);
// TODO generate event
}

if data_blacklist.is_empty() && !exp_checker_handle.is_finished() {
exp_checker_handle.abort();
debug!("IpBlacklist ExpChecker has been stopped");
}
}

IpBlacklistReq::Blacklist(req) => {
data_blacklist.insert(req.ip, req.exp);

if exp_checker_handle.is_finished() {
exp_checker_handle = tokio::spawn(spawn_exp_checker(tx.clone()));
}

// TODO generate event
}

IpBlacklistReq::BlacklistCheck(check) => {
Expand All @@ -74,6 +106,7 @@ pub async fn run(rx: flume::Receiver<IpBlacklistReq>) {
if check.increase_counter {
data_failed_logins.insert(check.ip, 1);
Some(1)
// TODO generate event ?
} else {
None
}
Expand All @@ -92,6 +125,7 @@ pub async fn run(rx: flume::Receiver<IpBlacklistReq>) {

IpBlacklistReq::BlacklistCleanup(req) => {
data_blacklist.remove(&req.ip);
// TODO generate event
}

IpBlacklistReq::LoginCleanup(req) => {
Expand All @@ -108,3 +142,12 @@ pub async fn run(rx: flume::Receiver<IpBlacklistReq>) {
}
}
}

async fn spawn_exp_checker(tx: flume::Sender<IpBlacklistReq>) {
debug!("IpBlacklist ExpChecker has been started");
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
tx.send_async(IpBlacklistReq::CheckExp).await.unwrap();
}
}
1 change: 1 addition & 0 deletions rauthy-handlers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ actix-web-actors = { workspace = true }
actix-web-lab = { workspace = true }
actix-web-validator = "5"
actix-web-grants = { workspace = true }
chrono = { workspace = true }
futures = "0.3"
lazy_static = { workspace = true }
mime_guess = "2"
Expand Down
97 changes: 97 additions & 0 deletions rauthy-handlers/src/middleware/ip_blacklist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::real_ip_from_svc_req;
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
web, Error,
};
use chrono::Utc;
use futures::future::LocalBoxFuture;
use rauthy_common::error_response::{ErrorResponse, ErrorResponseType};
use rauthy_common::ip_blacklist_handler::{IpBlacklistCheck, IpBlacklistReq};
use rauthy_models::app_state::AppState;
use rauthy_models::templates::TooManyRequestsHtml;
use std::future::{ready, Ready};
use std::rc::Rc;
use tokio::sync::oneshot;
use tracing::error;

pub struct RauthyIpBlacklistMiddleware;

// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for RauthyIpBlacklistMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = IpBlacklistMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;

fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(IpBlacklistMiddleware {
service: Rc::new(service),
}))
}
}

pub struct IpBlacklistMiddleware<S> {
service: Rc<S>,
}

impl<S, B> Service<ServiceRequest> for IpBlacklistMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

forward_ready!(service);

fn call(&self, req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);

Box::pin(async move {
let app_state = req
.app_data::<web::Data<AppState>>()
.expect("AppState to be in the Actix context");

if let Some(ip) = real_ip_from_svc_req(&req) {
let (tx, rx) = oneshot::channel();
app_state
.tx_ip_blacklist
.send_async(IpBlacklistReq::BlacklistCheck(IpBlacklistCheck {
ip: ip.clone(),
tx,
}))
.await
.unwrap();
match rx.await {
Ok(exp) => {
if let Some(exp) = exp {
if exp > Utc::now() {
let ts = exp.timestamp();
return Err(Error::from(ErrorResponse::new(
ErrorResponseType::TooManyRequests(ts),
TooManyRequestsHtml::build(&ip, ts),
)));
}
}
}
Err(err) => {
error!("Checking IP Blacklist status in middleware - this should never happen: {:?}", err);
}
}
} else {
error!("Cannot extract IP from HttpRequest - check your Reverse Proxy settings");
}

service.call(req).await
})
}
}
2 changes: 1 addition & 1 deletion rauthy-handlers/src/middleware/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod ip_blacklist;
pub mod logging;
//github.com/ # Rate limiting extractors
pub mod rate_limit;
pub mod session;
6 changes: 4 additions & 2 deletions rauthy-main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use rauthy_common::constants::{
};
use rauthy_common::error_response::ErrorResponse;
use rauthy_common::{ip_blacklist_handler, password_hasher};
use rauthy_handlers::middleware::ip_blacklist::RauthyIpBlacklistMiddleware;
use rauthy_handlers::middleware::logging::RauthyLoggingMiddleware;
use rauthy_handlers::middleware::session::RauthySessionMiddleware;
use rauthy_handlers::openapi::ApiDoc;
Expand Down Expand Up @@ -170,7 +171,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
tx_email.clone(),
tx_events.clone(),
tx_events_router.clone(),
tx_ip_blacklist,
tx_ip_blacklist.clone(),
caches,
)
.await?,
Expand Down Expand Up @@ -265,7 +266,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
tokio::spawn(password_hasher::run());

// spawn ip blacklist handler
tokio::spawn(ip_blacklist_handler::run(rx_ip_blacklist));
tokio::spawn(ip_blacklist_handler::run(tx_ip_blacklist, rx_ip_blacklist));

// spawn remote cache notification service
tokio::spawn(handle_notify(app_state.clone(), rx_notify));
Expand Down Expand Up @@ -401,6 +402,7 @@ async fn actix_main(app_state: web::Data<AppState>) -> std::io::Result<()> {
let mut app = App::new()
// .data shares application state for all workers
.app_data(app_state.clone())
.wrap(RauthyIpBlacklistMiddleware)
.wrap(GrantsMiddleware::with_extractor(
auth::permission_extractor,
))
Expand Down
28 changes: 4 additions & 24 deletions rauthy-service/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,7 @@ pub async fn handle_login_delay(
t if t > 20 => sleep_time_median + t * 20_000,

// 4th blacklist
t if t == 20 => {
20 => {
let not_before = Utc::now().add(chrono::Duration::seconds(3600));
let ts = not_before.timestamp();
let ip = peer_ip.unwrap_or_else(|| "UNKNOWN".to_string());
Expand All @@ -1148,7 +1148,7 @@ pub async fn handle_login_delay(
t if t > 15 => sleep_time_median + t * 15_000,

// 3rd blacklist
t if t == 15 => {
15 => {
let not_before = Utc::now().add(chrono::Duration::seconds(900));
let ts = not_before.timestamp();
let ip = peer_ip.unwrap_or_else(|| "UNKNOWN".to_string());
Expand All @@ -1171,7 +1171,7 @@ pub async fn handle_login_delay(
t if t > 10 => sleep_time_median + t * 10_000,

// 2nd blacklist
t if t == 10 => {
10 => {
let not_before = Utc::now().add(chrono::Duration::seconds(600));
let ts = not_before.timestamp();
let ip = peer_ip.unwrap_or_else(|| "UNKNOWN".to_string());
Expand All @@ -1194,7 +1194,7 @@ pub async fn handle_login_delay(
t if t > 7 => sleep_time_median + t * 5_000,

// 1st blacklist
t if t == 7 => {
7 => {
let not_before = Utc::now().add(chrono::Duration::seconds(60));
let ts = not_before.timestamp();
let ip = peer_ip.unwrap_or_else(|| "UNKNOWN".to_string());
Expand All @@ -1218,26 +1218,6 @@ pub async fn handle_login_delay(

t if t >= 3 => sleep_time_median + t * 2_000,

t if t == 2 => {
let not_before = Utc::now().add(chrono::Duration::seconds(60));
let ts = not_before.timestamp();
let ip = peer_ip.unwrap_or_else(|| "UNKNOWN".to_string());
let html = TooManyRequestsHtml::build(&ip, ts);

data.tx_ip_blacklist
.send_async(IpBlacklistReq::Blacklist(IpBlacklist {
ip,
exp: not_before,
}))
.await
.unwrap();

return Err(ErrorResponse::new(
ErrorResponseType::TooManyRequests(ts),
html,
));
}

_ => sleep_time_median,
};

Expand Down

0 comments on commit d69845e

Please sign in to comment.








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/sebadob/rauthy/commit/d69845ed772e1454e8ac902fdffb253416cbac14

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy