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:
parent
b64e9a97f3
commit
29633e2fe9
|
@ -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."))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue