http_acl/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4pub use ipnet::IpNet;
5
6pub mod acl;
7pub mod error;
8pub mod utils;
9
10pub use acl::{AclClassification, HttpAcl, HttpAclBuilder, HttpRequestMethod};
11pub use utils::IntoIpRange;
12
13#[cfg(test)]
14mod tests {
15    use std::net::IpAddr;
16    use std::sync::Arc;
17
18    use ipnet::IpNet;
19
20    use super::{AclClassification, HttpAclBuilder};
21
22    #[test]
23    fn acl() {
24        let acl = HttpAclBuilder::new()
25            .add_allowed_host("example.com".to_string())
26            .unwrap()
27            .add_allowed_host("example.org".to_string())
28            .unwrap()
29            .add_denied_host("example.net".to_string())
30            .unwrap()
31            .add_allowed_port_range(8080..=8080)
32            .unwrap()
33            .add_denied_port_range(8443..=8443)
34            .unwrap()
35            .add_allowed_ip_range("1.0.0.0/8".parse::<IpNet>().unwrap())
36            .unwrap()
37            .add_denied_ip_range("9.0.0.0/8".parse::<IpNet>().unwrap())
38            .unwrap()
39            .build();
40
41        assert!(acl.is_host_allowed("example.com").is_allowed());
42        assert!(acl.is_host_allowed("example.org").is_allowed());
43        assert!(!acl.is_host_allowed("example.net").is_allowed());
44        assert!(acl.is_port_allowed(8080).is_allowed());
45        assert!(!acl.is_port_allowed(8443).is_allowed());
46        assert!(acl.is_ip_allowed(&"1.1.1.1".parse().unwrap()).is_allowed());
47        assert!(acl.is_ip_allowed(&"9.9.9.9".parse().unwrap()).is_denied());
48        assert!(
49            acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
50                .is_denied()
51        );
52    }
53
54    #[test]
55    fn host_acl() {
56        let acl = HttpAclBuilder::new()
57            .add_allowed_host("example.com".to_string())
58            .unwrap()
59            .add_allowed_host("example.org".to_string())
60            .unwrap()
61            .add_denied_host("example.net".to_string())
62            .unwrap()
63            .build();
64
65        assert!(acl.is_host_allowed("example.com").is_allowed());
66        assert!(acl.is_host_allowed("example.org").is_allowed());
67        assert!(!acl.is_host_allowed("example.net").is_allowed());
68    }
69
70    #[test]
71    fn port_acl() {
72        let acl = HttpAclBuilder::new()
73            .clear_allowed_port_ranges()
74            .add_allowed_port_range(8080..=8080)
75            .unwrap()
76            .add_denied_port_range(8443..=8443)
77            .unwrap()
78            .build();
79
80        assert!(acl.is_port_allowed(8080).is_allowed());
81        assert!(!acl.is_port_allowed(8443).is_allowed());
82    }
83
84    #[test]
85    fn ip_acl() {
86        let acl = HttpAclBuilder::new()
87            .clear_allowed_ip_ranges()
88            .add_allowed_ip_range("1.0.0.0/8".parse::<IpNet>().unwrap())
89            .unwrap()
90            .add_denied_ip_range("9.0.0.0/8".parse::<IpNet>().unwrap())
91            .unwrap()
92            .build();
93
94        assert!(acl.is_ip_allowed(&"1.1.1.1".parse().unwrap()).is_allowed());
95        assert!(acl.is_ip_allowed(&"9.9.9.9".parse().unwrap()).is_denied());
96        assert!(
97            acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
98                .is_denied()
99        );
100    }
101
102    #[test]
103    fn private_ip_acl() {
104        let acl = HttpAclBuilder::new()
105            .private_ip_ranges(true)
106            .ip_acl_default(true)
107            .build();
108
109        assert!(
110            acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
111                .is_allowed()
112        );
113    }
114
115    #[test]
116    fn default_ip_acl() {
117        let acl = HttpAclBuilder::new().build();
118
119        assert!(
120            acl.is_ip_allowed(&"192.168.1.1".parse().unwrap())
121                .is_denied()
122        );
123        assert!(acl.is_ip_allowed(&"1.1.1.1".parse().unwrap()).is_denied());
124        assert!(!acl.is_port_allowed(8080).is_allowed());
125    }
126
127    #[test]
128    fn url_path_acl() {
129        let acl = HttpAclBuilder::new()
130            .add_allowed_url_path("/allowed".to_string())
131            .unwrap()
132            .add_allowed_url_path("/allowed/:id".to_string())
133            .unwrap()
134            .add_denied_url_path("/denied".to_string())
135            .unwrap()
136            .add_denied_url_path("/denied/{*path}".to_string())
137            .unwrap()
138            .build();
139
140        assert!(acl.is_url_path_allowed("/allowed").is_allowed());
141        assert!(acl.is_url_path_allowed("/allowed/allowed").is_allowed());
142        assert!(acl.is_url_path_allowed("/denied").is_denied());
143        assert!(acl.is_url_path_allowed("/denied/denied").is_denied());
144        assert!(acl.is_url_path_allowed("/denied/denied/denied").is_denied());
145    }
146
147    #[test]
148    fn header_acl() {
149        let acl = HttpAclBuilder::new()
150            .add_allowed_header("X-Allowed".to_string(), Some("true".to_string()))
151            .unwrap()
152            .add_allowed_header("X-Allowed2".to_string(), None)
153            .unwrap()
154            .add_denied_header("X-Denied".to_string(), Some("true".to_string()))
155            .unwrap()
156            .add_denied_header("X-Denied2".to_string(), None)
157            .unwrap()
158            .build();
159
160        assert!(acl.is_header_allowed("X-Allowed", "true").is_allowed());
161        assert!(acl.is_header_allowed("X-Allowed2", "false").is_allowed());
162        assert!(acl.is_header_allowed("X-Denied", "true").is_denied());
163        assert!(acl.is_header_allowed("X-Denied2", "false").is_denied());
164    }
165
166    #[test]
167    fn valid_acl() {
168        /*
169                pub type ValidateFn = Arc<
170            dyn for<'h> Fn(
171                    &str,
172                    &Authority,
173                    Box<dyn Iterator<Item = (&'h str, &'h str)> + Send + Sync + 'h>,
174                    Option<&[u8]>,
175                ) -> AclClassification
176                + Send
177                + Sync,
178        >;
179                 */
180        let acl =
181            HttpAclBuilder::new().build_full(Some(Arc::new(|scheme, authority, headers, body| {
182                if scheme == "http" {
183                    return AclClassification::DeniedUserAcl;
184                }
185
186                if authority.host.is_ip() {
187                    return AclClassification::DeniedUserAcl;
188                }
189
190                for (header_name, header_value) in headers {
191                    if header_name == "<dangerous-header>" && header_value == "<dangerous-value>" {
192                        return AclClassification::DeniedUserAcl;
193                    }
194                }
195
196                if let Some(body) = body {
197                    if body == b"<dangerous-body>" {
198                        return AclClassification::DeniedUserAcl;
199                    }
200                }
201
202                AclClassification::AllowedDefault
203            })));
204
205        assert!(
206            acl.is_valid(
207                "https",
208                &"example.com".into(),
209                [("<header>", "<value>")].into_iter(),
210                Some(b"body"),
211            )
212            .is_allowed()
213        );
214        assert!(
215            acl.is_valid(
216                "http",
217                &"example.com".into(),
218                [("<header>", "<value>")].into_iter(),
219                Some(b"body"),
220            )
221            .is_denied()
222        );
223        assert!(
224            acl.is_valid(
225                "https",
226                &"1.1.1.1".parse::<IpAddr>().unwrap().into(),
227                [("<header>", "<value>")].into_iter(),
228                Some(b"body"),
229            )
230            .is_denied()
231        );
232        assert!(
233            acl.is_valid(
234                "https",
235                &"example.com".into(),
236                [("<dangerous-header>", "<dangerous-value>")].into_iter(),
237                Some(b"body"),
238            )
239            .is_denied()
240        );
241        assert!(
242            acl.is_valid(
243                "https",
244                &"example.com".into(),
245                [("<header>", "<value>")].into_iter(),
246                Some(b"<dangerous-body>"),
247            )
248            .is_denied()
249        );
250    }
251}