Skip to main content

cloudillo_proxy/
lib.rs

1//! Reverse proxy module for proxying HTTP and WebSocket traffic to backend servers.
2
3pub mod admin;
4pub mod handler;
5pub mod protocol;
6
7mod prelude;
8
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use url::Url;
13
14use rustls::sign::CertifiedKey;
15use rustls_pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
16
17use crate::prelude::*;
18use cloudillo_types::auth_adapter::ProxySiteConfig;
19
20/// In-memory cache entry for a proxy site
21#[derive(Debug)]
22pub struct ProxySiteEntry {
23	pub site_id: i64,
24	pub domain: Box<str>,
25	pub proxy_type: Box<str>,
26	pub backend_url: Url,
27	pub config: ProxySiteConfig,
28}
29
30/// The proxy site cache, keyed by domain
31pub type ProxySiteCache = Arc<RwLock<HashMap<Box<str>, Arc<ProxySiteEntry>>>>;
32
33/// Create a new empty proxy site cache
34pub fn new_proxy_cache() -> ProxySiteCache {
35	Arc::new(RwLock::new(HashMap::new()))
36}
37
38/// Build a CertifiedKey from PEM-encoded certificate and private key
39fn build_certified_key(cert_pem: &str, key_pem: &str) -> Option<CertifiedKey> {
40	let certs: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(cert_pem.as_bytes())
41		.filter_map(Result::ok)
42		.collect();
43	let key = PrivateKeyDer::from_pem_slice(key_pem.as_bytes()).ok()?;
44	let provider = rustls::crypto::CryptoProvider::get_default()?;
45	CertifiedKey::from_der(certs, key, provider).ok()
46}
47
48/// Reload the proxy site cache from the database
49pub async fn reload_proxy_cache(app: &App) -> ClResult<()> {
50	let sites = app.auth_adapter.list_proxy_sites().await?;
51	let proxy_sites = app.ext::<ProxySiteCache>()?;
52	let mut cache = proxy_sites.write().await;
53	cache.clear();
54
55	// Pre-populate TLS cert cache for proxy sites with valid certs
56	if let Ok(mut cert_cache) = app.certs.write() {
57		for site in &sites {
58			if site.status.as_ref() == "D" {
59				continue;
60			}
61			if let (Some(cert_pem), Some(key_pem)) = (site.cert.as_ref(), site.cert_key.as_ref()) {
62				if let Some(certified_key) = build_certified_key(cert_pem, key_pem) {
63					cert_cache.insert(site.domain.clone(), Arc::new(certified_key));
64				}
65			}
66		}
67	}
68
69	for site in sites {
70		if site.status.as_ref() == "D" {
71			continue;
72		}
73		let url = Url::parse(&site.backend_url).map_err(|e| {
74			warn!("Invalid backend URL for proxy site {}: {}", site.domain, e);
75			Error::ValidationError(format!("invalid backend URL: {}", e))
76		})?;
77		let entry = Arc::new(ProxySiteEntry {
78			site_id: site.site_id,
79			domain: site.domain.clone(),
80			proxy_type: site.proxy_type,
81			backend_url: url,
82			config: site.config,
83		});
84		cache.insert(site.domain, entry);
85	}
86
87	info!("Proxy cache reloaded: {} active sites", cache.len());
88	Ok(())
89}
90
91// vim: ts=4