server/rest: forward real client IP on proxied request

needs new proxmox dependency to get the RpcEnvironment changes,
adding client_ip getter and setter.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2020-10-15 17:43:42 +02:00 committed by Dietmar Maurer
parent b64e9a97f3
commit 29633e2fe9
3 changed files with 48 additions and 7 deletions

View File

@ -138,6 +138,7 @@ fn create_ticket(
path: Option<String>, path: Option<String>,
privs: Option<String>, privs: Option<String>,
port: Option<u16>, port: Option<u16>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
match authenticate_user(&username, &password, path, privs, port) { match authenticate_user(&username, &password, path, privs, port) {
Ok(true) => { Ok(true) => {
@ -157,7 +158,11 @@ fn create_ticket(
"username": username, "username": username,
})), })),
Err(err) => { 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()); log::error!("authentication failure; rhost={} user={} msg={}", client_ip, username, err.to_string());
Err(http_err!(UNAUTHORIZED, "permission check failed.")) Err(http_err!(UNAUTHORIZED, "permission check failed."))
} }

View File

@ -7,6 +7,7 @@ pub struct RestEnvironment {
env_type: RpcEnvironmentType, env_type: RpcEnvironmentType,
result_attributes: Value, result_attributes: Value,
user: Option<String>, user: Option<String>,
client_ip: Option<std::net::SocketAddr>,
} }
impl RestEnvironment { impl RestEnvironment {
@ -14,6 +15,7 @@ impl RestEnvironment {
Self { Self {
result_attributes: json!({}), result_attributes: json!({}),
user: None, user: None,
client_ip: None,
env_type, env_type,
} }
} }
@ -40,4 +42,12 @@ impl RpcEnvironment for RestEnvironment {
fn get_user(&self) -> Option<String> { fn get_user(&self) -> Option<String> {
self.user.clone() 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()
}
} }

View File

@ -9,13 +9,15 @@ use std::task::{Context, Poll};
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use futures::future::{self, FutureExt, TryFutureExt}; use futures::future::{self, FutureExt, TryFutureExt};
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
use hyper::header; use hyper::header::{self, HeaderMap};
use hyper::http::request::Parts; use hyper::http::request::Parts;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use lazy_static::lazy_static;
use serde_json::{json, Value}; use serde_json::{json, Value};
use tokio::fs::File; use tokio::fs::File;
use tokio::time::Instant; use tokio::time::Instant;
use url::form_urlencoded; use url::form_urlencoded;
use regex::Regex;
use proxmox::http_err; use proxmox::http_err;
use proxmox::api::{ 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 { impl tower_service::Service<Request<Body>> for ApiService {
type Response = Response<Body>; type Response = Response<Body>;
type Error = Error; type Error = Error;
@ -141,9 +154,12 @@ impl tower_service::Service<Request<Body>> for ApiService {
let method = req.method().clone(); let method = req.method().clone();
let config = Arc::clone(&self.api_config); 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 { async move {
let response = match handle_request(config, req).await { let response = match handle_request(config, req, &peer).await {
Ok(response) => response, Ok(response) => response,
Err(err) => { Err(err) => {
let (err, code) = match err.downcast_ref::<HttpError>() { let (err, code) = match err.downcast_ref::<HttpError>() {
@ -246,6 +262,7 @@ async fn proxy_protected_request(
info: &'static ApiMethod, info: &'static ApiMethod,
mut parts: Parts, mut parts: Parts,
req_body: Body, req_body: Body,
peer: &std::net::SocketAddr,
) -> Result<Response<Body>, Error> { ) -> Result<Response<Body>, Error> {
let mut uri_parts = parts.uri.clone().into_parts(); let mut uri_parts = parts.uri.clone().into_parts();
@ -256,7 +273,10 @@ async fn proxy_protected_request(
parts.uri = new_uri; 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; let reload_timezone = info.reload_timezone;
@ -505,7 +525,11 @@ fn check_auth(
Ok(userid) 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(); 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 env_type = api.env_type();
let mut rpcenv = RestEnvironment::new(env_type); let mut rpcenv = RestEnvironment::new(env_type);
rpcenv.set_client_ip(Some(*peer));
let user_info = CachedUserInfo::new()?; let user_info = CachedUserInfo::new()?;
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000); 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 { 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 { } else {
handle_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await handle_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await
}; };