1use std::net::IpAddr;
32
33use async_trait::async_trait;
34use ipnet::IpNet;
35use tracing::info;
36
37use super::policy::{Check, StageSet, Verdict};
38use super::{ConnectionContext, IdentifierContext, ListVerdict, check_lists, parse_nets};
39
40#[derive(Debug, Clone, Default)]
42pub struct Settings {
43 pub allow: Vec<String>,
44 pub deny: Vec<String>,
45}
46
47#[derive(Debug)]
49pub struct AllowedFromIpAddress {
50 allow: Vec<IpNet>,
51 deny: Vec<IpNet>,
52}
53
54impl AllowedFromIpAddress {
55 pub fn from_settings(name: &str, settings: &Settings) -> anyhow::Result<Self> {
61 if settings.allow.is_empty() && settings.deny.is_empty() {
62 anyhow::bail!(
63 "filter.check.{name} has neither allow nor deny entries, so it would accept \
64 every request; list the permitted or refused networks, or drop the check"
65 );
66 }
67
68 let allow = parse_nets(&settings.allow, &format!("filter.check.{name}.allow"))?;
69 let deny = parse_nets(&settings.deny, &format!("filter.check.{name}.deny"))?;
70 info!(event = "filter_allowed_ip_loaded", outcome = "success", check = name, allow = ?settings.allow, deny = ?settings.deny);
71 Ok(Self { allow, deny })
72 }
73
74 fn decide(&self, client_ip: Option<IpAddr>) -> Verdict {
77 let client_ip = match super::require_client_ip(client_ip) {
81 Ok(client_ip) => client_ip,
82 Err(verdict) => return verdict,
83 };
84
85 match check_lists(&self.allow, &self.deny, |net| net.contains(&client_ip)) {
87 ListVerdict::Permitted => Verdict::Pass,
88 ListVerdict::Denied => Verdict::Fail(format!("address {client_ip} is denied")),
89 ListVerdict::NotAllowed => Verdict::Fail(format!("address {client_ip} is not allowed")),
90 }
91 }
92}
93
94#[async_trait]
95impl Check for AllowedFromIpAddress {
96 fn kind(&self) -> &'static str {
97 "allowed_ip"
98 }
99
100 fn stages(&self) -> StageSet {
101 StageSet::both()
102 }
103
104 async fn check_connection(&self, context: &ConnectionContext<'_>) -> Verdict {
105 self.decide(context.client_ip)
106 }
107
108 async fn check_identifiers(&self, context: &IdentifierContext<'_>) -> Verdict {
109 self.decide(context.client_ip)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::filter::IdentifierStage;
117 use axum::http::Method;
118
119 fn settings(allow: &[&str], deny: &[&str]) -> Settings {
120 Settings {
121 allow: allow.iter().map(std::string::ToString::to_string).collect(),
122 deny: deny.iter().map(std::string::ToString::to_string).collect(),
123 }
124 }
125
126 fn check(allow: &[&str]) -> AllowedFromIpAddress {
128 AllowedFromIpAddress::from_settings("net", &settings(allow, &[])).unwrap()
129 }
130
131 fn check_with(allow: &[&str], deny: &[&str]) -> AllowedFromIpAddress {
133 AllowedFromIpAddress::from_settings("net", &settings(allow, deny)).unwrap()
134 }
135
136 fn assert_failed(verdict: Verdict, needle: &str) {
137 match verdict {
138 Verdict::Fail(detail) => {
139 assert!(detail.contains(needle), "{detail:?} lacks {needle:?}");
140 }
141 other => panic!("expected Fail, got {other:?}"),
142 }
143 }
144
145 async fn connection(check: &AllowedFromIpAddress, ip: Option<&str>) -> Verdict {
146 let client_ip: Option<IpAddr> = ip.map(|value| value.parse().unwrap());
147 check
148 .check_connection(&ConnectionContext {
149 client_ip,
150 method: &Method::POST,
151 path: "/newOrder",
152 })
153 .await
154 }
155
156 #[tokio::test]
157 async fn allows_an_address_inside_an_ipv4_cidr() {
158 let check = check(&["192.168.1.0/24"]);
159 assert_eq!(connection(&check, Some("192.168.1.5")).await, Verdict::Pass);
160 assert_eq!(
161 connection(&check, Some("192.168.1.255")).await,
162 Verdict::Pass
163 );
164 }
165
166 #[tokio::test]
167 async fn denies_an_address_outside_every_network() {
168 let check = check(&["192.168.1.0/24", "10.0.0.0/8"]);
169 assert_failed(
170 connection(&check, Some("203.0.113.9")).await,
171 "203.0.113.9 is not allowed",
172 );
173 }
174
175 #[tokio::test]
176 async fn allows_an_address_inside_an_ipv6_cidr() {
177 let check = check(&["fd00::/8"]);
178 assert_eq!(connection(&check, Some("fd00::1")).await, Verdict::Pass);
179 assert_ne!(connection(&check, Some("2001:db8::1")).await, Verdict::Pass);
180 }
181
182 #[tokio::test]
183 async fn a_bare_address_entry_is_a_host_route() {
184 let check = check(&["203.0.113.7"]);
185 assert_eq!(connection(&check, Some("203.0.113.7")).await, Verdict::Pass);
186 assert_ne!(connection(&check, Some("203.0.113.8")).await, Verdict::Pass);
187 }
188
189 #[tokio::test]
190 async fn an_ipv4_mapped_client_matches_a_v4_rule() {
191 let check = check(&["192.168.1.0/24"]);
193 assert_eq!(
194 connection(&check, Some("::ffff:192.168.1.5")).await,
195 Verdict::Pass
196 );
197 }
198
199 #[tokio::test]
200 async fn a_missing_client_address_is_denied() {
201 let check = check(&["192.168.1.0/24"]);
202 assert_failed(connection(&check, None).await, "unavailable");
203 }
204
205 #[tokio::test]
208 async fn a_missing_client_address_is_denied_in_blocklist_mode() {
209 let check = check_with(&[], &["203.0.113.9"]);
210 assert_failed(connection(&check, None).await, "unavailable");
211 }
212
213 #[tokio::test]
218 async fn a_missing_client_address_is_a_refusal_not_an_unknown() {
219 let check = check(&["192.168.1.0/24"]);
220 assert!(matches!(connection(&check, None).await, Verdict::Fail(_)));
221 }
222
223 #[tokio::test]
224 async fn a_deny_only_config_is_a_blocklist() {
225 let check = check_with(&[], &["203.0.113.9", "10.0.0.0/8"]);
226
227 assert_failed(
228 connection(&check, Some("203.0.113.9")).await,
229 "203.0.113.9 is denied",
230 );
231 assert_failed(
232 connection(&check, Some("10.4.5.6")).await,
233 "10.4.5.6 is denied",
234 );
235 assert_eq!(connection(&check, Some("192.168.1.5")).await, Verdict::Pass);
237 assert_eq!(connection(&check, Some("2001:db8::1")).await, Verdict::Pass);
238 }
239
240 #[tokio::test]
242 async fn deny_wins_over_allow() {
243 let check = check_with(&["10.0.0.0/8"], &["10.1.2.0/24"]);
244
245 assert_eq!(connection(&check, Some("10.0.0.5")).await, Verdict::Pass);
246 assert_failed(
247 connection(&check, Some("10.1.2.5")).await,
248 "10.1.2.5 is denied",
249 );
250 assert_failed(
252 connection(&check, Some("192.168.1.5")).await,
253 "192.168.1.5 is not allowed",
254 );
255 }
256
257 #[tokio::test]
260 async fn a_more_specific_allow_does_not_beat_a_broader_deny() {
261 let check = check_with(&["10.1.2.3"], &["10.0.0.0/8"]);
262 assert_failed(
263 connection(&check, Some("10.1.2.3")).await,
264 "10.1.2.3 is denied",
265 );
266 }
267
268 #[tokio::test]
269 async fn deny_accepts_bare_addresses_and_ipv6() {
270 let check = check_with(&[], &["203.0.113.7", "2001:db8::/32"]);
271
272 assert_failed(connection(&check, Some("203.0.113.7")).await, "is denied");
273 assert_eq!(connection(&check, Some("203.0.113.8")).await, Verdict::Pass);
274 assert_failed(connection(&check, Some("2001:db8::1")).await, "is denied");
275 assert_eq!(connection(&check, Some("2001:dba::1")).await, Verdict::Pass);
276 }
277
278 #[tokio::test]
281 async fn an_ipv4_mapped_client_matches_a_v4_deny_rule() {
282 let check = check_with(&[], &["192.168.1.0/24"]);
283 assert_failed(
284 connection(&check, Some("::ffff:192.168.1.5")).await,
285 "192.168.1.5 is denied",
286 );
287 }
288
289 #[tokio::test]
292 async fn it_decides_the_same_way_at_the_identifier_stage() {
293 let check = check(&["10.0.0.0/8"]);
294 let identifiers = vec![crate::sqlite::order::Identifier::dns("example.com")];
295
296 let verdict = check
297 .check_identifiers(&IdentifierContext {
298 client_ip: Some("10.0.0.5".parse().unwrap()),
299 account_id: "acct",
300 stage: IdentifierStage::NewOrder,
301 identifiers: &identifiers,
302
303 eab: None,
304 })
305 .await;
306 assert_eq!(verdict, Verdict::Pass);
307
308 let verdict = check
309 .check_identifiers(&IdentifierContext {
310 client_ip: Some("203.0.113.9".parse().unwrap()),
311 account_id: "acct",
312 stage: IdentifierStage::NewOrder,
313 identifiers: &identifiers,
314
315 eab: None,
316 })
317 .await;
318 assert_failed(verdict, "is not allowed");
319 }
320
321 #[test]
322 fn both_lists_empty_is_a_startup_error() {
323 let error = AllowedFromIpAddress::from_settings("net", &Settings::default())
324 .unwrap_err()
325 .to_string();
326 assert!(error.contains("would accept every request"), "{error}");
327 assert!(error.contains("filter.check.net"), "{error}");
328 }
329
330 #[test]
331 fn a_bad_allow_network_is_a_startup_error() {
332 let error = AllowedFromIpAddress::from_settings("net", &settings(&["192.168.1.0/99"], &[]))
333 .unwrap_err()
334 .to_string();
335 assert!(error.contains("filter.check.net.allow"), "{error}");
336 }
337
338 #[test]
339 fn a_bad_deny_network_is_a_startup_error() {
340 let error = AllowedFromIpAddress::from_settings("net", &settings(&[], &["garbage"]))
341 .unwrap_err()
342 .to_string();
343 assert!(error.contains("filter.check.net.deny"), "{error}");
344 }
345
346 #[test]
347 fn reports_its_type_and_stages() {
348 let check = check(&["10.0.0.0/8"]);
349 assert_eq!(check.kind(), "allowed_ip");
350 assert_eq!(check.stages(), StageSet::both());
351 }
352}