sandbox_runtime/proxy/
filter.rs1use crate::config::{matches_domain_pattern, NetworkConfig};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum FilterDecision {
8 Allow,
10 Deny,
12 Mitm,
14}
15
16#[derive(Debug, Clone)]
18pub struct DomainFilter {
19 allowed_domains: Vec<String>,
20 denied_domains: Vec<String>,
21 mitm_domains: Vec<String>,
22}
23
24impl DomainFilter {
25 pub fn from_config(config: &NetworkConfig) -> Self {
27 let mitm_domains = config
28 .mitm_proxy
29 .as_ref()
30 .map(|m| m.domains.clone())
31 .unwrap_or_default();
32
33 Self {
34 allowed_domains: config.allowed_domains.clone(),
35 denied_domains: config.denied_domains.clone(),
36 mitm_domains,
37 }
38 }
39
40 pub fn allow_all() -> Self {
42 Self {
43 allowed_domains: vec![],
44 denied_domains: vec![],
45 mitm_domains: vec![],
46 }
47 }
48
49 pub fn check(&self, hostname: &str, _port: u16) -> FilterDecision {
51 for pattern in &self.denied_domains {
53 if matches_domain_pattern(hostname, pattern) {
54 return FilterDecision::Deny;
55 }
56 }
57
58 for pattern in &self.mitm_domains {
60 if matches_domain_pattern(hostname, pattern) {
61 return FilterDecision::Mitm;
62 }
63 }
64
65 if !self.allowed_domains.is_empty() {
67 for pattern in &self.allowed_domains {
68 if matches_domain_pattern(hostname, pattern) {
69 return FilterDecision::Allow;
70 }
71 }
72 return FilterDecision::Deny;
74 }
75
76 FilterDecision::Allow
78 }
79
80 pub fn is_allowed(&self, hostname: &str, port: u16) -> bool {
82 matches!(self.check(hostname, port), FilterDecision::Allow | FilterDecision::Mitm)
83 }
84
85 pub fn should_mitm(&self, hostname: &str) -> bool {
87 for pattern in &self.mitm_domains {
88 if matches_domain_pattern(hostname, pattern) {
89 return true;
90 }
91 }
92 false
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_domain_filter_allow_all() {
102 let filter = DomainFilter::allow_all();
103 assert_eq!(filter.check("example.com", 443), FilterDecision::Allow);
104 assert_eq!(filter.check("evil.com", 443), FilterDecision::Allow);
105 }
106
107 #[test]
108 fn test_domain_filter_with_allowed() {
109 let filter = DomainFilter {
110 allowed_domains: vec!["github.com".to_string(), "*.npmjs.org".to_string()],
111 denied_domains: vec![],
112 mitm_domains: vec![],
113 };
114
115 assert_eq!(filter.check("github.com", 443), FilterDecision::Allow);
116 assert_eq!(filter.check("registry.npmjs.org", 443), FilterDecision::Allow);
117 assert_eq!(filter.check("evil.com", 443), FilterDecision::Deny);
118 }
119
120 #[test]
121 fn test_domain_filter_with_denied() {
122 let filter = DomainFilter {
123 allowed_domains: vec!["*.example.com".to_string()],
124 denied_domains: vec!["evil.example.com".to_string()],
125 mitm_domains: vec![],
126 };
127
128 assert_eq!(filter.check("api.example.com", 443), FilterDecision::Allow);
129 assert_eq!(filter.check("evil.example.com", 443), FilterDecision::Deny);
130 }
131
132 #[test]
133 fn test_domain_filter_with_mitm() {
134 let filter = DomainFilter {
135 allowed_domains: vec!["*.example.com".to_string()],
136 denied_domains: vec![],
137 mitm_domains: vec!["api.example.com".to_string()],
138 };
139
140 assert_eq!(filter.check("api.example.com", 443), FilterDecision::Mitm);
141 assert_eq!(filter.check("other.example.com", 443), FilterDecision::Allow);
142 }
143}