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:
		
				
					committed by
					
						
						Dietmar Maurer
					
				
			
			
				
	
			
			
			
						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
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user