reverse proxy ip checking

This commit is contained in:
Tyler Murphy 2023-01-28 22:21:18 -05:00
parent b58654fd70
commit 9cbeee4b67
4 changed files with 104 additions and 61 deletions

47
Cargo.lock generated
View file

@ -64,6 +64,16 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "axum-client-ip"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfb5a3ddd6367075d50629546fb46710584016ae7704cd03b6d41cb5be82e5a"
dependencies = [
"axum",
"forwarded-header-value",
]
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.3.2" version = "0.3.2"
@ -187,6 +197,16 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "forwarded-header-value"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
dependencies = [
"nonempty",
"thiserror",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.25" version = "0.3.25"
@ -463,6 +483,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "nonempty"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -780,6 +806,26 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.4" version = "1.1.4"
@ -1134,6 +1180,7 @@ name = "xssbook"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"axum", "axum",
"axum-client-ip",
"bytes", "bytes",
"lazy_static", "lazy_static",
"rand", "rand",

View file

@ -10,6 +10,7 @@ tower-http = { version = "0.3.5", features = ["fs"] }
tower-cookies = "0.8.0" tower-cookies = "0.8.0"
tower = "0.4.13" tower = "0.4.13"
bytes = "1.3.0" bytes = "1.3.0"
axum-client-ip = "0.3.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["std"] } serde_json = { version = "1.0", features = ["std"] }
rusqlite = { version = "0.28.0", features = ["bundled"] } rusqlite = { version = "0.28.0", features = ["bundled"] }

View file

@ -15,7 +15,7 @@ The project is written in rust, so you can build it by running
`cargo build --release` `cargo build --release`
Make sure where you run the binary from, you also move the public folder to. Next, make sure where you are runing the binary from, that you copy the sources public folder to the same directory. The public folder is needed to server html, css, js, and font files.
Finally, the site runs on port `8080`, so its recommended you put it behind a reverse proxy, or you could use a docker container and remap the outsite port (see below). Finally, the site runs on port `8080`, so its recommended you put it behind a reverse proxy, or you could use a docker container and remap the outsite port (see below).
@ -37,6 +37,10 @@ to make the database persistant. Finally, before running the container run
since docker will create a folder there otherwise and it won't work. since docker will create a folder there otherwise and it won't work.
**reverse proxy**
Finally if you are using docker by itself, a reverse proxy, or both, the ip send to the container likily will not be the correct ip. xssbook looks for headers `x-forwarded-for`, `x-real-ip`, and `forwarded` to check for proxies. So make sure to have those headers set. Or if your running just docker, you could also run the docker container on the host network instead of on the bridge network.
**license** **license**
This amazing project is licensed under the WTFPL This amazing project is licensed under the WTFPL

View file

@ -1,14 +1,15 @@
use std::{io::Read, net::SocketAddr}; use std::io::Read;
use axum::{ use axum::{
async_trait, async_trait,
body::HttpBody, body::HttpBody,
extract::{ConnectInfo, FromRequest, FromRequestParts}, extract::{FromRequest, FromRequestParts},
headers::Cookie, headers::Cookie,
http::{request::Parts, Request}, http::{request::Parts, Request},
response::Response, response::Response,
BoxError, RequestExt, TypedHeader, BoxError, RequestExt, TypedHeader,
}; };
use axum_client_ip::ClientIp;
use bytes::Bytes; use bytes::Bytes;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -53,6 +54,7 @@ where
} }
pub struct Log; pub struct Log;
#[async_trait] #[async_trait]
impl<S, B> FromRequest<S, B> for Log impl<S, B> FromRequest<S, B> for Log
where where
@ -63,36 +65,8 @@ where
{ {
type Rejection = Response; type Rejection = Response;
async fn from_request(mut req: Request<B>, state: &S) -> Result<Self> { async fn from_request(req: Request<B>, state: &S) -> Result<Self> {
let Ok(ConnectInfo(info)) = req.extract_parts::<ConnectInfo<SocketAddr>>().await else { parse_body(req, state).await?;
return Ok(Self)
};
let method = req.method().clone();
let path = req
.extensions()
.get::<RouterURI>()
.map_or("", |path| path.0);
let uri = req.uri().clone();
let Ok(bytes) = Bytes::from_request(req, state).await else {
console::log(info.ip(), method.clone(), uri.clone(), Some(path.to_string()), None).await;
return Ok(Self)
};
let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else {
console::log(info.ip(), method.clone(), uri.clone(), Some(path.to_string()), None).await;
return Ok(Self)
};
console::log(
info.ip(),
method.clone(),
uri.clone(),
Some(path.to_string()),
Some(body.to_string()),
)
.await;
Ok(Self) Ok(Self)
} }
} }
@ -110,35 +84,12 @@ where
{ {
type Rejection = Response; type Rejection = Response;
async fn from_request(mut req: Request<B>, state: &S) -> Result<Self> { async fn from_request(req: Request<B>, state: &S) -> Result<Self> {
let Ok(ConnectInfo(info)) = req.extract_parts::<ConnectInfo<SocketAddr>>().await else {
tracing::error!("Failed to read connection info"); let body = match parse_body(req, state).await {
return Err(ResponseCode::InternalServerError.text("Failed to read connection info")); Ok(body) => body,
Err(err) => return Err(err)
}; };
let method = req.method().clone();
let path = req
.extensions()
.get::<RouterURI>()
.map_or("", |path| path.0);
let uri = req.uri().clone();
let Ok(bytes) = Bytes::from_request(req, state).await else {
tracing::error!("Failed to read request body");
return Err(ResponseCode::InternalServerError.text("Failed to read request body"));
};
let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else {
return Err(ResponseCode::BadRequest.text("Invalid utf8 body"))
};
console::log(
info.ip(),
method.clone(),
uri.clone(),
Some(path.to_string()),
Some(body.to_string()),
)
.await;
let Ok(value) = serde_json::from_str::<T>(&body) else { let Ok(value) = serde_json::from_str::<T>(&body) else {
return Err(ResponseCode::BadRequest.text("Invalid request body")) return Err(ResponseCode::BadRequest.text("Invalid request body"))
@ -172,5 +123,45 @@ pub trait Check {
} }
} }
pub async fn parse_body<S, B>(mut req: Request<B>, state: &S) -> Result<String>
where
B: HttpBody + Sync + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
S: Send + Sync
{
let Ok(ClientIp(ip)) = req.extract_parts::<ClientIp>().await else {
tracing::error!("Failed to read client ip");
return Err(ResponseCode::InternalServerError.text("Failed to read client ip"));
};
let method = req.method().clone();
let uri = req.uri().clone();
let path = req
.extensions()
.get::<RouterURI>()
.map_or("", |path| path.0);
let Ok(bytes) = Bytes::from_request(req, state).await else {
tracing::error!("Failed to read request body");
return Err(ResponseCode::InternalServerError.text("Failed to read request body"));
};
let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else {
return Err(ResponseCode::BadRequest.text("Invalid utf8 body"))
};
console::log(
ip,
method,
uri,
Some(path.to_string()),
Some(body.to_string()),
)
.await;
Ok(body)
}
#[derive(Clone)] #[derive(Clone)]
pub struct RouterURI(pub &'static str); pub struct RouterURI(pub &'static str);