mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +00:00
feat(acl): add group-based ACL rules and related structures (#1265)
* feat(acl): add group-based ACL rules and related structures * refactor(acl): optimize group handling with Arc and improve cache management * refactor(acl): clippy * feat(tests): add performance tests for generate_with_proof and verify methods * feat: update group_trust_map to use HashMap for more secure group proofs * refactor: refactor the logic of the trusted group getting and setting * feat(acl): support kcp/quic use group acl * feat(proxy): optimize group retrieval by IP in Kcp and Quic proxy handlers * feat(tests): add group-based ACL tree node test * always allow quic proxy traffic --------- Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn> Co-authored-by: sijie.sun <sijie.sun@smartx.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr as _,
|
||||
sync::Arc,
|
||||
@@ -61,6 +61,8 @@ pub struct FastLookupRule {
|
||||
pub dst_ip_ranges: Vec<cidr::IpCidr>,
|
||||
pub src_port_ranges: Vec<(u16, u16)>,
|
||||
pub dst_port_ranges: Vec<(u16, u16)>,
|
||||
pub source_groups: HashSet<String>,
|
||||
pub destination_groups: HashSet<String>,
|
||||
pub action: Action,
|
||||
pub enabled: bool,
|
||||
pub stateful: bool,
|
||||
@@ -78,6 +80,8 @@ pub struct AclCacheKey {
|
||||
pub dst_ip: IpAddr,
|
||||
pub src_port: u16,
|
||||
pub dst_port: u16,
|
||||
pub src_groups: Arc<Vec<String>>,
|
||||
pub dst_groups: Arc<Vec<String>>,
|
||||
}
|
||||
|
||||
impl AclCacheKey {
|
||||
@@ -89,6 +93,8 @@ impl AclCacheKey {
|
||||
dst_ip: packet_info.dst_ip,
|
||||
src_port: packet_info.src_port.unwrap_or(0),
|
||||
dst_port: packet_info.dst_port.unwrap_or(0),
|
||||
src_groups: packet_info.src_groups.clone(),
|
||||
dst_groups: packet_info.dst_groups.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +122,8 @@ pub struct PacketInfo {
|
||||
pub dst_port: Option<u16>,
|
||||
pub protocol: Protocol,
|
||||
pub packet_size: usize,
|
||||
pub src_groups: Arc<Vec<String>>,
|
||||
pub dst_groups: Arc<Vec<String>>,
|
||||
}
|
||||
|
||||
// ACL processing result
|
||||
@@ -684,6 +692,28 @@ impl AclProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Source group check
|
||||
if !rule.source_groups.is_empty() {
|
||||
let matches = packet_info
|
||||
.src_groups
|
||||
.iter()
|
||||
.any(|group| rule.source_groups.contains(group));
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Destination group check
|
||||
if !rule.destination_groups.is_empty() {
|
||||
let matches = packet_info
|
||||
.dst_groups
|
||||
.iter()
|
||||
.any(|group| rule.destination_groups.contains(group));
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -804,6 +834,8 @@ impl AclProcessor {
|
||||
dst_ip_ranges,
|
||||
src_port_ranges,
|
||||
dst_port_ranges,
|
||||
source_groups: rule.source_groups.iter().cloned().collect(),
|
||||
destination_groups: rule.destination_groups.iter().cloned().collect(),
|
||||
action: rule.action(),
|
||||
enabled: rule.enabled,
|
||||
stateful: rule.stateful,
|
||||
@@ -1071,6 +1103,8 @@ impl AclRuleBuilder {
|
||||
rate_limit: 0,
|
||||
burst_limit: 0,
|
||||
stateful: true,
|
||||
source_groups: vec![],
|
||||
destination_groups: vec![],
|
||||
};
|
||||
inbound_chain.rules.push(tcp_rule);
|
||||
rule_priority -= 1;
|
||||
@@ -1093,6 +1127,8 @@ impl AclRuleBuilder {
|
||||
rate_limit: 0,
|
||||
burst_limit: 0,
|
||||
stateful: false,
|
||||
source_groups: vec![],
|
||||
destination_groups: vec![],
|
||||
};
|
||||
inbound_chain.rules.push(udp_rule);
|
||||
}
|
||||
@@ -1108,6 +1144,10 @@ impl AclRuleBuilder {
|
||||
} else {
|
||||
acl.acl_v1 = Some(AclV1 {
|
||||
chains: vec![inbound_chain],
|
||||
group: Some(GroupInfo {
|
||||
declares: vec![],
|
||||
members: vec![],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1144,6 +1184,106 @@ mod tests {
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_group_based_acl_rules() {
|
||||
let mut acl_config = Acl::default();
|
||||
let mut acl_v1 = AclV1::default();
|
||||
let mut chain = Chain {
|
||||
name: "group_test_chain".to_string(),
|
||||
chain_type: ChainType::Inbound as i32,
|
||||
enabled: true,
|
||||
default_action: Action::Drop as i32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Rules
|
||||
chain.rules.push(Rule {
|
||||
name: "allow_admins_to_db".to_string(),
|
||||
priority: 100,
|
||||
enabled: true,
|
||||
action: Action::Allow as i32,
|
||||
protocol: Protocol::Any as i32,
|
||||
source_groups: vec!["admin".to_string()],
|
||||
destination_groups: vec!["db-server".to_string()],
|
||||
..Default::default()
|
||||
});
|
||||
chain.rules.push(Rule {
|
||||
name: "allow_devs_from_anywhere".to_string(),
|
||||
priority: 90,
|
||||
enabled: true,
|
||||
action: Action::Allow as i32,
|
||||
protocol: Protocol::Any as i32,
|
||||
source_groups: vec!["dev".to_string()],
|
||||
..Default::default()
|
||||
});
|
||||
chain.rules.push(Rule {
|
||||
name: "deny_guests_to_db".to_string(),
|
||||
priority: 80,
|
||||
enabled: true,
|
||||
action: Action::Drop as i32,
|
||||
protocol: Protocol::Any as i32,
|
||||
source_groups: vec!["guest".to_string()],
|
||||
destination_groups: vec!["db-server".to_string()],
|
||||
..Default::default()
|
||||
});
|
||||
chain.rules.push(Rule {
|
||||
name: "allow_specific_ip".to_string(),
|
||||
priority: 70,
|
||||
enabled: true,
|
||||
action: Action::Allow as i32,
|
||||
protocol: Protocol::Any as i32,
|
||||
source_ips: vec!["1.2.3.4/32".to_string()],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
acl_v1.chains.push(chain);
|
||||
acl_config.acl_v1 = Some(acl_v1);
|
||||
|
||||
let processor = AclProcessor::new(acl_config);
|
||||
|
||||
// Case 3.1: Source group match (devs from anywhere)
|
||||
let mut packet_info = create_test_packet_info();
|
||||
packet_info.src_groups = Arc::new(vec!["dev".to_string()]);
|
||||
let result = processor.process_packet(&packet_info, ChainType::Inbound);
|
||||
assert_eq!(result.action, Action::Allow);
|
||||
assert_eq!(result.matched_rule, Some(RuleId::Priority(90)));
|
||||
|
||||
// Case 3.2: Source group no match
|
||||
packet_info.src_groups = Arc::new(vec!["guest".to_string()]);
|
||||
let result = processor.process_packet(&packet_info, ChainType::Inbound);
|
||||
assert_eq!(result.action, Action::Drop); // Default drop
|
||||
assert_eq!(result.matched_rule, Some(RuleId::Default));
|
||||
|
||||
// Case 3.3: Destination group match (deny guests to db)
|
||||
packet_info.src_groups = Arc::new(vec!["guest".to_string()]);
|
||||
packet_info.dst_groups = Arc::new(vec!["db-server".to_string()]);
|
||||
let result = processor.process_packet(&packet_info, ChainType::Inbound);
|
||||
assert_eq!(result.action, Action::Drop);
|
||||
assert_eq!(result.matched_rule, Some(RuleId::Priority(80)));
|
||||
|
||||
// Case 3.4: Source and Destination groups match
|
||||
packet_info.src_groups = Arc::new(vec!["admin".to_string()]);
|
||||
packet_info.dst_groups = Arc::new(vec!["db-server".to_string()]);
|
||||
let result = processor.process_packet(&packet_info, ChainType::Inbound);
|
||||
assert_eq!(result.action, Action::Allow);
|
||||
assert_eq!(result.matched_rule, Some(RuleId::Priority(100)));
|
||||
|
||||
// Case 3.5: Partial match (admin to web-server)
|
||||
packet_info.src_groups = Arc::new(vec!["admin".to_string()]);
|
||||
packet_info.dst_groups = Arc::new(vec!["web-server".to_string()]);
|
||||
let result = processor.process_packet(&packet_info, ChainType::Inbound);
|
||||
assert_eq!(result.action, Action::Drop); // Default drop
|
||||
assert_eq!(result.matched_rule, Some(RuleId::Default));
|
||||
|
||||
// Case 3.6: Rule with no group definition
|
||||
packet_info.src_ip = "1.2.3.4".parse().unwrap();
|
||||
packet_info.src_groups = Arc::new(vec!["admin".to_string()]);
|
||||
packet_info.dst_groups = Arc::new(vec![]);
|
||||
let result = processor.process_packet(&packet_info, ChainType::Inbound);
|
||||
assert_eq!(result.action, Action::Allow);
|
||||
assert_eq!(result.matched_rule, Some(RuleId::Priority(70)));
|
||||
}
|
||||
|
||||
fn create_test_acl_config() -> Acl {
|
||||
let mut acl_config = Acl::default();
|
||||
|
||||
@@ -1182,6 +1322,8 @@ mod tests {
|
||||
dst_port: Some(80),
|
||||
protocol: Protocol::Tcp,
|
||||
packet_size: 1024,
|
||||
src_groups: Arc::new(vec![]),
|
||||
dst_groups: Arc::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1380,6 +1522,8 @@ mod tests {
|
||||
dst_port: Some(53), // DNS
|
||||
protocol: Protocol::Udp, // UDP
|
||||
packet_size: 512,
|
||||
src_groups: Arc::new(vec![]),
|
||||
dst_groups: Arc::new(vec![]),
|
||||
};
|
||||
|
||||
// Test TCP packet (should hit stateful+rate-limited rule)
|
||||
@@ -1390,6 +1534,8 @@ mod tests {
|
||||
dst_port: Some(80), // HTTP
|
||||
protocol: Protocol::Tcp, // TCP
|
||||
packet_size: 1024,
|
||||
src_groups: Arc::new(vec![]),
|
||||
dst_groups: Arc::new(vec![]),
|
||||
};
|
||||
|
||||
// Process UDP packets multiple times
|
||||
|
||||
@@ -8,8 +8,10 @@ use crate::common::config::ProxyNetworkConfig;
|
||||
use crate::common::stats_manager::StatsManager;
|
||||
use crate::common::token_bucket::TokenBucketManager;
|
||||
use crate::peers::acl_filter::AclFilter;
|
||||
use crate::proto::acl::GroupIdentity;
|
||||
use crate::proto::cli::PeerConnInfo;
|
||||
use crate::proto::common::{PeerFeatureFlag, PortForwardConfigPb};
|
||||
use crate::proto::peer_rpc::PeerGroupInfo;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
|
||||
use super::{
|
||||
@@ -351,6 +353,7 @@ impl GlobalCtx {
|
||||
}
|
||||
|
||||
pub fn set_quic_proxy_port(&self, port: Option<u16>) {
|
||||
self.acl_filter.set_quic_udp_port(port.unwrap_or(0));
|
||||
self.quic_proxy_port.store(port);
|
||||
}
|
||||
|
||||
@@ -365,6 +368,37 @@ impl GlobalCtx {
|
||||
pub fn get_acl_filter(&self) -> &Arc<AclFilter> {
|
||||
&self.acl_filter
|
||||
}
|
||||
|
||||
pub fn get_acl_groups(&self, peer_id: PeerId) -> Vec<PeerGroupInfo> {
|
||||
use std::collections::HashSet;
|
||||
self.config
|
||||
.get_acl()
|
||||
.and_then(|acl| acl.acl_v1)
|
||||
.and_then(|acl_v1| acl_v1.group)
|
||||
.map_or_else(Vec::new, |group| {
|
||||
let memberships: HashSet<_> = group.members.iter().collect();
|
||||
group
|
||||
.declares
|
||||
.iter()
|
||||
.filter(|g| memberships.contains(&g.group_name))
|
||||
.map(|g| {
|
||||
PeerGroupInfo::generate_with_proof(
|
||||
g.group_name.clone(),
|
||||
g.group_secret.clone(),
|
||||
peer_id,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_acl_group_declarations(&self) -> Vec<GroupIdentity> {
|
||||
self.config
|
||||
.get_acl()
|
||||
.and_then(|acl| acl.acl_v1)
|
||||
.and_then(|acl_v1| acl_v1.group)
|
||||
.map_or_else(Vec::new, |group| group.declares.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user