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>,
|
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."))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue