Skip to main content

cloudillo_proxy/
lib.rs

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