Skip to main content

dfw/iptables/
process.rs

1// Copyright Pit Kleyersburg <pitkley@googlemail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified or distributed
8// except according to those terms.
9
10use 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        // Enforce policy for default Docker-bridge (usually docker0) to access host-resources
202        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                // Set policy for input-chain
210                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                // Set policy for forward-chain, divided by the external network interfaces.
222                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        // Configure postrouting
236        if let Some(ref external_network_interfaces) = self.external_network_interfaces {
237            for external_network_interface in external_network_interfaces {
238                // Configure postrouting
239                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        // The shared FORWARD chain also carries host ingress and Internet egress.
286        // Apply this fallback only when both interfaces are Docker bridges.
287        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        // Set jump
420        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        // Enforce default policy for container-to-wider-world communication.
445        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        // Try to build the rule without the out_interface defined to see if all of the other
533        // mandatory fields has been populated.
534        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        // Default policy
576        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        // Try to build the rule without the out_interface defined to see if all of the other
652        // mandatory fields has been populated.
653        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                // Network for container has to exist
755                continue;
756            }
757
758            // Set correct protocol
759            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            // Try to build the rule without the out_interface defined to see if any of the
767            // other mandatory fields has been populated.
768            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                // The DNAT rule requires the external interface
792                continue;
793            }
794
795            // If source CIDRs have been specified, create the FORWARD-rules as required to
796            // restrict the traffic as intended.
797            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                // Apply the rule
847                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}