diff --git a/Cargo.lock b/Cargo.lock index 46289c9..f4a843e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,17 @@ dependencies = [ "syn 1.0.109", ] +[[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.1" @@ -1201,11 +1212,11 @@ dependencies = [ name = "oidc-test-client" version = "0.1.0" dependencies = [ + "actix-remote-ip", "actix-web", "askama", "clap", "env_logger", - "futures-util", "lazy_static", "light-openid", "log", diff --git a/Cargo.toml b/Cargo.toml index a62c23f..613d6bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,4 @@ askama = "0.12.0" serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" reqwest = { version = "0.11.16", features = ["json"] } -futures-util = "0.3.28" \ No newline at end of file +actix-remote-ip = "0.1.0" \ No newline at end of file diff --git a/src/app_config.rs b/src/app_config.rs deleted file mode 100644 index 5a2dc5b..0000000 --- a/src/app_config.rs +++ /dev/null @@ -1,58 +0,0 @@ -use clap::Parser; - -const REDIRECT_URI: &str = "/redirect"; - -/// Basic OpenID test client -#[derive(Parser, Debug)] -pub struct AppConfig { - /// Listen URL - #[arg(short, long, env, default_value = "0.0.0.0:7510")] - pub listen_addr: String, - - /// Public URL, the URL where this service is accessible, without the trailing slash - #[arg(short, long, env, default_value = "http://localhost:7510")] - pub public_url: String, - - /// URL where the OpenID configuration can be found - #[arg(short, long, env)] - pub configuration_url: String, - - /// OpenID client ID - #[arg(long, env)] - pub client_id: String, - - /// OpenID client secret - #[arg(long, env)] - pub client_secret: String, - - /// Proxy IP, might end with a "*" - #[clap(long, env)] - pub proxy_ip: Option, -} - -impl AppConfig { - pub fn get() -> &'static Self { - &CONF - } - - pub fn redirect_url(&self) -> String { - format!("{}{}", self.public_url, REDIRECT_URI) - } -} - -lazy_static::lazy_static! { - static ref CONF: AppConfig = { - AppConfig::parse() - }; -} - -#[cfg(test)] -mod test { - use crate::app_config::AppConfig; - - #[test] - fn verify_cli() { - use clap::CommandFactory; - AppConfig::command().debug_assert(); - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 8a74ab8..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -use std::error::Error; - -pub type Res = Result>; - -pub mod app_config; -pub mod remote_ip; diff --git a/src/main.rs b/src/main.rs index 3b10045..7cb4a21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,57 @@ +use actix_remote_ip::{RemoteIP, RemoteIPConfig}; use actix_web::middleware::Logger; use actix_web::{get, web, App, HttpResponse, HttpServer}; use askama::Template; use light_openid::basic_state_manager::BasicStateManager; use light_openid::primitives::OpenIDConfig; -use oidc_test_client::app_config::AppConfig; -use oidc_test_client::remote_ip::RemoteIP; +use clap::Parser; + +const REDIRECT_URI: &str = "/redirect"; + +/// Basic OpenID test client +#[derive(Parser, Debug)] +pub struct AppConfig { + /// Listen URL + #[arg(short, long, env, default_value = "0.0.0.0:7510")] + pub listen_addr: String, + + /// Public URL, the URL where this service is accessible, without the trailing slash + #[arg(short, long, env, default_value = "http://localhost:7510")] + pub public_url: String, + + /// URL where the OpenID configuration can be found + #[arg(short, long, env)] + pub configuration_url: String, + + /// OpenID client ID + #[arg(long, env)] + pub client_id: String, + + /// OpenID client secret + #[arg(long, env)] + pub client_secret: String, + + /// Proxy IP, might end with a "*" + #[clap(long, env)] + pub proxy_ip: Option, +} + +impl AppConfig { + pub fn get() -> &'static Self { + &CONF + } + + pub fn redirect_url(&self) -> String { + format!("{}{}", self.public_url, REDIRECT_URI) + } +} + +lazy_static::lazy_static! { + static ref CONF: AppConfig = { + AppConfig::parse() + }; +} #[get("/assets/bootstrap.min.css")] async fn bootstrap() -> HttpResponse { @@ -24,6 +70,7 @@ async fn cover() -> HttpResponse { #[derive(Template)] #[template(path = "home.html")] struct HomeTemplate { + remote_ip: String, redirect_url: String, } @@ -49,9 +96,10 @@ impl<'a> ErrorTemplate<'a> { } #[get("/")] -async fn home() -> HttpResponse { +async fn home(remote_ip: RemoteIP) -> HttpResponse { HttpResponse::Ok().content_type("text/html").body( HomeTemplate { + remote_ip: remote_ip.0.to_string(), redirect_url: AppConfig::get().redirect_url(), } .render() @@ -169,6 +217,9 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() .wrap(Logger::default()) + .app_data(web::Data::new(RemoteIPConfig { + proxy: CONF.proxy_ip.clone(), + })) .app_data(state_manager.clone()) .service(bootstrap) .service(cover) @@ -181,3 +232,14 @@ async fn main() -> std::io::Result<()> { .run() .await } + +#[cfg(test)] +mod test { + use crate::AppConfig; + + #[test] + fn verify_cli() { + use clap::CommandFactory; + AppConfig::command().debug_assert(); + } +} diff --git a/src/remote_ip.rs b/src/remote_ip.rs deleted file mode 100644 index 31b8706..0000000 --- a/src/remote_ip.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::net::{IpAddr, Ipv6Addr}; - -use crate::app_config::AppConfig; -use actix_web::dev::Payload; -use actix_web::{Error, FromRequest, HttpRequest}; -use futures_util::future::{ready, Ready}; -use std::str::FromStr; - -/// Parse an IP address -pub fn parse_ip(ip: &str) -> Option { - let mut ip = match IpAddr::from_str(ip) { - Ok(ip) => ip, - Err(e) => { - log::warn!("Failed to parse an IP address: {}", e); - return None; - } - }; - - if let IpAddr::V6(ipv6) = &mut ip { - let mut octets = ipv6.octets(); - for o in octets.iter_mut().skip(8) { - *o = 0; - } - ip = IpAddr::V6(Ipv6Addr::from(octets)); - } - - Some(ip) -} - -/// Check if two ips matches -pub fn match_ip(pattern: &str, ip: &str) -> bool { - if pattern.eq(ip) { - return true; - } - - if pattern.ends_with('*') && ip.starts_with(&pattern.replace('*', "")) { - return true; - } - - false -} - -/// Get the remote IP address -pub fn get_remote_ip(req: &HttpRequest, proxy_ip: Option<&str>) -> IpAddr { - let mut ip = req.peer_addr().unwrap().ip(); - - // We check if the request comes from a trusted reverse proxy - if let Some(proxy) = proxy_ip.as_ref() { - if match_ip(proxy, &ip.to_string()) { - if let Some(header) = req.headers().get("X-Forwarded-For") { - let header = header.to_str().unwrap(); - - let remote_ip = if let Some((upstream_ip, _)) = header.split_once(',') { - upstream_ip - } else { - header - }; - - if let Some(upstream_ip) = parse_ip(remote_ip) { - ip = upstream_ip; - } - } - } - } - - ip -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct RemoteIP(pub IpAddr); - -impl From for IpAddr { - fn from(i: RemoteIP) -> Self { - i.0 - } -} - -impl FromRequest for RemoteIP { - type Error = Error; - type Future = Ready>; - - #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - ready(Ok(RemoteIP(get_remote_ip( - req, - AppConfig::get().proxy_ip.as_deref(), - )))) - } -} - -#[cfg(test)] -mod test { - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use std::str::FromStr; - - use crate::remote_ip::{get_remote_ip, parse_ip}; - use actix_web::test::TestRequest; - - #[test] - fn test_get_remote_ip() { - let req = TestRequest::default() - .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) - .to_http_request(); - assert_eq!( - get_remote_ip(&req, None), - "192.168.1.1".parse::().unwrap() - ); - } - - #[test] - fn test_get_remote_ip_from_proxy() { - let req = TestRequest::default() - .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) - .insert_header(("X-Forwarded-For", "1.1.1.1")) - .to_http_request(); - assert_eq!( - get_remote_ip(&req, Some("192.168.1.1")), - "1.1.1.1".parse::().unwrap() - ); - } - - #[test] - fn test_get_remote_ip_from_proxy_2() { - let req = TestRequest::default() - .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) - .insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2")) - .to_http_request(); - assert_eq!( - get_remote_ip(&req, Some("192.168.1.1")), - "1.1.1.1".parse::().unwrap() - ); - } - - #[test] - fn test_get_remote_ip_from_proxy_ipv6() { - let req = TestRequest::default() - .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) - .insert_header(("X-Forwarded-For", "10::1, 1.2.2.2")) - .to_http_request(); - assert_eq!( - get_remote_ip(&req, Some("192.168.1.1")), - "10::".parse::().unwrap() - ); - } - - #[test] - fn test_get_remote_ip_from_no_proxy() { - let req = TestRequest::default() - .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) - .insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2")) - .to_http_request(); - assert_eq!( - get_remote_ip(&req, None), - "192.168.1.1".parse::().unwrap() - ); - } - - #[test] - fn test_get_remote_ip_from_other_proxy() { - let req = TestRequest::default() - .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) - .insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2")) - .to_http_request(); - assert_eq!( - get_remote_ip(&req, Some("192.168.1.2")), - "192.168.1.1".parse::().unwrap() - ); - } - - #[test] - fn parse_bad_ip() { - let ip = parse_ip("badbad"); - assert_eq!(None, ip); - } - - #[test] - fn parse_ip_v4_address() { - let ip = parse_ip("192.168.1.1").unwrap(); - assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))); - } - - #[test] - fn parse_ip_v6_address() { - let ip = parse_ip("2a00:1450:4007:813::200e").unwrap(); - assert_eq!( - ip, - IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x4007, 0x813, 0, 0, 0, 0)) - ); - } - - #[test] - fn parse_ip_v6_address_2() { - let ip = parse_ip("::1").unwrap(); - assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))); - } - - #[test] - fn parse_ip_v6_address_3() { - let ip = parse_ip("a::1").unwrap(); - assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0xa, 0, 0, 0, 0, 0, 0, 0))); - } -} diff --git a/templates/home.html b/templates/home.html index fbb1309..32e1ad9 100644 --- a/templates/home.html +++ b/templates/home.html @@ -4,6 +4,7 @@

Test OIDC Authentication flow.

Get started testing OIDC authentication flow

Redirect URI: {{ redirect_url }}

+

Your IP: {{ remote_ip }}

Start