product-os-proxy 0.0.19

Product OS : Proxy builds on the work of hudsucker, taking it to the next level with a man-in-the-middle proxy server that can tunnel traffic through a VPN utilising Product OS : VPN.
//! Middleware that serves CA download and setup endpoints on the proxy.

use std::sync::Arc;

use async_trait::async_trait;
use product_os_http::{Response, StatusCode};
use product_os_http_body::BodyBytes as Body;
use product_os_server::{BeforeResult, ProductOSMiddleware, ProductOSMiddlewareAsync, RequestData};

/// Paths served by the CA setup middleware.
pub const CA_PEM_PATH: &str = "/_product-os/ca.pem";
/// HTML setup wizard served when `serveCertEndpoint` is enabled.
pub const CA_SETUP_PATH: &str = "/_product-os/setup";

/// Wraps an inner middleware and intercepts CA setup HTTP requests.
pub struct CaServeMiddleware {
    inner: Arc<dyn ProductOSMiddlewareAsync>,
    cert_pem: Vec<u8>,
    proxy_host: String,
    proxy_port: u16,
}

impl CaServeMiddleware {
    /// Create middleware that serves the root CA PEM and a minimal setup page.
    pub fn new(
        inner: Arc<dyn ProductOSMiddlewareAsync>,
        cert_pem: Vec<u8>,
        proxy_host: String,
        proxy_port: u16,
    ) -> Self {
        Self {
            inner,
            cert_pem,
            proxy_host,
            proxy_port,
        }
    }

    fn setup_html(&self) -> String {
        format!(
            r#"<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Product OS Proxy CA Setup</title></head>
<body>
<h1>Product OS Proxy — CA Setup</h1>
<p>Download the MITM root CA and install it in your system trust store.</p>
<p><a href="{CA_PEM_PATH}">Download ca.pem</a></p>
<p>Configure your browser or system to use HTTP proxy <code>{host}:{port}</code>.</p>
<p>Then run <code>pos-proxy cert guide</code> for platform-specific trust steps.</p>
</body></html>"#,
            host = self.proxy_host,
            port = self.proxy_port,
        )
    }
}

impl ProductOSMiddleware for CaServeMiddleware {}

#[async_trait]
impl ProductOSMiddlewareAsync for CaServeMiddleware {
    async fn before(&self, request: product_os_http::Request<Body>) -> BeforeResult {
        let path = request.uri().path();
        if path == CA_PEM_PATH {
            return BeforeResult::Bypass(
                Response::builder()
                    .status(StatusCode::OK)
                    .header("content-type", "application/x-pem-file")
                    .body(Body::from(self.cert_pem.clone()))
                    .unwrap_or_else(|_| Response::new(Body::empty())),
            );
        }
        if path == CA_SETUP_PATH {
            let html = self.setup_html();
            return BeforeResult::Bypass(
                Response::builder()
                    .status(StatusCode::OK)
                    .header("content-type", "text/html; charset=utf-8")
                    .body(Body::from(html))
                    .unwrap_or_else(|_| Response::new(Body::empty())),
            );
        }
        self.inner.before(request).await
    }

    async fn after(
        &self,
        response: product_os_http::Response<Body>,
        request_data: RequestData,
    ) -> product_os_http::Response<Body> {
        self.inner.after(response, request_data).await
    }

    async fn success(
        &self,
        response: product_os_http::Response<Body>,
        request_data: RequestData,
    ) -> product_os_http::Response<Body> {
        self.inner.success(response, request_data).await
    }

    async fn failure(
        &self,
        response: product_os_http::Response<Body>,
        request_data: RequestData,
    ) -> product_os_http::Response<Body> {
        self.inner.failure(response, request_data).await
    }

    async fn error(&self) -> product_os_http::Response<Body> {
        self.inner.error().await
    }

    async fn before_product_os_response(
        &self,
        request: product_os_http::Request<Body>,
    ) -> BeforeResult {
        self.inner.before_product_os_response(request).await
    }

    async fn after_product_os_response(
        &self,
        response: product_os_request::ProductOSResponse<Body>,
        request_data: RequestData,
    ) -> product_os_http::Response<Body> {
        self.inner
            .after_product_os_response(response, request_data)
            .await
    }
}