Enforce network IP restriction

This commit is contained in:
Pierre HUBERT 2025-01-30 22:06:17 +01:00
parent 501520a9df
commit af1dd4d122
4 changed files with 41 additions and 4 deletions

12
Cargo.lock generated
View File

@ -68,6 +68,17 @@ dependencies = [
"syn", "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]] [[package]]
name = "actix-router" name = "actix-router"
version = "0.5.3" version = "0.5.3"
@ -1887,6 +1898,7 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
name = "matrix_gateway" name = "matrix_gateway"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-remote-ip",
"actix-session", "actix-session",
"actix-web", "actix-web",
"anyhow", "anyhow",

View File

@ -26,3 +26,4 @@ ipnet = { version = "2.11.0", features = ["serde"] }
chrono = "0.4.39" chrono = "0.4.39"
futures-util = "0.3.31" futures-util = "0.3.31"
jwt-simple = { version = "0.12.11", default-features=false, features=["pure-rust"] } jwt-simple = { version = "0.12.11", default-features=false, features=["pure-rust"] }
actix-remote-ip = "0.1.0"

View File

@ -1,9 +1,11 @@
use crate::user::{APIClient, APIClientID, UserConfig, UserID}; use crate::user::{APIClient, APIClientID, UserConfig, UserID};
use crate::utils::curr_time; use crate::utils::curr_time;
use actix_remote_ip::RemoteIP;
use actix_web::dev::Payload; use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest}; use actix_web::{FromRequest, HttpRequest};
use jwt_simple::common::VerificationOptions; use jwt_simple::common::VerificationOptions;
use jwt_simple::prelude::{Duration, HS256Key, MACLike}; use jwt_simple::prelude::{Duration, HS256Key, MACLike};
use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
pub struct APIClientAuth { pub struct APIClientAuth {
@ -19,7 +21,7 @@ pub struct TokenClaims {
} }
impl APIClientAuth { impl APIClientAuth {
async fn extract_auth(req: &HttpRequest) -> Result<Self, actix_web::Error> { async fn extract_auth(req: &HttpRequest, remote_ip: IpAddr) -> Result<Self, actix_web::Error> {
let Some(token) = req.headers().get("x-client-auth") else { let Some(token) = req.headers().get("x-client-auth") else {
return Err(actix_web::error::ErrorBadRequest( return Err(actix_web::error::ErrorBadRequest(
"Missing authentication header!", "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 // Check URI & verb
if claims.custom.uri != req.uri().to_string() { if claims.custom.uri != req.uri().to_string() {
return Err(actix_web::error::ErrorBadRequest("URI mismatch!")); return Err(actix_web::error::ErrorBadRequest("URI mismatch!"));
@ -115,7 +130,6 @@ impl APIClientAuth {
} }
// TODO : handle payload // TODO : handle payload
// TODO : check for IP restriction
// Update last use (if needed) // Update last use (if needed)
if client.need_update_last_used() { if client.need_update_last_used() {
@ -143,6 +157,12 @@ impl FromRequest for APIClientAuth {
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let req = req.clone(); 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 })
} }
} }

View File

@ -1,3 +1,4 @@
use actix_remote_ip::RemoteIPConfig;
use actix_session::config::SessionLifecycle; use actix_session::config::SessionLifecycle;
use actix_session::{storage::RedisSessionStore, SessionMiddleware}; use actix_session::{storage::RedisSessionStore, SessionMiddleware};
use actix_web::cookie::Key; use actix_web::cookie::Key;
@ -35,6 +36,9 @@ async fn main() -> std::io::Result<()> {
.session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) .session_lifecycle(SessionLifecycle::BrowserSession(Default::default()))
.build(), .build(),
) )
.app_data(web::Data::new(RemoteIPConfig {
proxy: AppConfig::get().proxy_ip.clone(),
}))
// Web configuration routes // Web configuration routes
.route("/assets/{tail:.*}", web::get().to(web_ui::static_file)) .route("/assets/{tail:.*}", web::get().to(web_ui::static_file))
.route("/", web::get().to(web_ui::home)) .route("/", web::get().to(web_ui::home))