Files
RustProxy/src/config/model.rs
T
2026-10-20 22:09:50 +02:00

315 lines
12 KiB
Rust

use crate::{
acl::{Acl, CompiledAcl},
headers::{CompiledHeaders, HeaderRules},
};
use anyhow::{ensure, Context, Result};
use http::{header::HeaderValue, uri::Authority};
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
sync::Arc,
};
use url::{Host, Url};
pub const MAX_CONFIG_BYTES: usize = 262_144;
pub const MAX_BACKENDS: usize = 128;
pub const MAX_ROUTES: usize = 256;
fn legacy_server_header_default() -> crate::settings::ServerHeader {
crate::settings::ServerHeader::legacy_compat_default()
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub administration: crate::settings::Administration,
#[serde(default)]
pub logging: crate::logging::Settings,
#[serde(default)]
pub error_log: crate::logging::ErrorLogSettings,
#[serde(default)]
pub prometheus: crate::prometheus::Settings,
#[serde(default)]
pub geoip_updates: crate::geoip::UpdateSettings,
#[serde(default)]
pub listeners: crate::settings::Listeners,
/// Legacy/default HTTP/2 lifecycle limits for listeners that do not yet
/// persist an explicit `binding.http2_settings` object.
#[serde(default)]
pub http2: crate::settings::Http2Connections,
#[serde(default)]
pub docker_agents: crate::docker::Settings,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub streams: Vec<crate::stream::Host>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub redirects: Vec<crate::redirect::Host>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub not_found_hosts: Vec<crate::not_found::Host>,
#[serde(default)]
pub cache: crate::cache::Settings,
#[serde(default)]
pub transform: crate::transform::Settings,
#[serde(default)]
pub pages: crate::pages::GlobalPages,
#[serde(default)]
pub compression: crate::compression::Settings,
#[serde(default = "legacy_server_header_default")]
pub server_header: crate::settings::ServerHeader,
#[serde(default)]
pub diagnostic_headers: crate::settings::DiagnosticHeaders,
#[serde(default)]
pub forwarding: crate::forwarding::Settings,
#[serde(default, skip_serializing_if = "crate::tls::Settings::is_empty")]
pub tls: crate::tls::Settings,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub web_services: Vec<crate::web_server::Service>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub php_fpm_profiles: Vec<crate::web_server::PhpFpmProfile>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub vhosts: Vec<crate::vhost::Vhost>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub host_policies: Vec<HostPolicy>,
#[serde(default)]
pub user_agent_blocking: crate::user_agent::Policy,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub acl_profiles: Vec<AclProfile>,
#[serde(default, skip_serializing_if = "HeaderRules::is_empty")]
pub headers: HeaderRules,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HostPolicy {
pub hosts: Vec<String>,
pub acl: Acl,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct AclProfile {
pub id: String,
pub name: String,
pub acl: Option<Acl>,
pub country_access: crate::rate_limit::CountryAccess,
}
impl Default for AclProfile {
fn default() -> Self {
Self {
id: String::new(),
name: String::new(),
acl: None,
country_access: Default::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Backend {
#[serde(default)]
pub protocol: UpstreamProtocol,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub address_allowlist: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tls_ca_pem: Option<String>,
/// Optional DNS name used for TLS SNI and certificate hostname verification.
/// TCP/DNS connection targeting remains pinned to `url`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tls_server_name: Option<String>,
/// Verify the HTTPS backend certificate chain and server identity.
/// Disabled by default for reverse-proxy compatibility; enable explicitly when required.
#[serde(default)]
pub tls_verify: bool,
pub id: String,
pub url: String,
#[serde(default)]
pub allow_private: bool,
/// Explicitly allow a backend that resolves to a local RustProxy listener.
/// This can expose the control plane or create a proxy loop and is off by default.
#[serde(default)]
pub accept_local_listener_risk: bool,
#[serde(default)]
pub preserve_host: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UpstreamProtocol {
#[default]
Http1,
/// Legacy value `http2`: HTTPS uses ALPN H2/H1, HTTP uses H2C prior knowledge.
/// Retain its 0.32.2 meaning when loading unchanged saved configurations.
Http2,
/// HTTPS negotiates H2/H1; plaintext uses H1 without H2C probing.
Auto,
/// New explicit mode `h2`: require H2 for HTTPS and H2C for cleartext.
H2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Envelope {
pub revision: u64,
pub config: Config,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Update {
pub expected_revision: u64,
pub config: Config,
}
#[derive(Debug, Clone)]
pub struct ValidationPolicy {
pub tls_addr: Option<SocketAddr>,
pub cert_dir: Option<PathBuf>,
/// Resolved runtime data directory. Exposed only through authenticated admin status.
pub data_dir: Option<PathBuf>,
pub proxy_addr: SocketAddr,
pub admin_addr: SocketAddr,
}
#[derive(Debug)]
pub struct CompiledBackend {
pub address_policy: crate::upstream::AddressPolicy,
pub client: crate::upstream::ProxyClient,
pub websocket_client: crate::upstream::ProxyClient,
pub id: String,
/// Configured network origin used by raw TCP probes and policy checks.
pub origin: String,
/// HTTP request origin. For HTTPS this may carry a separate verified TLS name.
pub request_origin: String,
/// Parsed once at configuration publish; reused for every upstream request.
pub request_origin_uri: http::Uri,
pub host_header: HeaderValue,
pub preserve_host: bool,
pub protocol: UpstreamProtocol,
/// Whether backend TLS certificate chain/name verification is enabled.
/// Retained in the compiled snapshot for failure diagnostics only.
pub tls_verify: bool,
pub accept_local_listener_risk: bool,
/// True only when this backend URL names the configured administration socket
/// directly. Used by the guarded control-plane reverse-proxy path.
pub targets_admin_listener: bool,
}
#[derive(Debug, Clone)]
pub enum CompiledSource {
ReverseProxy,
WebServer(Arc<crate::web_server::CompiledService>),
}
#[derive(Debug, Clone)]
pub struct CompiledLocationRedirect {
pub status: u16,
pub target: String,
pub rewrite_regex: Option<regex::Regex>,
pub preserve_query: bool,
}
#[derive(Debug, Clone)]
pub struct CompiledRoute {
pub backend_id: Option<String>,
pub bindings: Vec<crate::vhost::Listener>,
pub frontend: Option<Arc<CompiledFrontend>>,
pub pool: Option<Arc<crate::balance::Pool>>,
pub cache: Option<bool>,
/// Vhost-local stable location ID (`main` for the main source).
pub name: String,
pub id: String,
pub internal: bool,
pub path_prefix: String,
pub match_type: crate::vhost::PathMatch,
pub path_regex: Option<regex::Regex>,
pub rewrite: crate::vhost::PathRewrite,
pub response: Option<crate::vhost::LocationResponse>,
pub redirect: Option<CompiledLocationRedirect>,
pub error_fallback: Option<crate::vhost::ErrorFallback>,
pub source: CompiledSource,
pub backend: Option<Arc<CompiledBackend>>,
pub acl_profile_id: Option<String>,
pub profile_acl: Option<CompiledAcl>,
pub profile_country_access: crate::rate_limit::CountryAccess,
pub acl: Option<CompiledAcl>,
pub country_access: crate::rate_limit::CountryAccess,
pub method_policy: crate::vhost::MethodPolicy,
pub headers: CompiledHeaders,
pub unavailable: bool,
}
#[derive(Debug)]
pub struct CompiledFrontend {
pub websockets: bool,
pub websocket_legacy_support: bool,
pub websocket_rfc8441: bool,
pub websocket_rfc9220: bool,
pub force_ssl: bool,
/// Optional quick www/non-www canonical redirect (from, to).
pub www_redirect: Option<(String, String)>,
pub forward_client_ip: bool,
/// HTTPS target port used by Force SSL redirects. Port 443 wins when multiple TLS ports are selected.
pub https_redirect_port: Option<u16>,
pub http2_enabled: bool,
pub http3_enabled: bool,
pub tls_policy: crate::tls::ProtocolPolicy,
pub tls: Option<Arc<rustls::ServerConfig>>,
pub tls_http1: Option<Arc<rustls::ServerConfig>>,
pub cloudflare_client_ip: bool,
pub timing_headers: bool,
pub backend_header: bool,
pub backend_timing_header: bool,
pub rate_limit: crate::rate_limit::Settings,
pub method_policy: crate::vhost::MethodPolicy,
pub basic_auth: Option<crate::basic_auth::Compiled>,
pub metrics: Arc<crate::metrics::VhostMetrics>,
pub server_header: crate::settings::ServerHeader,
pub diagnostic_headers: crate::settings::DiagnosticHeaders,
pub tinyauth: Option<crate::tinyauth::Compiled>,
pub id: String,
pub bindings: Vec<crate::vhost::Listener>,
pub acl_profile_id: Option<String>,
pub profile_acl: Option<CompiledAcl>,
pub profile_country_access: crate::rate_limit::CountryAccess,
/// All Vhost profile scopes, checked before the inline ACL.
pub profile_policies: Vec<crate::acl::ProfileAccess>,
pub acl: Option<CompiledAcl>,
pub headers: CompiledHeaders,
pub security: crate::security::CompiledSecurity,
pub compression: crate::compression::Overrides,
pub cache: crate::cache::Settings,
pub force_cache: crate::cache::UnsafeForce,
pub transform: crate::transform::Settings,
pub pages: crate::pages::VhostPages,
pub proxy_timeouts: crate::vhost::ProxyTimeouts,
pub user_agent_blocking: crate::user_agent::CompiledPolicy,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedLogVhost {
pub vhost: String,
pub matched_by: &'static str,
}
pub struct Snapshot {
pub web_php_services: HashMap<String, Arc<crate::web_server::CompiledService>>,
pub vhost_metrics: std::collections::BTreeMap<String, Arc<crate::metrics::VhostMetrics>>,
pub forwarding: crate::forwarding::Compiled,
pub quic_tls: Option<Arc<quinn::crypto::rustls::QuicServerConfig>>,
pub listeners: Vec<crate::vhost::Listener>,
pub pools: Vec<Arc<crate::balance::Pool>>,
pub tls_http1: Option<Arc<rustls::ServerConfig>>,
frontends: HashMap<String, Vec<Arc<CompiledFrontend>>>,
pub tls: Option<Arc<rustls::ServerConfig>>,
pub envelope: Envelope,
routes: HashMap<String, Vec<Arc<CompiledRoute>>>,
/// Main vhost backend fallback, kept separate from user Custom locations.
fallbacks: HashMap<String, Vec<Arc<CompiledRoute>>>,
redirects: HashMap<String, Vec<Arc<crate::redirect::CompiledHost>>>,
not_found_hosts: HashMap<String, Vec<Arc<crate::not_found::CompiledHost>>>,
host_acls: HashMap<String, Arc<CompiledAcl>>,
pub user_agent_blocking: crate::user_agent::CompiledPolicy,
pub headers: CompiledHeaders,
}
impl std::fmt::Debug for Snapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// QUIC crypto has no Debug implementation; never format private keys.
f.debug_struct("Snapshot")
.field("revision", &self.envelope.revision)
.field("listeners", &self.listeners)
.field("quic_tls", &self.quic_tls.is_some())
.finish_non_exhaustive()
}
}