vicarian 0.5.0

Vicarian is a TLS-first reverse-proxy server with ACME support
mod cleartext;
mod mimetypes;
mod router;
mod services;
mod r#static;
#[cfg(test)]
mod tests;

use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use http::StatusCode;
use pingora_core::{
    ErrorType, listeners::tls::TlsSettings, server::Server as PingoraServer,
    services::listening::Service,
};
use pingora_proxy::Session;
use tracing::info;

use crate::{
    certificates::{
        CertificateRuntime,
        handler::{CertHandler, NoopCallbackHandler},
    },
    config::ProxyBackend,
    RunContext,
    proxy::{
        cleartext::CleartextHandler,
        services::Vicarian,
    },
};

pub const E401: pingora_core::ErrorType = ErrorType::HTTPStatus(StatusCode::UNAUTHORIZED.as_u16());
pub const E404: pingora_core::ErrorType = ErrorType::HTTPStatus(StatusCode::NOT_FOUND.as_u16());
pub const E500: pingora_core::ErrorType = ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.as_u16());

pub const YEAR_IN_SECS: u64 = 31536000;


#[async_trait]
pub trait BackendHandler: Send + Sync {
    async fn handle(&self, session: &mut Session) -> Result<bool>;
}


struct ProxyHandler;

impl ProxyHandler {
    fn new(_backend: &ProxyBackend) -> Self {
        ProxyHandler
    }
}

#[async_trait]
impl BackendHandler for ProxyHandler {
    async fn handle(&self, _session: &mut Session) -> Result<bool> {
        Ok(false)
    }
}

pub fn run_indefinitely(cert_runtime: Arc<CertificateRuntime>, context: Arc<RunContext>) -> Result<()> {
    info!("Starting Proxy");

    let mut pingora_server = PingoraServer::new(None)?;
    pingora_server.bootstrap();

    let vicarian_service = {
        let vicarian = Vicarian::new(cert_runtime.certstore().clone(), context.clone());

        let mut pingora_proxy = pingora_proxy::http_proxy_service(
            &pingora_server.configuration,
            vicarian);

        for addr in &context.config.listen.addrs {
            let cert_handler = CertHandler::new(cert_runtime.certstore().clone());

            // When using the BoringSSL backend `with_callbacks()` is how both the cert
            // resolvers added the settings object is created. However with the rustls
            // backend the resolver is set after the setting object is created (see
            // below). For this reason we need to add a dummy handler (which is never
            // accessed) to create settings. I assume this will get cleaned-up in a
            // later version.
            let dummy_callbacks = NoopCallbackHandler {};
            let mut tls_settings = TlsSettings::with_callbacks(Box::new(dummy_callbacks))?;

            tls_settings.enable_h2();
            tls_settings.set_cert_resolver(Arc::new(cert_handler));

            let mut addr_port = *addr;
            addr_port.set_port(context.config.listen.tls_port);
            let addr_port = addr_port.to_string();
            info!("Binding to {addr_port}");
            pingora_proxy.add_tls_with_settings(&addr_port, None, tls_settings);

        }
        pingora_proxy
    };
    pingora_server.add_service(vicarian_service);


    let redirector = CleartextHandler::new(cert_runtime.acme().clone(), context.config.listen.tls_port);
    let mut cleartext_service = Service::new("HTTP->HTTPS Redirector".to_string(), redirector);

    for addr in &context.config.listen.addrs {
        let mut addr_port = *addr;
        addr_port.set_port(context.config.listen.insecure_port);
        let addr_port = addr_port.to_string();
        info!("Binding to {addr_port}");
        cleartext_service.add_tcp(&addr_port);
    }
    pingora_server.add_service(cleartext_service);

    pingora_server.run(pingora_core::server::RunArgs::default());

    Ok(())
}