diff --git a/crates/common/src/cache/reload.rs b/crates/common/src/cache/reload.rs index b32c12b..abf7aa2 100644 --- a/crates/common/src/cache/reload.rs +++ b/crates/common/src/cache/reload.rs @@ -257,7 +257,8 @@ struct SettingsReloadState { /// The reload a write to `object` calls for: the object to reload, or None /// when the running settings don't hold that object (accounts, domains and /// other data read as needed, stores, which take a restart, and objects with -/// reload actions of their own, such as applications). +/// reload actions of their own, such as applications). Blocked IPs have a +/// reload of their own; allowed IPs take the full one. pub fn write_reload_target(object: ObjectType) -> Option { match object { ObjectType::Certificate => Some(ObjectType::Certificate), @@ -265,8 +266,12 @@ pub fn write_reload_target(object: ObjectType) -> Option { | ObjectType::MemoryLookupKeyValue | ObjectType::HttpLookup | ObjectType::StoreLookup => Some(ObjectType::StoreLookup), - ObjectType::BlockedIp | ObjectType::AllowedIp => Some(ObjectType::BlockedIp), - ObjectType::AcmeProvider + ObjectType::BlockedIp => Some(ObjectType::BlockedIp), + // Allowed IPs are part of the core's security settings + // (Security::parse), which only a full reload rebuilds; the blocked-IP + // reload doesn't touch them + ObjectType::AllowedIp + | ObjectType::AcmeProvider | ObjectType::AddressBook | ObjectType::AiModel | ObjectType::Asn diff --git a/tests/src/system/auto_reload.rs b/tests/src/system/auto_reload.rs index c5cc36a..d7a89c0 100644 --- a/tests/src/system/auto_reload.rs +++ b/tests/src/system/auto_reload.rs @@ -12,13 +12,16 @@ use crate::utils::{ server::{TestServer, TestServerBuilder}, }; use common::BuildServer; -use registry::schema::{ - enums::TracingLevel, - prelude::ObjectType, - structs::{ - CertificateManagement, DkimManagement, DnsManagement, Domain, Expression, - MtaDeliverySchedule, MtaStageAuth, MtaVirtualQueue, Tracer, TracerStdout, +use registry::{ + schema::{ + enums::TracingLevel, + prelude::ObjectType, + structs::{ + AllowedIp, CertificateManagement, DkimManagement, DnsManagement, Domain, Expression, + MtaDeliverySchedule, MtaStageAuth, MtaVirtualQueue, Tracer, TracerStdout, + }, }, + types::ipmask::IpAddrOrMask, }; use serde_json::Value; @@ -152,6 +155,30 @@ async fn test_write_applies(test: &TestServer) { .await; assert_applied(&response); + // An allowed IP is live as soon as it is saved, and gone once + // destroyed. It lives in the core's security settings, which the + // blocked-IP reload it used to get doesn't rebuild. + let ip: std::net::IpAddr = "198.51.100.7".parse().unwrap(); + assert!(!is_allowed(test, ip)); + let response = admin + .registry_create([AllowedIp { + address: IpAddrOrMask::from_ip(ip), + reason: Some("autoreload".into()), + ..Default::default() + }]) + .await; + assert_applied(&response); + assert!( + is_allowed(test, ip), + "allowed IP not in the running settings" + ); + let allowed_id = response.created_id(0); + let response = admin + .registry_destroy(ObjectType::AllowedIp, [allowed_id]) + .await; + assert_applied(&response); + assert!(!is_allowed(test, ip), "destroyed allowed IP still live"); + // Data that isn't part of the running settings doesn't reload them let response = admin .registry_create([Domain { @@ -187,3 +214,7 @@ fn has_schedule(test: &TestServer, name: &str) -> bool { .queue_strategy .contains_key(name) } + +fn is_allowed(test: &TestServer, ip: std::net::IpAddr) -> bool { + test.server.inner.build_server().is_ip_allowed(ip) +}