Skip to main content

hickory_proto/
access_control.rs

1//! Utility functions that are used in multiple crates
2use core::net::IpAddr;
3
4use ipnet::{IpNet, Ipv4Net, Ipv6Net};
5use prefix_trie::PrefixSet;
6use tracing::debug;
7
8use crate::ProtoError;
9
10/// A builder interface for constructing an [`AccessControlSet`].
11pub struct AccessControlSetBuilder(AccessControlSet);
12
13impl<'a> AccessControlSetBuilder {
14    /// Construct a builder for an access control set with the given `name`.
15    ///
16    /// The `name` is used to contextualize logging when an [`IpAddr`] is denied.
17    pub fn new(name: &'static str) -> Self {
18        Self(AccessControlSet::new(name))
19    }
20
21    /// Override the [`Self::deny()`] list for the provided IP networks, allowing access.
22    ///
23    /// Existing networks allowed by prior [`Self::allow()`] calls are not removed.
24    ///
25    /// See [`AccessControlSet`] for more information on the access semantics.
26    pub fn allow(mut self, allow: impl Iterator<Item = &'a IpNet>) -> Self {
27        for network in allow {
28            debug!(name = self.0.name, ?network, "appending to allow list");
29            match network {
30                IpNet::V4(network) => {
31                    self.0.v4_allow.insert(*network);
32                }
33                IpNet::V6(network) => {
34                    self.0.v6_allow.insert(*network);
35                }
36            }
37        }
38        self
39    }
40
41    /// Deny clients from the provided IP networks, unless present in the [`Self::allow()`] list.
42    ///
43    /// Existing networks denied by prior [`Self::deny()`] calls are not removed.
44    ///
45    /// See [`AccessControlSet`] for more information on the access semantics.
46    pub fn deny(mut self, deny: impl Iterator<Item = &'a IpNet>) -> Self {
47        for network in deny {
48            debug!(name = self.0.name, ?network, "appending to deny list");
49            match network {
50                IpNet::V4(network) => {
51                    self.0.v4_deny.insert(*network);
52                }
53                IpNet::V6(network) => {
54                    self.0.v6_deny.insert(*network);
55                }
56            }
57        }
58        self
59    }
60
61    /// Clear all IP networks previous allowed with [`Self::allow()`].
62    pub fn clear_allow(mut self) -> Self {
63        self.0.v4_allow.clear();
64        self.0.v6_allow.clear();
65        self
66    }
67
68    /// Clear all IP networks previously denied with [`Self::deny()`].
69    pub fn clear_deny(mut self) -> Self {
70        self.0.v4_deny.clear();
71        self.0.v6_deny.clear();
72        self
73    }
74
75    /// Consume the builder and produce an [`AccessControlSet`].
76    ///
77    /// Returns an error if [`Self::allow()`] was used to add deny list override networks
78    /// without using [`Self::deny()`] to specify one or more denied networks.
79    pub fn build(self) -> Result<AccessControlSet, ProtoError> {
80        let deny_empty = self.0.v4_deny.is_empty() && self.0.v6_deny.is_empty();
81        let allowed_count = self.0.v4_allow.iter().count() + self.0.v6_allow.iter().count();
82        if deny_empty && allowed_count != 0 {
83            return Err(format!(
84                "access control set {name:?} has {allowed_count} allowed overrides, but no denied networks to override",
85                name = self.0.name
86            ).into());
87        }
88        Ok(self.0)
89    }
90}
91
92/// An IPv4/IPv6 access control set.
93///
94/// Use [`AccessControlSetBuilder`] to construct an instance.
95/// When determining if an [`IpAddr`] is denied with [`Self::denied()`], the deny list
96/// is considered first, and the allow list may override the deny
97/// decision.
98///
99/// The full access semantics are:
100///
101/// | Present in deny list | Present in allow list |  Result  |
102/// |-----------------------|----------------------|----------|
103/// |                  true |                false |  denied |
104/// |                 false |                false |  allowed |
105/// |                  true |                 true |  allowed |
106/// |                 false |                 true |  allowed |
107#[derive(Clone, Debug)]
108pub struct AccessControlSet {
109    name: &'static str,
110    v4_allow: PrefixSet<Ipv4Net>,
111    v4_deny: PrefixSet<Ipv4Net>,
112    v6_allow: PrefixSet<Ipv6Net>,
113    v6_deny: PrefixSet<Ipv6Net>,
114}
115
116impl AccessControlSet {
117    /// Construct an access control set with the given `name`.
118    ///
119    /// The name is used to contextualize logging when an [`IpAddr`] is denied.
120    fn new(name: &'static str) -> Self {
121        Self {
122            name,
123            v4_allow: PrefixSet::new(),
124            v4_deny: PrefixSet::new(),
125            v6_allow: PrefixSet::new(),
126            v6_deny: PrefixSet::new(),
127        }
128    }
129
130    /// Construct an empty access control set with the given `name` that allows all networks.
131    pub fn empty(name: &'static str) -> Self {
132        Self::new(name)
133    }
134
135    /// Returns true if the access control set allows all addresses.
136    ///
137    /// This is true when no IPv4 or IPv6 networks are denied.
138    pub fn allows_all(&self) -> bool {
139        self.v4_deny.is_empty() && self.v6_deny.is_empty()
140    }
141
142    /// Check if the IP address `ip` should be denied.
143    ///
144    /// If the IP address is in a network previously denied by [`AccessControlSetBuilder::deny()`]
145    /// that wasn't explicitly allowed with [`AccessControlSetBuilder::allow()`], this function
146    /// will return true.
147    ///
148    /// All other combinations will return false (i.e., [`AccessControlSetBuilder::allow()`] acts
149    /// like an exception list for [`AccessControlSetBuilder::deny()`])
150    pub fn denied(&self, ip: IpAddr) -> bool {
151        // If both deny lists are empty, short-circuit. There's nothing to consider.
152        if self.allows_all() {
153            return false;
154        }
155        match ip {
156            IpAddr::V4(ip) => {
157                self.v4_allow.get_spm(&ip.into()).is_none()
158                    && self.v4_deny.get_spm(&ip.into()).is_some()
159            }
160            IpAddr::V6(ip) => {
161                self.v6_allow.get_spm(&ip.into()).is_none()
162                    && self.v6_deny.get_spm(&ip.into()).is_some()
163            }
164        }
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use crate::access_control::{AccessControlSet, AccessControlSetBuilder};
171
172    #[test]
173    fn access_control_set_networks_test() {
174        let acs = AccessControlSetBuilder::new("test acs")
175            .deny(
176                [
177                    "10.0.0.0/8".parse().unwrap(),
178                    "172.16.0.0/12".parse().unwrap(),
179                    "192.168.0.0/16".parse().unwrap(),
180                    "fe80::/10".parse().unwrap(),
181                ]
182                .iter(),
183            )
184            .allow(
185                [
186                    "10.1.0.3/29".parse().unwrap(),
187                    "192.168.1.10/32".parse().unwrap(),
188                    "fe80::200/128".parse().unwrap(),
189                ]
190                .iter(),
191            )
192            .build()
193            .unwrap();
194
195        // 10.1.0.3/29 above should cause 10.1.0.0/29 to be placed into the allow list; validate the
196        // address before and after are blocked, and addresses within the subnet are allowed
197        assert!(acs.denied([10, 0, 254, 254].into()));
198        assert!(!acs.denied([10, 1, 0, 0].into()));
199        assert!(!acs.denied([10, 1, 0, 3].into()));
200        assert!(!acs.denied([10, 1, 0, 7].into()));
201        assert!(acs.denied([10, 1, 0, 8].into()));
202
203        assert!(acs.denied([192, 168, 1, 1].into()));
204        assert!(!acs.denied([192, 168, 1, 10].into()));
205
206        assert!(!acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 0x200].into()));
207        assert!(acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 1].into()));
208    }
209
210    // Test the access control semantics described in the Rustdoc of `AccessControlSet`
211    #[test]
212    fn access_control_semantics_test() {
213        struct TestCase {
214            name: &'static str,
215            in_deny: bool,
216            in_allow: bool,
217            expected_build_err: bool,
218            expected_denied: bool,
219        }
220
221        let test_cases = [
222            TestCase {
223                name: "deny=true, allow=false -> denied",
224                in_deny: true,
225                in_allow: false,
226                expected_build_err: false,
227                expected_denied: true,
228            },
229            TestCase {
230                name: "deny=false, allow=false -> allowed",
231                in_deny: false,
232                in_allow: false,
233                expected_build_err: false,
234                expected_denied: false,
235            },
236            TestCase {
237                name: "deny=true, allow=true -> allowed",
238                in_deny: true,
239                in_allow: true,
240                expected_build_err: false,
241                expected_denied: false,
242            },
243            TestCase {
244                name: "deny=false, allow=true -> allowed",
245                in_deny: false,
246                in_allow: true,
247                expected_build_err: true,
248                expected_denied: false,
249            },
250        ];
251
252        let test_v4 = [192, 0, 2, 1].into();
253        let test_v4_net = "192.0.2.0/24".parse().unwrap();
254        let test_v6 = [0x2001, 0xdb8, 0, 0, 0, 0, 0, 1].into();
255        let test_v6_net = "2001:db8::/32".parse().unwrap();
256
257        for tc in &test_cases {
258            let mut builder = AccessControlSetBuilder::new(tc.name);
259            if tc.in_deny {
260                builder = builder.deny([test_v4_net, test_v6_net].iter());
261            }
262            if tc.in_allow {
263                builder = builder.allow([test_v4_net, test_v6_net].iter());
264            }
265
266            let Ok(acs) = builder.build() else {
267                match tc.expected_build_err {
268                    true => continue,
269                    false => panic!("unexpected builder error"),
270                }
271            };
272            assert_eq!(
273                acs.denied(test_v4),
274                tc.expected_denied,
275                "IPv4 case '{}' failed",
276                tc.name
277            );
278            assert_eq!(
279                acs.denied(test_v6),
280                tc.expected_denied,
281                "IPv6 case '{}' failed",
282                tc.name
283            );
284        }
285    }
286
287    #[test]
288    fn allows_all_test() {
289        let empty = AccessControlSet::empty("empty");
290        assert!(empty.allows_all());
291
292        let v4_only = AccessControlSetBuilder::new("v4 only")
293            .deny(["10.0.0.0/8".parse().unwrap()].iter())
294            .build()
295            .unwrap();
296        assert!(!v4_only.allows_all());
297
298        let v6_only = AccessControlSetBuilder::new("v6 only")
299            .deny(["fe80::/10".parse().unwrap()].iter())
300            .build()
301            .unwrap();
302        assert!(!v6_only.allows_all());
303
304        let both = AccessControlSetBuilder::new("both")
305            .deny(["10.0.0.0/8".parse().unwrap(), "fe80::/10".parse().unwrap()].iter())
306            .build()
307            .unwrap();
308        assert!(!both.allows_all());
309    }
310
311    #[test]
312    fn v4_only_deny_test() {
313        let acs = AccessControlSetBuilder::new("v4 only deny")
314            .deny(["10.0.0.0/8".parse().unwrap()].iter())
315            .build()
316            .unwrap();
317
318        assert!(!acs.allows_all());
319        assert!(acs.denied([10, 0, 0, 1].into()));
320        assert!(acs.denied([10, 255, 255, 255].into()));
321        assert!(!acs.denied([11, 0, 0, 1].into()));
322        assert!(!acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 1].into()));
323    }
324
325    #[test]
326    fn v6_only_deny_test() {
327        let acs = AccessControlSetBuilder::new("v6 only deny")
328            .deny(["fe80::/10".parse().unwrap()].iter())
329            .build()
330            .unwrap();
331
332        assert!(!acs.allows_all());
333        assert!(acs.denied([0xfe80, 0, 0, 0, 0, 0, 0, 1].into()));
334        assert!(
335            acs.denied(
336                [
337                    0xfebf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
338                ]
339                .into()
340            )
341        );
342        assert!(!acs.denied([0xfec0, 0, 0, 0, 0, 0, 0, 1].into()));
343        assert!(!acs.denied([10, 0, 0, 1].into()));
344    }
345}