[pbs-devel] [PATCH backup] server/rest: forward real client IP on proxied request
Thomas Lamprecht
t.lamprecht at proxmox.com
Thu Oct 15 17:43:42 CEST 2020
needs new proxmox dependency to get the RpcEnvironment changes,
adding client_ip getter and setter.
Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
---
NOTE: needs new (not yet bumped) proxmox crate for the client_ip setter/getter
src/api2/access.rs | 7 ++++++-
src/server/environment.rs | 10 ++++++++++
src/server/rest.rs | 38 ++++++++++++++++++++++++++++++++------
3 files changed, 48 insertions(+), 7 deletions(-)
diff --git a/src/api2/access.rs b/src/api2/access.rs
index 81666bda..19b128b1 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -138,6 +138,7 @@ fn create_ticket(
path: Option<String>,
privs: Option<String>,
port: Option<u16>,
+ rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
match authenticate_user(&username, &password, path, privs, port) {
Ok(true) => {
@@ -157,7 +158,11 @@ fn create_ticket(
"username": username,
})),
Err(err) => {
- let client_ip = "unknown"; // $rpcenv->get_client_ip() || '';
+ let client_ip = match rpcenv.get_client_ip().map(|addr| addr.ip()) {
+ Some(ip) => format!("{}", ip),
+ None => "unknown".into(),
+ };
+
log::error!("authentication failure; rhost={} user={} msg={}", client_ip, username, err.to_string());
Err(http_err!(UNAUTHORIZED, "permission check failed."))
}
diff --git a/src/server/environment.rs b/src/server/environment.rs
index 10ff958c..5fbff307 100644
--- a/src/server/environment.rs
+++ b/src/server/environment.rs
@@ -7,6 +7,7 @@ pub struct RestEnvironment {
env_type: RpcEnvironmentType,
result_attributes: Value,
user: Option<String>,
+ client_ip: Option<std::net::SocketAddr>,
}
impl RestEnvironment {
@@ -14,6 +15,7 @@ impl RestEnvironment {
Self {
result_attributes: json!({}),
user: None,
+ client_ip: None,
env_type,
}
}
@@ -40,4 +42,12 @@ impl RpcEnvironment for RestEnvironment {
fn get_user(&self) -> Option<String> {
self.user.clone()
}
+
+ fn set_client_ip(&mut self, client_ip: Option<std::net::SocketAddr>) {
+ self.client_ip = client_ip;
+ }
+
+ fn get_client_ip(&self) -> Option<std::net::SocketAddr> {
+ self.client_ip.clone()
+ }
}
diff --git a/src/server/rest.rs b/src/server/rest.rs
index d145573f..98e56039 100644
--- a/src/server/rest.rs
+++ b/src/server/rest.rs
@@ -9,13 +9,15 @@ use std::task::{Context, Poll};
use anyhow::{bail, format_err, Error};
use futures::future::{self, FutureExt, TryFutureExt};
use futures::stream::TryStreamExt;
-use hyper::header;
+use hyper::header::{self, HeaderMap};
use hyper::http::request::Parts;
use hyper::{Body, Request, Response, StatusCode};
+use lazy_static::lazy_static;
use serde_json::{json, Value};
use tokio::fs::File;
use tokio::time::Instant;
use url::form_urlencoded;
+use regex::Regex;
use proxmox::http_err;
use proxmox::api::{
@@ -127,6 +129,17 @@ fn log_response(
}
}
+fn get_proxied_peer(headers: &HeaderMap) -> Option<std::net::SocketAddr> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r#"for="([^"]+)""#).unwrap();
+ }
+ let forwarded = headers.get(header::FORWARDED)?.to_str().ok()?;
+ let capture = RE.captures(&forwarded)?;
+ let rhost = capture.get(1)?.as_str();
+
+ rhost.parse().ok()
+}
+
impl tower_service::Service<Request<Body>> for ApiService {
type Response = Response<Body>;
type Error = Error;
@@ -141,9 +154,12 @@ impl tower_service::Service<Request<Body>> for ApiService {
let method = req.method().clone();
let config = Arc::clone(&self.api_config);
- let peer = self.peer;
+ let peer = match get_proxied_peer(req.headers()) {
+ Some(proxied_peer) => proxied_peer,
+ None => self.peer,
+ };
async move {
- let response = match handle_request(config, req).await {
+ let response = match handle_request(config, req, &peer).await {
Ok(response) => response,
Err(err) => {
let (err, code) = match err.downcast_ref::<HttpError>() {
@@ -246,6 +262,7 @@ async fn proxy_protected_request(
info: &'static ApiMethod,
mut parts: Parts,
req_body: Body,
+ peer: &std::net::SocketAddr,
) -> Result<Response<Body>, Error> {
let mut uri_parts = parts.uri.clone().into_parts();
@@ -256,7 +273,10 @@ async fn proxy_protected_request(
parts.uri = new_uri;
- let request = Request::from_parts(parts, req_body);
+ let mut request = Request::from_parts(parts, req_body);
+ request
+ .headers_mut()
+ .insert(header::FORWARDED, format!("for=\"{}\";", peer).parse().unwrap());
let reload_timezone = info.reload_timezone;
@@ -505,7 +525,11 @@ fn check_auth(
Ok(userid)
}
-async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<Response<Body>, Error> {
+async fn handle_request(
+ api: Arc<ApiConfig>,
+ req: Request<Body>,
+ peer: &std::net::SocketAddr,
+) -> Result<Response<Body>, Error> {
let (parts, body) = req.into_parts();
@@ -517,6 +541,8 @@ async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<Respo
let env_type = api.env_type();
let mut rpcenv = RestEnvironment::new(env_type);
+ rpcenv.set_client_ip(Some(*peer));
+
let user_info = CachedUserInfo::new()?;
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
@@ -571,7 +597,7 @@ async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<Respo
}
let result = if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
- proxy_protected_request(api_method, parts, body).await
+ proxy_protected_request(api_method, parts, body, peer).await
} else {
handle_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await
};
--
2.27.0
More information about the pbs-devel
mailing list