From a02c551a5e99bf873b684e0e1dc77ac1d51fad53 Mon Sep 17 00:00:00 2001 From: Andreas Fuchs Date: Wed, 30 Oct 2024 11:11:29 -0400 Subject: [PATCH] Disallow any unknown fields in configuration files This will break backwards compatibility of configurations, but ensures that a previously-configured setting won't get dropped arbitrarily. Pretty much worth it, I think. --- src/config.rs | 18 ++++++++++++++++++ ...ons__check_config__invalid_keys@stderr.snap | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4c3d5a44..5c9ae2ee 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,6 +29,7 @@ pub(crate) static DEFAULT_PATH: Lazy = #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub(crate) struct Config { #[serde(default = "false_fn")] pub(crate) conduit_compat: bool, @@ -77,6 +78,7 @@ pub(crate) struct Config { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub(crate) struct ServerDiscovery { /// Server-server discovery configuration #[serde(default)] @@ -88,6 +90,7 @@ pub(crate) struct ServerDiscovery { /// Server-server discovery configuration #[derive(Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] pub(crate) struct ServerServerDiscovery { /// The alternative authority to make server-server API requests to pub(crate) authority: Option, @@ -95,6 +98,7 @@ pub(crate) struct ServerServerDiscovery { /// Client-server discovery configuration #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub(crate) struct ClientServerDiscovery { /// The base URL to make client-server API requests to pub(crate) base_url: Url, @@ -104,6 +108,7 @@ pub(crate) struct ClientServerDiscovery { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub(crate) struct TlsConfig { pub(crate) certs: String, pub(crate) key: String, @@ -114,6 +119,7 @@ pub(crate) struct TlsConfig { )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] +#[serde(deny_unknown_fields)] pub(crate) enum ListenComponent { Client, Federation, @@ -129,6 +135,7 @@ impl ListenComponent { #[derive(Clone, Debug, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] +#[serde(deny_unknown_fields)] pub(crate) enum ListenTransport { Tcp { #[serde(default = "default_address")] @@ -171,6 +178,7 @@ impl Display for ListenTransport { } #[derive(Clone, Debug, Deserialize)] +// Incompatible with deny_unknown_fields due to serde(flatten). pub(crate) struct ListenConfig { #[serde(default = "ListenComponent::all_components")] pub(crate) components: HashSet, @@ -194,6 +202,7 @@ impl Display for ListenConfig { } #[derive(Copy, Clone, Default, Debug, Deserialize, clap::ValueEnum)] +#[serde(deny_unknown_fields)] #[serde(rename_all = "snake_case")] pub(crate) enum LogFormat { /// Multiple lines per event, includes all information @@ -222,6 +231,7 @@ impl Display for LogFormat { } #[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct TurnConfig { pub(crate) username: String, @@ -244,6 +254,7 @@ impl Default for TurnConfig { } #[derive(Clone, Copy, Debug, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(rename_all = "lowercase")] pub(crate) enum DatabaseBackend { #[cfg(feature = "rocksdb")] @@ -264,6 +275,7 @@ impl Display for DatabaseBackend { } #[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub(crate) struct DatabaseConfig { pub(crate) backend: DatabaseBackend, pub(crate) path: String, @@ -275,12 +287,14 @@ pub(crate) struct DatabaseConfig { } #[derive(Clone, Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct MetricsConfig { pub(crate) enable: bool, } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct OtelTraceConfig { pub(crate) enable: bool, @@ -301,6 +315,7 @@ impl Default for OtelTraceConfig { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct FlameConfig { pub(crate) enable: bool, @@ -319,6 +334,7 @@ impl Default for FlameConfig { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct LogConfig { pub(crate) filter: EnvFilterClone, @@ -339,6 +355,7 @@ impl Default for LogConfig { } #[derive(Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct ObservabilityConfig { /// Prometheus metrics @@ -352,6 +369,7 @@ pub(crate) struct ObservabilityConfig { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct FederationConfig { pub(crate) enable: bool, diff --git a/tests/integrations/snapshots/integrations__check_config__invalid_keys@stderr.snap b/tests/integrations/snapshots/integrations__check_config__invalid_keys@stderr.snap index 7cf08682..942cce3b 100644 --- a/tests/integrations/snapshots/integrations__check_config__invalid_keys@stderr.snap +++ b/tests/integrations/snapshots/integrations__check_config__invalid_keys@stderr.snap @@ -8,5 +8,5 @@ Error: failed to validate configuration Caused by: TOML parse error at line 1, column 1 | 1 | some_name = "example.com" - | ^^^^^^^^^^^^^^^^^^^^^^^^^ -missing field `server_name` + | ^^^^^^^^^ +unknown field `some_name`, expected one of `conduit_compat`, `listen`, `tls`, `server_name`, `server_discovery`, `database`, `federation`, `cache_capacity_modifier`, `pdu_cache_capacity`, `cleanup_second_interval`, `max_request_size`, `allow_registration`, `registration_token`, `allow_encryption`, `allow_room_creation`, `serve_media_unauthenticated`, `default_room_version`, `proxy`, `jwt_secret`, `observability`, `turn`, `emergency_password`