From 362aa7a9cd025d6769509fc7b04d8b59de48a851 Mon Sep 17 00:00:00 2001 From: fanyang Date: Mon, 4 May 2026 00:47:24 +0800 Subject: [PATCH] fix: allow omitted ACL config fields (#2206) --- easytier/build.rs | 5 +++ easytier/src/common/acl_processor.rs | 39 +++++++++++++++++ easytier/src/common/config.rs | 65 ++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/easytier/build.rs b/easytier/build.rs index 0cc372df..6b2ea81f 100644 --- a/easytier/build.rs +++ b/easytier/build.rs @@ -191,6 +191,11 @@ fn main() -> Result<(), Box> { ) .type_attribute("peer_rpc.RouteForeignNetworkSummary", "#[derive(Hash, Eq)]") .type_attribute("common.RpcDescriptor", "#[derive(Hash, Eq)]") + .type_attribute("acl.Acl", "#[serde(default)]") + .type_attribute("acl.AclV1", "#[serde(default)]") + .type_attribute("acl.Chain", "#[serde(default)]") + .type_attribute("acl.Rule", "#[serde(default)]") + .type_attribute("acl.GroupInfo", "#[serde(default)]") .field_attribute(".api.manage.NetworkConfig", "#[serde(default)]") .service_generator(Box::new(easytier_rpc_build::ServiceGenerator::default())) .btree_map(["."]) diff --git a/easytier/src/common/acl_processor.rs b/easytier/src/common/acl_processor.rs index 2594670c..4a414603 100644 --- a/easytier/src/common/acl_processor.rs +++ b/easytier/src/common/acl_processor.rs @@ -1339,6 +1339,45 @@ mod tests { assert_eq!(result.matched_rule, Some(RuleId::Priority(70))); } + #[tokio::test] + async fn test_forward_acl_source_ip_whitelist() { + let mut acl_config = Acl::default(); + let mut acl_v1 = AclV1::default(); + let mut chain = Chain { + name: "subnet_proxy_protect".to_string(), + chain_type: ChainType::Forward as i32, + enabled: true, + default_action: Action::Drop as i32, + ..Default::default() + }; + + chain.rules.push(Rule { + name: "allow_my_devices".to_string(), + priority: 1000, + enabled: true, + action: Action::Allow as i32, + protocol: Protocol::Any as i32, + source_ips: vec!["10.172.192.2/32".to_string()], + ..Default::default() + }); + acl_v1.chains.push(chain); + acl_config.acl_v1 = Some(acl_v1); + + let processor = AclProcessor::new(acl_config); + let mut packet_info = create_test_packet_info(); + packet_info.dst_ip = "192.168.1.10".parse().unwrap(); + + packet_info.src_ip = "10.172.192.2".parse().unwrap(); + let result = processor.process_packet(&packet_info, ChainType::Forward); + assert_eq!(result.action, Action::Allow); + assert_eq!(result.matched_rule, Some(RuleId::Priority(1000))); + + packet_info.src_ip = "10.172.192.3".parse().unwrap(); + let result = processor.process_packet(&packet_info, ChainType::Forward); + assert_eq!(result.action, Action::Drop); + assert_eq!(result.matched_rule, Some(RuleId::Default)); + } + fn create_test_acl_config() -> Acl { let mut acl_config = Acl::default(); diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 33a72375..2f02b8c8 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -1337,6 +1337,71 @@ stun_servers = [ assert!(err.to_string().contains("mapped listener port is missing")); } + #[test] + fn test_acl_toml_rule_uses_defaults_for_omitted_fields() { + use crate::proto::acl::{Action, ChainType, Protocol}; + + let config_str = r#" +[[acl.acl_v1.chains]] +name = "subnet_proxy_protect" +chain_type = 3 +enabled = true +default_action = 2 + +[[acl.acl_v1.chains.rules]] +name = "allow_my_devices" +priority = 1000 +action = 1 +source_ips = ["10.172.192.2/32"] +protocol = 5 +enabled = true +"#; + + let config = TomlConfigLoader::new_from_str(config_str).unwrap(); + let acl = config.get_acl().unwrap(); + let acl_v1 = acl.acl_v1.unwrap(); + let chain = &acl_v1.chains[0]; + let rule = &chain.rules[0]; + + assert_eq!(chain.chain_type, ChainType::Forward as i32); + assert_eq!(chain.default_action, Action::Drop as i32); + assert_eq!(rule.action, Action::Allow as i32); + assert_eq!(rule.protocol, Protocol::Any as i32); + assert_eq!(rule.source_ips, vec!["10.172.192.2/32"]); + assert!(rule.ports.is_empty()); + assert!(rule.source_ports.is_empty()); + assert!(rule.destination_ips.is_empty()); + assert!(rule.source_groups.is_empty()); + assert!(rule.destination_groups.is_empty()); + assert_eq!(rule.rate_limit, 0); + assert_eq!(rule.burst_limit, 0); + assert!(!rule.stateful); + } + + #[test] + fn test_acl_toml_group_can_omit_declares_or_members() { + let declares_only = r#" +[acl.acl_v1.group] + +[[acl.acl_v1.group.declares]] +group_name = "admin" +group_secret = "admin-pw" +"#; + let config = TomlConfigLoader::new_from_str(declares_only).unwrap(); + let group = config.get_acl().unwrap().acl_v1.unwrap().group.unwrap(); + assert_eq!(group.declares.len(), 1); + assert!(group.members.is_empty()); + + let members_only = r#" +[acl.acl_v1.group] +members = ["admin"] +"#; + let config = TomlConfigLoader::new_from_str(members_only).unwrap(); + let group = config.get_acl().unwrap().acl_v1.unwrap().group.unwrap(); + assert!(group.declares.is_empty()); + assert_eq!(group.members, vec!["admin"]); + } + #[test] fn test_network_config_source_user_is_implicit() { let config = TomlConfigLoader::default();