1use super::{
11 rule::{BuiltRule, Rule},
12 Iptables, IptablesRule, IptablesRuleDiscriminants, PolicyOrRule, DFW_FORWARD_CHAIN,
13 DFW_INPUT_CHAIN, DFW_POSTROUTING_CHAIN, DFW_PREROUTING_CHAIN,
14};
15use crate::{errors::*, process::*, types::*, FirewallBackend};
16use failure::{format_err, ResultExt};
17use slog::{debug, info, o, trace};
18use std::collections::BTreeSet;
19use time::format_description::well_known::Rfc3339;
20
21impl Process<Iptables> for DFW<Iptables> {
22 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
23 info!(ctx.logger, "Starting processing";
24 o!("started_processing_at" => time::OffsetDateTime::now_utc().format(&Rfc3339).expect("failed to format time")));
25 let mut rules = vec![
26 new_chain(IptablesRuleDiscriminants::V4, "filter", DFW_FORWARD_CHAIN),
27 flush_chain(IptablesRuleDiscriminants::V4, "filter", DFW_FORWARD_CHAIN),
28 new_chain(IptablesRuleDiscriminants::V6, "filter", DFW_FORWARD_CHAIN),
29 flush_chain(IptablesRuleDiscriminants::V6, "filter", DFW_FORWARD_CHAIN),
30 new_chain(IptablesRuleDiscriminants::V4, "filter", DFW_INPUT_CHAIN),
31 flush_chain(IptablesRuleDiscriminants::V4, "filter", DFW_INPUT_CHAIN),
32 new_chain(IptablesRuleDiscriminants::V6, "filter", DFW_INPUT_CHAIN),
33 flush_chain(IptablesRuleDiscriminants::V6, "filter", DFW_INPUT_CHAIN),
34 new_chain(IptablesRuleDiscriminants::V4, "nat", DFW_POSTROUTING_CHAIN),
35 flush_chain(IptablesRuleDiscriminants::V4, "nat", DFW_POSTROUTING_CHAIN),
36 new_chain(IptablesRuleDiscriminants::V6, "nat", DFW_POSTROUTING_CHAIN),
37 flush_chain(IptablesRuleDiscriminants::V6, "nat", DFW_POSTROUTING_CHAIN),
38 new_chain(IptablesRuleDiscriminants::V4, "nat", DFW_PREROUTING_CHAIN),
39 flush_chain(IptablesRuleDiscriminants::V4, "nat", DFW_PREROUTING_CHAIN),
40 new_chain(IptablesRuleDiscriminants::V6, "nat", DFW_PREROUTING_CHAIN),
41 flush_chain(IptablesRuleDiscriminants::V6, "nat", DFW_PREROUTING_CHAIN),
42 append_rule(
43 IptablesRuleDiscriminants::V4,
44 "filter",
45 DFW_INPUT_CHAIN,
46 "-m state --state INVALID -j DROP",
47 ),
48 append_rule(
49 IptablesRuleDiscriminants::V6,
50 "filter",
51 DFW_INPUT_CHAIN,
52 "-m state --state INVALID -j DROP",
53 ),
54 append_rule(
55 IptablesRuleDiscriminants::V4,
56 "filter",
57 DFW_INPUT_CHAIN,
58 "-m state --state RELATED,ESTABLISHED -j ACCEPT",
59 ),
60 append_rule(
61 IptablesRuleDiscriminants::V6,
62 "filter",
63 DFW_INPUT_CHAIN,
64 "-m state --state RELATED,ESTABLISHED -j ACCEPT",
65 ),
66 append_rule(
67 IptablesRuleDiscriminants::V4,
68 "filter",
69 "INPUT",
70 &format!("-j {}", DFW_INPUT_CHAIN),
71 ),
72 set_policy(IptablesRuleDiscriminants::V4, "filter", "INPUT", "-"),
73 append_rule(
74 IptablesRuleDiscriminants::V4,
75 "filter",
76 DFW_FORWARD_CHAIN,
77 "-m state --state INVALID -j DROP",
78 ),
79 append_rule(
80 IptablesRuleDiscriminants::V6,
81 "filter",
82 DFW_FORWARD_CHAIN,
83 "-m state --state INVALID -j DROP",
84 ),
85 append_rule(
86 IptablesRuleDiscriminants::V4,
87 "filter",
88 DFW_FORWARD_CHAIN,
89 "-m state --state RELATED,ESTABLISHED -j ACCEPT",
90 ),
91 append_rule(
92 IptablesRuleDiscriminants::V6,
93 "filter",
94 DFW_FORWARD_CHAIN,
95 "-m state --state RELATED,ESTABLISHED -j ACCEPT",
96 ),
97 append_rule(
98 IptablesRuleDiscriminants::V4,
99 "filter",
100 "FORWARD",
101 &format!("-j {}", DFW_FORWARD_CHAIN),
102 ),
103 set_policy(IptablesRuleDiscriminants::V4, "filter", "FORWARD", "-"),
104 append_rule(
105 IptablesRuleDiscriminants::V4,
106 "nat",
107 "PREROUTING",
108 &format!("-j {}", DFW_PREROUTING_CHAIN),
109 ),
110 set_policy(IptablesRuleDiscriminants::V4, "nat", "PREROUTING", "-"),
111 append_rule(
112 IptablesRuleDiscriminants::V4,
113 "nat",
114 "POSTROUTING",
115 &format!("-j {}", DFW_POSTROUTING_CHAIN),
116 ),
117 set_policy(IptablesRuleDiscriminants::V4, "nat", "POSTROUTING", "-"),
118 ];
119 for mut sub_rules in vec![
120 self.backend_defaults.process(ctx)?,
121 self.container_to_container.process(ctx)?,
122 self.container_to_wider_world.process(ctx)?,
123 self.container_to_host.process(ctx)?,
124 self.wider_world_to_container.process(ctx)?,
125 self.container_dnat.process(ctx)?,
126 self.global_defaults.process(ctx)?,
127 ]
128 .into_iter()
129 .flatten()
130 {
131 rules.append(&mut sub_rules);
132 }
133
134 info!(ctx.logger, "Finished processing";
135 o!("finished_processing_at" => time::OffsetDateTime::now_utc().format(&Rfc3339).expect("failed to format time")));
136
137 Ok(Some(rules))
138 }
139}
140
141impl Process<Iptables> for <Iptables as FirewallBackend>::Defaults {
142 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
143 debug!(ctx.logger, "Starting sub-processing";
144 o!("part" => "backend_defaults"));
145 let mut rules = Vec::new();
146
147 if let Some(initialization) = &self.initialization {
148 if let Some(v4) = &initialization.v4 {
149 debug!(ctx.logger, "Process initialization rules";
150 o!("ip" => "v4"));
151 for (table, initialization_rules) in v4.iter() {
152 debug!(ctx.logger, "Process table";
153 o!("ip" => "v4",
154 "table" => table));
155 for rule in initialization_rules {
156 debug!(ctx.logger, "Process rule";
157 o!("ip" => "v4",
158 "table" => table,
159 "rule" => rule));
160 rules.push(add_rule(IptablesRuleDiscriminants::V4, table, rule));
161 trace!(ctx.logger, "Rule added";
162 o!("ip" => "v4",
163 "table" => table,
164 "rule" => rule))
165 }
166 }
167 }
168
169 if let Some(v6) = &initialization.v6 {
170 debug!(ctx.logger, "Process initialization rules";
171 o!("ip" => "v6"));
172 for (table, initialization_rules) in v6.iter() {
173 debug!(ctx.logger, "Process table";
174 o!("ip" => "v6",
175 "table" => table));
176 for rule in initialization_rules {
177 debug!(ctx.logger, "Process rule";
178 o!("ip" => "v6",
179 "table" => table,
180 "rule" => rule));
181 rules.push(add_rule(IptablesRuleDiscriminants::V6, table, rule));
182 trace!(ctx.logger, "Rule added";
183 o!("ip" => "v6",
184 "table" => table,
185 "rule" => rule))
186 }
187 }
188 }
189 }
190
191 Ok(Some(rules))
192 }
193}
194
195impl Process<Iptables> for GlobalDefaults {
196 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
197 debug!(ctx.logger, "Starting sub-processing";
198 o!("part" => "global_defaults"));
199 let mut rules = Vec::new();
200
201 if let Some(bridge_network) = ctx.network_map.get("bridge") {
203 if let Some(bridge_name) = bridge_network
204 .options
205 .as_ref()
206 .ok_or_else(|| format_err!("couldn't get network options"))?
207 .get("com.docker.network.bridge.name")
208 {
209 let rule = Rule::new("filter", DFW_INPUT_CHAIN)
211 .in_interface(bridge_name)
212 .jump(
213 &self
214 .default_docker_bridge_to_host_policy
215 .to_string()
216 .to_uppercase(),
217 )
218 .build()?;
219 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &rule));
220
221 if let Some(ref external_network_interfaces) = self.external_network_interfaces {
223 for external_network_interface in external_network_interfaces {
224 let rule = Rule::new("filter", DFW_FORWARD_CHAIN)
225 .in_interface(bridge_name)
226 .out_interface(external_network_interface)
227 .jump("ACCEPT")
228 .build()?;
229 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &rule));
230 }
231 }
232 }
233 }
234
235 if let Some(ref external_network_interfaces) = self.external_network_interfaces {
237 for external_network_interface in external_network_interfaces {
238 let rule = Rule::new("nat", DFW_POSTROUTING_CHAIN)
240 .out_interface(external_network_interface)
241 .jump("MASQUERADE")
242 .build()?;
243 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &rule));
244 }
245 }
246
247 Ok(Some(rules))
248 }
249}
250
251impl Process<Iptables> for ContainerToContainer {
252 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
253 debug!(ctx.logger, "Starting sub-processing";
254 o!("part" => "container_to_container"));
255 let mut rules = Vec::new();
256
257 if let Some(mut ctc_rules) = self.rules.process(ctx)? {
258 rules.append(&mut ctc_rules);
259 }
260
261 if let Some(same_network_verdict) = self.same_network_verdict {
262 for network in ctx.network_map.values() {
263 let network_id = network.id.as_ref().expect("Docker network ID missing");
264 let bridge_name = get_bridge_name(network_id)?;
265 trace!(ctx.logger, "Got bridge name";
266 o!("network_name" => &network.name,
267 "bridge_name" => &bridge_name));
268
269 let ipt_rule = Rule::new("filter", DFW_FORWARD_CHAIN)
270 .in_interface(&bridge_name)
271 .out_interface(&bridge_name)
272 .jump(&same_network_verdict.to_string().to_uppercase())
273 .build()?;
274
275 debug!(ctx.logger, "Add forward rule for same network verdict for bridge";
276 o!("part" => "container_to_container",
277 "bridge_name" => bridge_name,
278 "same_network_verdict" => same_network_verdict,
279 "rule" => &ipt_rule));
280
281 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &ipt_rule));
282 }
283 }
284
285 let mut bridges = BTreeSet::new();
288 for network in ctx.network_map.values() {
289 if network.driver.as_deref() != Some("bridge") {
290 continue;
291 }
292 let bridge = match network
293 .options
294 .as_ref()
295 .and_then(|options| options.get("com.docker.network.bridge.name"))
296 {
297 Some(name) => name.clone(),
298 None => match network.id.as_deref() {
299 Some(id) => get_bridge_name(id)?,
300 None => continue,
301 },
302 };
303 bridges.insert(bridge);
304 }
305 if !bridges.is_empty() {
306 const DEFAULT_CHAIN: &str = "DFWRS_CTC_DEFAULT";
307 let family = IptablesRuleDiscriminants::V4;
308 rules.push(new_chain(family, "filter", DEFAULT_CHAIN));
309 rules.push(flush_chain(family, "filter", DEFAULT_CHAIN));
310 let verdict = self.default_policy.to_string().to_uppercase();
311 for bridge in &bridges {
312 let input = Rule::new("filter", DFW_FORWARD_CHAIN)
313 .in_interface(bridge)
314 .jump(DEFAULT_CHAIN)
315 .build()?;
316 rules.push(append_built_rule(family, &input));
317 let output = Rule::new("filter", DEFAULT_CHAIN)
318 .out_interface(bridge)
319 .jump(&verdict)
320 .build()?;
321 rules.push(append_built_rule(family, &output));
322 }
323 }
324
325 Ok(Some(rules))
326 }
327}
328
329impl Process<Iptables> for ContainerToContainerRule {
330 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
331 trace!(ctx.logger, "Processing rule";
332 o!("part" => "container_to_container_rule",
333 "rule" => format!("{:?}", self)));
334 let mut ipt_rule = Rule::new("filter", DFW_FORWARD_CHAIN);
335
336 let network = match ctx.network_map.get(&self.network) {
337 Some(network) => network,
338 None => return Ok(None),
339 };
340 trace!(ctx.logger, "Got network";
341 o!("network_name" => &self.network,
342 "network" => format!("{:?}", network)));
343
344 let network_id = network.id.as_ref().expect("Docker network ID missing");
345 let bridge_name = get_bridge_name(network_id)?;
346 trace!(ctx.logger, "Got bridge name";
347 o!("network_name" => &network.name,
348 "bridge_name" => &bridge_name));
349
350 ipt_rule
351 .in_interface(&bridge_name)
352 .out_interface(&bridge_name);
353
354 if let Some(ref src_container) = self.src_container {
355 let src_network = match get_network_for_container(
356 ctx.docker,
357 &ctx.container_map,
358 src_container,
359 network_id,
360 )? {
361 Some(src_network) => src_network,
362 None => return Ok(None),
363 };
364 trace!(ctx.logger, "Got source network";
365 o!("network_name" => &network.name,
366 "src_network" => format!("{:?}", src_network)));
367
368 let bridge_name = get_bridge_name(network_id)?;
369 trace!(ctx.logger, "Got bridge name";
370 o!("network_name" => &network.name,
371 "bridge_name" => &bridge_name));
372
373 ipt_rule
374 .in_interface(&bridge_name)
375 .out_interface(&bridge_name)
376 .source(
377 src_network
378 .ipv4_address
379 .expect("IPv4 address for container missing")
380 .split('/')
381 .next()
382 .ok_or_else(|| format_err!("IPv4 address is empty"))?,
383 );
384 }
385
386 if let Some(ref dst_container) = self.dst_container {
387 let dst_network = match get_network_for_container(
388 ctx.docker,
389 &ctx.container_map,
390 dst_container,
391 network_id,
392 )? {
393 Some(dst_network) => dst_network,
394 None => return Ok(None),
395 };
396 trace!(ctx.logger, "Got destination network";
397 o!("network_name" => &network.name,
398 "dst_network" => format!("{:?}", dst_network)));
399
400 let bridge_name = get_bridge_name(network_id)?;
401 trace!(ctx.logger, "Got bridge name";
402 o!("network_name" => &network.name,
403 "bridge_name" => &bridge_name));
404
405 ipt_rule.out_interface(&bridge_name).destination(
406 dst_network
407 .ipv4_address
408 .expect("IPv4 address for container missing")
409 .split('/')
410 .next()
411 .ok_or_else(|| format_err!("IPv4 address is empty"))?,
412 );
413 }
414
415 if let Some(ref filter) = self.matches {
416 ipt_rule.filter(filter);
417 }
418
419 ipt_rule.jump(&self.verdict.to_string().to_uppercase());
421
422 let rule = ipt_rule.build()?;
423 debug!(ctx.logger, "Add forward rule";
424 o!("part" => "container_to_container",
425 "rule" => &rule.rule));
426
427 Ok(Some(vec![append_built_rule(
428 IptablesRuleDiscriminants::V4,
429 &rule,
430 )]))
431 }
432}
433
434impl Process<Iptables> for ContainerToWiderWorld {
435 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
436 debug!(ctx.logger, "Starting sub-processing";
437 o!("part" => "container_to_wider_world"));
438 let mut rules = Vec::new();
439
440 if let Some(mut ctww_rules) = self.rules.process(ctx)? {
441 rules.append(&mut ctww_rules);
442 }
443
444 if let Some(external_network_interfaces) = &ctx.external_network_interfaces {
446 debug!(ctx.logger, "Set default policy for external network interfaces";
447 o!("part" => "container_to_wider_world",
448 "external_network_interfaces" => format!("{:?}", external_network_interfaces),
449 "default_policy" => &self.default_policy));
450 for external_network_interface in external_network_interfaces {
451 trace!(ctx.logger, "Process default policy for external network interface";
452 o!("part" => "container_to_wider_world",
453 "external_network_interface" => external_network_interface,
454 "default_policy" => &self.default_policy));
455 for network in ctx.network_map.values() {
456 let network_id = network.id.as_ref().expect("Docker network ID missing");
457 let bridge_name = get_bridge_name(network_id)?;
458 trace!(ctx.logger, "Got bridge name";
459 o!("network_name" => &network.name,
460 "bridge_name" => &bridge_name));
461
462 let rule = Rule::new("filter", DFW_FORWARD_CHAIN)
463 .in_interface(&bridge_name)
464 .out_interface(external_network_interface)
465 .jump(&self.default_policy.to_string().to_uppercase())
466 .build()?;
467
468 debug!(ctx.logger, "Add forward rule for default policy";
469 o!("part" => "container_to_wider_world",
470 "external_network_interface" => external_network_interface,
471 "default_policy" => &self.default_policy,
472 "rule" => &rule));
473
474 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &rule));
475 }
476 }
477 }
478
479 Ok(Some(rules))
480 }
481}
482
483impl Process<Iptables> for ContainerToWiderWorldRule {
484 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
485 trace!(ctx.logger, "Process rule";
486 o!("part" => "container_to_wider_world_rule",
487 "rule" => format!("{:?}", self)));
488
489 let mut ipt_rule = Rule::new("filter", DFW_FORWARD_CHAIN);
490
491 if self.network.is_none() && self.src_container.is_some() {
492 return Err(format_err!(
493 "a container-to-wider-world rule with src_container requires network"
494 ));
495 }
496
497 if let Some(ref network_name) = self.network {
498 let Some(network) = ctx.network_map.get(network_name) else {
499 return Ok(None);
500 };
501 let network_id = network.id.as_ref().expect("Docker network ID missing");
502 let bridge_name = get_bridge_name(network_id)?;
503 trace!(ctx.logger, "Got bridge name";
504 o!("network_name" => network_name,
505 "bridge_name" => &bridge_name));
506
507 ipt_rule.in_interface(&bridge_name);
508
509 if let Some(ref src_container) = self.src_container {
510 let Some(src_network) = get_network_for_container(
511 ctx.docker,
512 &ctx.container_map,
513 src_container,
514 network_id,
515 )?
516 else {
517 return Ok(None);
518 };
519 let Some(address) = container_ipv4(&src_network) else {
520 return Ok(None);
521 };
522 ipt_rule.source(&address.to_string());
523 }
524 }
525
526 if let Some(ref filter) = self.matches {
527 ipt_rule.filter(filter);
528 }
529
530 ipt_rule.jump(&self.verdict.to_string().to_uppercase());
531
532 debug!(ctx.logger, "Build rule to verify contents";
535 o!("args" => format!("{:?}", ipt_rule)));
536 ipt_rule.build().context(format!(
537 "failed to build rule, maybe the network `{:?}` or container `{:?}` doesn't exist",
538 self.network, self.src_container
539 ))?;
540
541 if let Some(ref external_network_interface) = self.external_network_interface {
542 trace!(ctx.logger, "Rule has specific external network interface";
543 o!("external_network_interface" => external_network_interface));
544 ipt_rule.out_interface(external_network_interface);
545 } else if let Some(ref primary_external_network_interface) =
546 ctx.primary_external_network_interface
547 {
548 trace!(ctx.logger, "Rule uses primary external network interface";
549 o!("external_network_interface" => primary_external_network_interface));
550 ipt_rule.out_interface(primary_external_network_interface);
551 }
552
553 let rule = ipt_rule.build()?;
554 debug!(ctx.logger, "Add forward rule";
555 o!("part" => "container_to_wider_world",
556 "rule" => &rule.rule));
557
558 Ok(Some(vec![append_built_rule(
559 IptablesRuleDiscriminants::V4,
560 &rule,
561 )]))
562 }
563}
564
565impl Process<Iptables> for ContainerToHost {
566 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
567 debug!(ctx.logger, "Starting sub-processing";
568 o!("part" => "container_to_host"));
569 let mut rules = Vec::new();
570
571 if let Some(mut cth_rules) = self.rules.process(ctx)? {
572 rules.append(&mut cth_rules);
573 }
574
575 for network in ctx.network_map.values() {
577 let network_id = network.id.as_ref().expect("Docker network ID missing");
578 let bridge_name = get_bridge_name(network_id)?;
579 trace!(ctx.logger, "Got bridge name";
580 o!("network_name" => &network.name,
581 "bridge_name" => &bridge_name));
582
583 let rule = Rule::new("filter", DFW_INPUT_CHAIN)
584 .in_interface(&bridge_name)
585 .jump(&self.default_policy.to_string().to_uppercase())
586 .build()?;
587
588 trace!(ctx.logger, "Add input rule for default policy";
589 o!("part" => "container_to_host",
590 "default_policy" => self.default_policy,
591 "rule" => &rule));
592 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &rule));
593 }
594
595 Ok(Some(rules))
596 }
597}
598
599impl Process<Iptables> for ContainerToHostRule {
600 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
601 trace!(ctx.logger, "Process rule";
602 o!("part" => "container_to_host_rule",
603 "rule" => format!("{:?}", self)));
604 let mut ipt_rule = Rule::new("filter", DFW_INPUT_CHAIN);
605
606 let network = match ctx.network_map.get(&self.network) {
607 Some(network) => network,
608 None => return Ok(None),
609 };
610 trace!(ctx.logger, "Got network";
611 o!("network_name" => &network.name,
612 "network" => format!("{:?}", network)));
613
614 let network_id = network.id.as_ref().expect("Docker network ID missing");
615 let bridge_name = get_bridge_name(network_id)?;
616 trace!(ctx.logger, "Got bridge name";
617 o!("network_name" => &network.name,
618 "bridge_name" => &bridge_name));
619
620 ipt_rule.in_interface(&bridge_name);
621
622 if let Some(ref src_container) = self.src_container {
623 let src_network = match get_network_for_container(
624 ctx.docker,
625 &ctx.container_map,
626 src_container,
627 network_id,
628 )? {
629 Some(src_network) => src_network,
630 None => return Ok(None),
631 };
632 trace!(ctx.logger, "Got source network";
633 o!("network_name" => &network.name,
634 "src_network" => format!("{:?}", src_network)));
635 ipt_rule.source(
636 src_network
637 .ipv4_address
638 .expect("IPv4 address for container missing")
639 .split('/')
640 .next()
641 .ok_or_else(|| format_err!("IPv4 address is empty"))?,
642 );
643 }
644
645 if let Some(ref filter) = self.matches {
646 ipt_rule.filter(filter);
647 }
648
649 ipt_rule.jump(&self.verdict.to_string().to_uppercase());
650
651 debug!(ctx.logger, "Build rule to verify contents";
654 o!("args" => format!("{:?}", ipt_rule)));
655 ipt_rule.build().context(format!(
656 "failed to build rule, maybe the container `{:?}` doesn't exist",
657 self.src_container
658 ))?;
659
660 let rule = ipt_rule.build()?;
661 debug!(ctx.logger, "Add input rule";
662 o!("part" => "container_to_host",
663 "rule" => &rule));
664
665 Ok(Some(vec![append_built_rule(
666 IptablesRuleDiscriminants::V4,
667 &rule,
668 )]))
669 }
670}
671
672impl Process<Iptables> for WiderWorldToContainer {
673 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
674 debug!(ctx.logger, "Starting sub-processing";
675 o!("part" => "wider_world_to_container"));
676 if self.rules.is_some() {
677 debug!(ctx.logger, "Process rules";
678 o!("part" => "wider_world_to_container"));
679 self.rules.process(ctx)
680 } else {
681 trace!(ctx.logger, "No rules";
682 o!("part" => "wider_world_to_container"));
683 Ok(None)
684 }
685 }
686}
687
688impl Process<Iptables> for WiderWorldToContainerRule {
689 #[allow(clippy::cognitive_complexity)]
690 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
691 trace!(ctx.logger, "Process rule";
692 o!("part" => "wider_world_to_container_rule",
693 "rule" => format!("{:?}", self)));
694 let mut rules = Vec::new();
695
696 for expose_port in &self.expose_port {
697 let mut ipt_forward_rule = Rule::new("filter", DFW_FORWARD_CHAIN);
698 let mut ipt_dnat_rule = Rule::new("nat", DFW_PREROUTING_CHAIN);
699 let mut ipt6_input_rule = Rule::new("filter", DFW_INPUT_CHAIN);
700
701 let network = match ctx.network_map.get(&self.network) {
702 Some(network) => network,
703 None => continue,
704 };
705 trace!(ctx.logger, "Got network";
706 o!("network_name" => &network.name,
707 "network" => format!("{:?}", network)));
708
709 let network_id = network.id.as_ref().expect("Docker network ID missing");
710 let bridge_name = get_bridge_name(network_id)?;
711 trace!(ctx.logger, "Got bridge name";
712 o!("network_name" => &network.name,
713 "bridge_name" => &bridge_name));
714
715 ipt_forward_rule.out_interface(&bridge_name);
716
717 if let Some(dst_network) = get_network_for_container(
718 ctx.docker,
719 &ctx.container_map,
720 &self.dst_container,
721 network_id,
722 )? {
723 trace!(ctx.logger, "Got destination network";
724 o!("network_name" => &network.name,
725 "dst_network" => format!("{:?}", dst_network)));
726
727 let container_ipv4_address = dst_network
728 .ipv4_address
729 .expect("IPv4 address for container missing");
730 ipt_forward_rule.destination(
731 container_ipv4_address
732 .split('/')
733 .next()
734 .ok_or_else(|| format_err!("IPv4 address is empty"))?,
735 );
736
737 let host_port = expose_port.host_port.to_string();
738 let container_port = match expose_port.container_port {
739 Some(destination_port) => destination_port.to_string(),
740 None => expose_port.host_port.to_string(),
741 };
742 ipt_forward_rule.destination_port(&container_port);
743 ipt_dnat_rule.destination_port(&host_port);
744 ipt_dnat_rule.jump(&format!(
745 "DNAT --to-destination {}:{}",
746 container_ipv4_address
747 .split('/')
748 .next()
749 .ok_or_else(|| format_err!("IPv4 address is empty"))?,
750 container_port
751 ));
752 ipt6_input_rule.destination_port(&host_port);
753 } else {
754 continue;
756 }
757
758 ipt_forward_rule.protocol(&expose_port.family);
760 ipt_dnat_rule.protocol(&expose_port.family);
761 ipt6_input_rule.protocol(&expose_port.family);
762
763 ipt_forward_rule.jump("ACCEPT");
764 ipt6_input_rule.jump("ACCEPT");
765
766 debug!(ctx.logger, "Build rule to verify contents";
769 o!("args" => format!("{:?}", ipt_forward_rule)));
770 ipt_forward_rule.build()?;
771 debug!(ctx.logger, "Build rule to verify contents";
772 o!("args" => format!("{:?}", ipt_dnat_rule)));
773 ipt_dnat_rule.build()?;
774
775 if let Some(ref external_network_interface) = self.external_network_interface {
776 trace!(ctx.logger, "Rule has specific external network interface";
777 o!("external_network_interface" => external_network_interface));
778
779 ipt_forward_rule.in_interface(external_network_interface);
780 ipt_dnat_rule.in_interface(external_network_interface);
781 } else if let Some(ref primary_external_network_interface) =
782 ctx.primary_external_network_interface
783 {
784 trace!(ctx.logger, "Rule uses primary external network interface";
785 o!("external_network_interface" => primary_external_network_interface));
786
787 ipt_forward_rule.in_interface(primary_external_network_interface);
788 ipt_dnat_rule.in_interface(primary_external_network_interface);
789 ipt6_input_rule.in_interface(primary_external_network_interface);
790 } else {
791 continue;
793 }
794
795 if let Some(source_cidrs) = &self.source_cidr_v4 {
798 debug!(ctx.logger, "Generate extended FORWARD rules, source CIDRs were specified";
799 o!("args" => format!("{:?}", ipt_dnat_rule),
800 "source_cidrs" => source_cidrs.join(", ")));
801 for additional_forward_rule in source_cidrs
802 .iter()
803 .map(|source_cidr| {
804 let mut forward_rule = ipt_forward_rule.clone();
805 forward_rule.source(source_cidr);
806 forward_rule
807 })
808 .map(|forward_rule| forward_rule.build())
809 .collect::<Result<Vec<_>>>()?
810 {
811 debug!(ctx.logger, "Add FORWARD rule";
812 o!("part" => "wider_world_to_container",
813 "rule" => &additional_forward_rule.rule));
814 rules.push(append_built_rule(
815 IptablesRuleDiscriminants::V4,
816 &additional_forward_rule,
817 ));
818 }
819 for additional_dnat_rule in source_cidrs
820 .iter()
821 .map(|source_cidr| {
822 let mut dnat_rule = ipt_dnat_rule.clone();
823 dnat_rule.source(source_cidr);
824 dnat_rule
825 })
826 .map(|dnat_rule| dnat_rule.build())
827 .collect::<Result<Vec<_>>>()?
828 {
829 debug!(ctx.logger, "Add DNAT rule";
830 o!("part" => "wider_world_to_container",
831 "rule" => &additional_dnat_rule.rule));
832 rules.push(append_built_rule(
833 IptablesRuleDiscriminants::V4,
834 &additional_dnat_rule,
835 ));
836 }
837 } else {
838 let forward_rule = ipt_forward_rule.build()?;
839 debug!(ctx.logger, "Add forward rule";
840 o!("part" => "wider_world_to_container",
841 "rule" => &forward_rule.rule));
842 let dnat_rule = ipt_dnat_rule.build()?;
843 debug!(ctx.logger, "Add DNAT rule";
844 o!("part" => "wider_world_to_container",
845 "rule" => &dnat_rule.rule));
846 rules.push(append_built_rule(
848 IptablesRuleDiscriminants::V4,
849 &forward_rule,
850 ));
851 rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &dnat_rule));
852 }
853 if self.expose_via_ipv6 {
854 if let Some(source_cidrs) = &self.source_cidr_v6 {
855 for additional_input_rule in source_cidrs
856 .iter()
857 .map(|source_cidr| {
858 let mut input_rule = ipt6_input_rule.clone();
859 input_rule.source(source_cidr);
860 input_rule
861 })
862 .map(|input_rule| input_rule.build())
863 .collect::<Result<Vec<_>>>()?
864 {
865 debug!(ctx.logger, "Add IPv6 INPUT rule";
866 o!("part" => "wider_world_to_container",
867 "rule" => &additional_input_rule.rule));
868 rules.push(append_built_rule(
869 IptablesRuleDiscriminants::V6,
870 &additional_input_rule,
871 ));
872 }
873 } else {
874 let input_rule = ipt6_input_rule.build()?;
875 debug!(ctx.logger, "Add IPv6 INPUT rule";
876 o!("part" => "wider_world_to_container",
877 "rule" => &input_rule.rule));
878 rules.push(append_built_rule(
879 IptablesRuleDiscriminants::V6,
880 &input_rule,
881 ));
882 }
883 }
884 }
885
886 Ok(Some(rules))
887 }
888}
889
890impl Process<Iptables> for ContainerDNAT {
891 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
892 debug!(ctx.logger, "Starting sub-processing";
893 o!("part" => "container_dnat"));
894 if self.rules.is_some() {
895 debug!(ctx.logger, "Process rules";
896 o!("part" => "container_dnat"));
897 self.rules.process(ctx)
898 } else {
899 trace!(ctx.logger, "No rules";
900 o!("part" => "container_dnat"));
901 Ok(None)
902 }
903 }
904}
905
906impl Process<Iptables> for ContainerDNATRule {
907 fn process(&self, ctx: &ProcessContext<Iptables>) -> Result<Option<Vec<IptablesRule>>> {
908 trace!(ctx.logger, "Process rule";
909 o!("part" => "container_dnat_rule",
910 "rule" => format!("{:?}", self)));
911 let Some(endpoints) = container_dnat_endpoints(self, ctx)? else {
912 return Ok(None);
913 };
914
915 let mut rules = Vec::new();
916 for port in &self.expose_port {
917 let destination_port = port.container_port.unwrap_or(port.host_port);
918 for bridge in &endpoints.source_bridges {
919 let mut rule = Rule::new("nat", DFW_PREROUTING_CHAIN);
920 rule.in_interface(bridge)
921 .protocol(&port.family)
922 .destination_port(&port.host_port.to_string())
923 .filter("-m addrtype --dst-type LOCAL")
924 .jump(&format!(
925 "DNAT --to-destination {}:{}",
926 endpoints.destination_ip, destination_port
927 ));
928 if let Some(source_ip) = endpoints.source_ip {
929 rule.source(&source_ip.to_string());
930 }
931 rules.push(append_built_rule(
932 IptablesRuleDiscriminants::V4,
933 &rule.build()?,
934 ));
935 }
936 }
937
938 Ok(Some(rules))
939 }
940}
941
942fn set_policy(
943 rule_discriminant: IptablesRuleDiscriminants,
944 table: &str,
945 chain: &str,
946 policy: &str,
947) -> IptablesRule {
948 IptablesRule::from_discriminant(
949 rule_discriminant,
950 PolicyOrRule::Policy {
951 table: table.to_owned(),
952 chain: chain.to_owned(),
953 policy: policy.to_owned(),
954 },
955 )
956}
957
958fn new_chain(
959 rule_discriminant: IptablesRuleDiscriminants,
960 table: &str,
961 chain: &str,
962) -> IptablesRule {
963 set_policy(rule_discriminant, table, chain, "-")
964}
965
966fn flush_chain(
967 rule_discriminant: IptablesRuleDiscriminants,
968 table: &str,
969 chain: &str,
970) -> IptablesRule {
971 IptablesRule::from_discriminant(
972 rule_discriminant,
973 PolicyOrRule::Rule {
974 table: table.to_owned(),
975 chain: chain.to_owned(),
976 value: format!("-F {}", chain),
977 },
978 )
979}
980
981fn add_rule(rule_discriminant: IptablesRuleDiscriminants, table: &str, rule: &str) -> IptablesRule {
982 IptablesRule::from_discriminant(
983 rule_discriminant,
984 PolicyOrRule::Rule {
985 table: table.to_owned(),
986 chain: "".to_owned(),
987 value: rule.to_owned(),
988 },
989 )
990}
991
992fn append_rule(
993 rule_discriminant: IptablesRuleDiscriminants,
994 table: &str,
995 chain: &str,
996 rule: &str,
997) -> IptablesRule {
998 IptablesRule::from_discriminant(
999 rule_discriminant,
1000 PolicyOrRule::Rule {
1001 table: table.to_owned(),
1002 chain: chain.to_owned(),
1003 value: format!("-A {} {}", chain, rule),
1004 },
1005 )
1006}
1007
1008fn append_built_rule(
1009 rule_discriminant: IptablesRuleDiscriminants,
1010 rule: &BuiltRule,
1011) -> IptablesRule {
1012 append_rule(rule_discriminant, &rule.table, &rule.chain, &rule.rule)
1013}
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::*;
1018 use bollard::{models::Network, Docker, API_DEFAULT_VERSION};
1019 use slog::{Discard, Logger};
1020
1021 #[test]
1022 fn container_forwarding_default_requires_two_docker_bridges() {
1023 let docker =
1024 Docker::connect_with_http("http://127.0.0.1:2375", 1, API_DEFAULT_VERSION).unwrap();
1025 let config: DFW<Iptables> =
1026 toml::from_str("[container_to_container]\ndefault_policy = \"drop\"").unwrap();
1027 let bridge = |name: &str| Network {
1028 driver: Some("bridge".into()),
1029 options: Some([("com.docker.network.bridge.name".into(), name.into())].into()),
1030 ..Default::default()
1031 };
1032 let ctx = ProcessContext {
1033 docker: &docker,
1034 dfw: &config,
1035 container_map: Default::default(),
1036 network_map: [
1037 ("bridge".into(), bridge("docker0")),
1038 ("test".into(), bridge("custom0")),
1039 (
1040 "host".into(),
1041 Network {
1042 driver: Some("host".into()),
1043 ..Default::default()
1044 },
1045 ),
1046 ]
1047 .into(),
1048 external_network_interfaces: None,
1049 primary_external_network_interface: None,
1050 logger: Logger::root(Discard, o!()),
1051 dry_run: true,
1052 };
1053
1054 let rules = Process::<Iptables>::process(&config.container_to_container, &ctx)
1055 .unwrap()
1056 .unwrap();
1057 assert_eq!(
1058 Iptables::get_rules(rules, IptablesRuleDiscriminants::V4),
1059 [
1060 "*filter",
1061 ":DFWRS_CTC_DEFAULT - [0:0]",
1062 "-F DFWRS_CTC_DEFAULT",
1063 "-A DFWRS_CTC_DEFAULT -o custom0 -j DROP",
1064 "-A DFWRS_CTC_DEFAULT -o docker0 -j DROP",
1065 "-A DFWRS_FORWARD -i custom0 -j DFWRS_CTC_DEFAULT",
1066 "-A DFWRS_FORWARD -i docker0 -j DFWRS_CTC_DEFAULT",
1067 "COMMIT",
1068 ]
1069 );
1070 }
1071}