From af1dd4d122c69c8f96f29482cee8a9cd04183aba Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 30 Jan 2025 22:06:17 +0100 Subject: [PATCH] Enforce network IP restriction --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 3 ++- src/extractors/client_auth.rs | 26 +++++++++++++++++++++++--- src/main.rs | 4 ++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffd758d..58e5684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,17 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-remote-ip" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7629b357d4705cf3f1e31f989f48ecd56027112f7d52dcf06dd96ee197065f8e" +dependencies = [ + "actix-web", + "futures-util", + "log", +] + [[package]] name = "actix-router" version = "0.5.3" @@ -1887,6 +1898,7 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" name = "matrix_gateway" version = "0.1.0" dependencies = [ + "actix-remote-ip", "actix-session", "actix-web", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 92762d9..7599277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,5 @@ uuid = { version = "1.12.1", features = ["v4", "serde"] } ipnet = { version = "2.11.0", features = ["serde"] } chrono = "0.4.39" futures-util = "0.3.31" -jwt-simple = { version = "0.12.11", default-features=false, features=["pure-rust"] } \ No newline at end of file +jwt-simple = { version = "0.12.11", default-features=false, features=["pure-rust"] } +actix-remote-ip = "0.1.0" \ No newline at end of file diff --git a/src/extractors/client_auth.rs b/src/extractors/client_auth.rs index e71f57d..0f446ec 100644 --- a/src/extractors/client_auth.rs +++ b/src/extractors/client_auth.rs @@ -1,9 +1,11 @@ use crate::user::{APIClient, APIClientID, UserConfig, UserID}; use crate::utils::curr_time; +use actix_remote_ip::RemoteIP; use actix_web::dev::Payload; use actix_web::{FromRequest, HttpRequest}; use jwt_simple::common::VerificationOptions; use jwt_simple::prelude::{Duration, HS256Key, MACLike}; +use std::net::IpAddr; use std::str::FromStr; pub struct APIClientAuth { @@ -19,7 +21,7 @@ pub struct TokenClaims { } impl APIClientAuth { - async fn extract_auth(req: &HttpRequest) -> Result { + async fn extract_auth(req: &HttpRequest, remote_ip: IpAddr) -> Result { let Some(token) = req.headers().get("x-client-auth") else { return Err(actix_web::error::ErrorBadRequest( "Missing authentication header!", @@ -99,6 +101,19 @@ impl APIClientAuth { )); } + // Check IP restriction + if let Some(net) = client.network { + if !net.contains(&remote_ip) { + log::error!( + "Trying to use client {} from unauthorized IP address: {remote_ip}", + client.id.0 + ); + return Err(actix_web::error::ErrorForbidden( + "This client cannot be used from this IP address!", + )); + } + } + // Check URI & verb if claims.custom.uri != req.uri().to_string() { return Err(actix_web::error::ErrorBadRequest("URI mismatch!")); @@ -115,7 +130,6 @@ impl APIClientAuth { } // TODO : handle payload - // TODO : check for IP restriction // Update last use (if needed) if client.need_update_last_used() { @@ -143,6 +157,12 @@ impl FromRequest for APIClientAuth { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req = req.clone(); - Box::pin(async move { Self::extract_auth(&req).await }) + + let remote_ip = match RemoteIP::from_request(&req, &mut Payload::None).into_inner() { + Ok(ip) => ip, + Err(e) => return Box::pin(async { Err(e) }), + }; + + Box::pin(async move { Self::extract_auth(&req, remote_ip.0).await }) } } diff --git a/src/main.rs b/src/main.rs index a364918..55a3e65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use actix_remote_ip::RemoteIPConfig; use actix_session::config::SessionLifecycle; use actix_session::{storage::RedisSessionStore, SessionMiddleware}; use actix_web::cookie::Key; @@ -35,6 +36,9 @@ async fn main() -> std::io::Result<()> { .session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) .build(), ) + .app_data(web::Data::new(RemoteIPConfig { + proxy: AppConfig::get().proxy_ip.clone(), + })) // Web configuration routes .route("/assets/{tail:.*}", web::get().to(web_ui::static_file)) .route("/", web::get().to(web_ui::home))