dfw/
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
10//! This module holds the types related to configuration processing and rule creation.
11
12use crate::{errors::*, types::*, util::FutureExt, FirewallBackend};
13use bollard::{
14    container::ListContainersOptions,
15    models::{ContainerSummary, Network, NetworkContainer},
16    Docker,
17};
18use failure::{bail, format_err};
19use maplit::hashmap;
20use slog::{debug, o, trace, Logger};
21use std::collections::HashMap as Map;
22
23/// This trait allows a type to define its own processing rules. It is expected to return a list
24/// of rules that can be applied with nft.
25///
26/// # Example
27///
28/// ```
29/// # use dfw::FirewallBackend;
30/// # use dfw::process::{Process, ProcessContext};
31/// # use dfw::types::DFW;
32/// # use failure::Error;
33/// struct MyBackend;
34/// impl FirewallBackend for MyBackend {
35///     type Rule = String;
36/// #    type Defaults = ();
37///
38///     fn apply(rules: Vec<String>, ctx: &ProcessContext<Self>) -> Result<(), Error> {
39///         // Write code to apply the processed rules.
40/// #        unimplemented!()
41///     }
42/// }
43/// # impl Process<MyBackend> for DFW<MyBackend> {
44/// #     fn process(&self, ctx: &ProcessContext<MyBackend>) -> Result<Option<Vec<String>>, Error> {
45/// #         unimplemented!()
46/// #     }
47/// # }
48/// struct MyType {
49///     rules: Vec<String>,
50/// }
51///
52/// impl Process<MyBackend> for MyType {
53///     fn process(&self, ctx: &ProcessContext<MyBackend>) -> Result<Option<Vec<String>>, Error> {
54///         let mut rules = Vec::new();
55///         for rule in &self.rules {
56///             rules.push(format!("add rule {}", rule));
57///         }
58///         Ok(Some(rules))
59///     }
60/// }
61/// ```
62pub trait Process<B: FirewallBackend>
63where
64    DFW<B>: Process<B>,
65{
66    /// Process the current type within the given [`ProcessContext`], returning zero or more rules
67    /// to apply with nft.
68    ///
69    /// [`ProcessContext`]: struct.ProcessContext.html
70    fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>>;
71}
72
73impl<B, T> Process<B> for Option<T>
74where
75    B: FirewallBackend,
76    DFW<B>: Process<B>,
77    T: Process<B>,
78{
79    fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>> {
80        match self {
81            Some(t) => t.process(ctx),
82            None => Ok(None),
83        }
84    }
85}
86
87impl<B, T> Process<B> for Vec<T>
88where
89    B: FirewallBackend,
90    DFW<B>: Process<B>,
91    T: Process<B>,
92{
93    fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>> {
94        let mut rules = Vec::new();
95        for rule in self {
96            if let Some(mut sub_rules) = rule.process(ctx)? {
97                rules.append(&mut sub_rules);
98            }
99        }
100
101        Ok(Some(rules))
102    }
103}
104
105/// Enclosing struct to manage rule processing.
106pub struct ProcessContext<'a, B>
107where
108    B: FirewallBackend,
109    DFW<B>: Process<B>,
110{
111    pub(crate) docker: &'a Docker,
112    pub(crate) dfw: &'a DFW<B>,
113    pub(crate) container_map: Map<String, ContainerSummary>,
114    pub(crate) network_map: Map<String, Network>,
115    pub(crate) external_network_interfaces: Option<Vec<String>>,
116    pub(crate) primary_external_network_interface: Option<String>,
117    pub(crate) logger: Logger,
118    pub(crate) dry_run: bool,
119}
120
121impl<'a, B> ProcessContext<'a, B>
122where
123    B: FirewallBackend,
124    DFW<B>: Process<B>,
125{
126    /// Create a new instance of `ProcessDFW` for rule processing.
127    pub fn new(
128        docker: &'a Docker,
129        dfw: &'a DFW<B>,
130        processing_options: &'a ProcessingOptions,
131        logger: &'a Logger,
132        dry_run: bool,
133    ) -> Result<ProcessContext<'a, B>> {
134        let logger = logger.new(o!());
135
136        let list_containers_options = match processing_options.container_filter {
137            ContainerFilter::All => None,
138            ContainerFilter::Running => Some(ListContainersOptions {
139                filters: hashmap! { "status" => vec!["running"]},
140                ..Default::default()
141            }),
142        };
143        let containers = docker.list_containers(list_containers_options).sync()?;
144        debug!(logger, "Got list of containers";
145               o!("containers" => format!("{:#?}", containers)));
146
147        let container_map = get_container_map(&containers);
148        trace!(logger, "Got map of containers";
149               o!("container_map" => format!("{:#?}", container_map)));
150
151        let networks = docker.list_networks::<String>(None).sync()?;
152        debug!(logger, "Got list of networks";
153               o!("networks" => format!("{:#?}", networks)));
154
155        let network_map =
156            get_network_map(&networks).ok_or_else(|| format_err!("no networks found"))?;
157        trace!(logger, "Got map of networks";
158               o!("container_map" => format!("{:#?}", container_map)));
159
160        let external_network_interfaces = dfw
161            .global_defaults
162            .external_network_interfaces
163            .as_ref()
164            .cloned();
165        let primary_external_network_interface = external_network_interfaces
166            .as_ref()
167            .and_then(|v| v.first())
168            .map(|s| s.to_owned());
169
170        Ok(ProcessContext {
171            docker,
172            dfw,
173            container_map,
174            network_map,
175            external_network_interfaces,
176            primary_external_network_interface,
177            logger,
178            dry_run,
179        })
180    }
181
182    /// Start the processing using the configuration given at creation.
183    pub fn process(&mut self) -> Result<()> {
184        let rules = Process::<B>::process(self.dfw, self)?;
185        if let Some(rules) = rules {
186            B::apply(rules, self)?;
187        }
188
189        Ok(())
190    }
191}
192
193/// Option to filter the containers to be processed
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum ContainerFilter {
196    /// Process all containers, i.e. don't filter.
197    All,
198    /// Only process running containers.
199    Running,
200}
201
202/// Options to configure the processing procedure.
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct ProcessingOptions {
205    /// Option to filter the containers to be processed, see
206    /// [`ContainerFilter`](enum.ContainerFilter.html).
207    pub container_filter: ContainerFilter,
208}
209
210impl Default for ProcessingOptions {
211    fn default() -> Self {
212        ProcessingOptions {
213            container_filter: ContainerFilter::All,
214        }
215    }
216}
217
218pub(crate) fn get_bridge_name(network_id: &str) -> Result<String> {
219    if network_id.len() < 12 {
220        bail!("network has to be longer than 12 characters");
221    }
222    Ok(format!("br-{}", &network_id[..12]))
223}
224
225pub(crate) fn get_network_for_container(
226    docker: &Docker,
227    container_map: &Map<String, ContainerSummary>,
228    container_name: &str,
229    network_id: &str,
230) -> Result<Option<NetworkContainer>> {
231    if let Some(container) = container_map.get(container_name) {
232        Ok(docker
233            .inspect_network::<String>(network_id, None)
234            .sync()?
235            .containers
236            .and_then(|containers| {
237                container
238                    .id
239                    .as_ref()
240                    .and_then(|container_id| containers.get(container_id).cloned())
241            }))
242    } else {
243        Ok(None)
244    }
245}
246
247pub(crate) fn get_container_map(containers: &[ContainerSummary]) -> Map<String, ContainerSummary> {
248    let mut container_map: Map<String, ContainerSummary> = Map::new();
249    for container in containers {
250        if let Some(names) = &container.names {
251            for name in names {
252                container_map.insert(
253                    name.clone().trim_start_matches('/').to_owned(),
254                    container.clone(),
255                );
256            }
257        }
258    }
259
260    container_map
261}
262
263pub(crate) fn get_network_map(networks: &[Network]) -> Option<Map<String, Network>> {
264    let mut network_map: Map<String, Network> = Map::new();
265    for network in networks {
266        if let Some(name) = &network.name {
267            network_map.insert(name.clone(), network.clone());
268        }
269    }
270
271    if network_map.is_empty() {
272        None
273    } else {
274        Some(network_map)
275    }
276}
277
278pub(crate) fn generate_marker(components: &[&str]) -> String {
279    format!("DFW-MARKER:{}", components.join(";"))
280}