349 lines
12 KiB
Rust
349 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 = 2_097_152;
|
|
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,
|
|
#[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,
|
|
rename = "user_agent_blocking",
|
|
skip_serializing_if = "Option::is_none"
|
|
)]
|
|
pub legacy_user_agent_blocking: Option<crate::user_agent::Policy>,
|
|
#[serde(default)]
|
|
pub global_security: GlobalSecurity,
|
|
#[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, Default, Serialize, Deserialize)]
|
|
#[serde(default, deny_unknown_fields)]
|
|
pub struct GlobalSecurity {
|
|
pub user_agent_blocking: Option<crate::user_agent::Policy>,
|
|
pub country_access: crate::rate_limit::CountryAccess,
|
|
pub acl: Option<Acl>,
|
|
pub method_policy: crate::vhost::MethodPolicy,
|
|
pub content_type_policy: crate::vhost::ContentTypePolicy,
|
|
pub rate_limit: crate::rate_limit::Settings,
|
|
pub security_headers: crate::security::SecurityHeaders,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct CompiledGlobalSecurity {
|
|
pub user_agent_blocking: crate::user_agent::CompiledPolicy,
|
|
pub country_access: crate::rate_limit::CountryAccess,
|
|
pub acl: Option<CompiledAcl>,
|
|
pub method_policy: crate::vhost::MethodPolicy,
|
|
pub content_type_policy: crate::vhost::ContentTypePolicy,
|
|
pub rate_limit: crate::rate_limit::Settings,
|
|
pub headers: crate::security::CompiledSecurity,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn migrate_legacy_user_agent_blocking(&mut self) -> Result<()> {
|
|
if let Some(legacy) = self.legacy_user_agent_blocking.take() {
|
|
match &self.global_security.user_agent_blocking {
|
|
Some(current) => ensure!(
|
|
*current == legacy,
|
|
"conflicting legacy user_agent_blocking and global_security.user_agent_blocking policies"
|
|
),
|
|
None => self.global_security.user_agent_blocking = Some(legacy),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl GlobalSecurity {
|
|
pub fn compile(&mut self) -> Result<CompiledGlobalSecurity> {
|
|
let user_agent_blocking = self
|
|
.user_agent_blocking
|
|
.get_or_insert_with(Default::default)
|
|
.compile()?;
|
|
self.country_access.validate()?;
|
|
self.method_policy.validate()?;
|
|
self.content_type_policy.validate()?;
|
|
self.rate_limit.validate()?;
|
|
let acl = self.acl.as_ref().map(Acl::compile).transpose()?;
|
|
let headers = self.security_headers.compile()?;
|
|
Ok(CompiledGlobalSecurity {
|
|
user_agent_blocking,
|
|
country_access: self.country_access.clone(),
|
|
acl,
|
|
method_policy: self.method_policy.clone(),
|
|
content_type_policy: self.content_type_policy.clone(),
|
|
rate_limit: self.rate_limit.clone(),
|
|
headers,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct HostPolicy {
|
|
pub hosts: Vec<String>,
|
|
pub acl: Acl,
|
|
}
|
|
#[derive(Debug, Clone, Default, 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,
|
|
}
|
|
#[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>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub tls_server_name: Option<String>,
|
|
#[serde(default)]
|
|
pub tls_verify: bool,
|
|
pub id: String,
|
|
pub url: String,
|
|
#[serde(default)]
|
|
pub allow_private: bool,
|
|
#[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,
|
|
Http2,
|
|
Auto,
|
|
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>,
|
|
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,
|
|
pub origin: String,
|
|
pub request_origin: String,
|
|
pub request_origin_uri: http::Uri,
|
|
pub host_header: HeaderValue,
|
|
pub preserve_host: bool,
|
|
pub protocol: UpstreamProtocol,
|
|
pub tls_verify: bool,
|
|
pub accept_local_listener_risk: bool,
|
|
pub targets_admin_listener: bool,
|
|
pub health_probe: Option<Arc<CompiledBackend>>,
|
|
}
|
|
#[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>,
|
|
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,
|
|
pub www_redirect: Option<(String, String)>,
|
|
pub forward_client_ip: bool,
|
|
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 content_type_policy: crate::vhost::ContentTypePolicy,
|
|
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 auth_provider: Option<crate::auth_provider::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,
|
|
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>>>,
|
|
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 global_security: CompiledGlobalSecurity,
|
|
pub headers: CompiledHeaders,
|
|
}
|
|
impl std::fmt::Debug for Snapshot {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Snapshot")
|
|
.field("revision", &self.envelope.revision)
|
|
.field("listeners", &self.listeners)
|
|
.field("quic_tls", &self.quic_tls.is_some())
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|