Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support default network verdict within the same network #697

Merged
merged 3 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .github/workflows/it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,6 @@ jobs:
runs-on: ubuntu-20.04
- dind-image: docker:18.06-dind
runs-on: ubuntu-20.04
- dind-image: docker:18.03-dind
runs-on: ubuntu-20.04
- dind-image: docker:17.12-dind
runs-on: ubuntu-20.04
- dind-image: docker:17.09-dind
runs-on: ubuntu-20.04
- dind-image: docker:17.07-dind
runs-on: ubuntu-20.04
- dind-image: docker:17.06-dind
runs-on: ubuntu-20.04
- dind-image: docker:1.13-dind
runs-on: ubuntu-20.04

services:
dind:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Unreleased

* Add [`same_network_verdict` option](https://dfw.rs/latest/dfw/types/struct.ContainerToContainer.html#structfield.same_network_verdict) to container-to-container configuration, enabling users to specify whether traffic between containers within the same network should be allowed or not.
* Replace library used to communicate with Docker (which also fixes [#411]).

This release replaces the previously used library [shiplift] by [bollard].
Expand Down
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,8 @@ DFW is continuously and automatically tested with the following stable Docker ve
* `19.03`
* `18.09`
* `18.06`
* `18.03`
* `17.12`
* `17.09`
* `17.07`
* `17.06`
* `1.13`

Docker version 20.10 is also officially supported by DFW, although it is not yet automatically tested.

Docker versions between 18.03 and 1.13 should also work, but are not automatically tested anymore due to lacking Docker BuildKit support.

## <a name="versionbumppolicy"></a> Version bump policy

Expand Down
7 changes: 7 additions & 0 deletions resources/test/docker/ctc-network-policies/conf.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[container_to_container]
default_policy = "drop"
same_network_verdict = "accept"

[[container_to_container.rules]]
network = "PROJECT_default"
verdict = "reject"
12 changes: 12 additions & 0 deletions resources/test/docker/ctc-network-policies/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: '2'

services:
a:
image: nginx:alpine
networks:
- default
- other

networks:
default:
other:
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
*filter
:DFWRS_FORWARD - [0:0]
:DFWRS_INPUT - [0:0]
:FORWARD - [0:0]
:INPUT - [0:0]
-F DFWRS_FORWARD
-A DFWRS_FORWARD -m state --state INVALID -j DROP
-A DFWRS_FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
-A DFWRS_FORWARD -i $input=bridge -o $output=bridge -j REJECT "$input" == "$output"
-A DFWRS_FORWARD -i $input=bridge -o $output=bridge -j ACCEPT "$input" == "$output"
-A DFWRS_FORWARD -i $input=bridge -o $output=bridge -j ACCEPT "$input" == "$output"
-A DFWRS_FORWARD -i $input=bridge -o $output=bridge -j ACCEPT "$input" == "$output"
-A DFWRS_FORWARD -i $input=bridge -o $output=bridge -j ACCEPT "$input" == "$output"
-A DFWRS_FORWARD -i $input=bridge -o $output=bridge -j ACCEPT "$input" == "$output"
-A DFWRS_FORWARD -j DROP
-F DFWRS_INPUT
-A DFWRS_INPUT -m state --state INVALID -j DROP
-A DFWRS_INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A DFWRS_INPUT -i docker0 -j ACCEPT
-A FORWARD -j DFWRS_FORWARD
-A INPUT -j DFWRS_INPUT
COMMIT
*nat
:DFWRS_POSTROUTING - [0:0]
:DFWRS_PREROUTING - [0:0]
:POSTROUTING - [0:0]
:PREROUTING - [0:0]
-F DFWRS_POSTROUTING
-F DFWRS_PREROUTING
-A POSTROUTING -j DFWRS_POSTROUTING
-A PREROUTING -j DFWRS_PREROUTING
COMMIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*filter
:DFWRS_FORWARD - [0:0]
:DFWRS_INPUT - [0:0]
-F DFWRS_FORWARD
-A DFWRS_FORWARD -m state --state INVALID -j DROP
-A DFWRS_FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
-F DFWRS_INPUT
-A DFWRS_INPUT -m state --state INVALID -j DROP
-A DFWRS_INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
COMMIT
*nat
:DFWRS_POSTROUTING - [0:0]
:DFWRS_PREROUTING - [0:0]
-F DFWRS_POSTROUTING
-F DFWRS_PREROUTING
COMMIT
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
add table inet dfw
flush table inet dfw
add chain inet dfw input { type filter hook input priority -5 ; }
add rule inet dfw input ct state invalid drop
add rule inet dfw input ct state { related, established } accept
add chain inet dfw forward { type filter hook forward priority -5 ; }
add rule inet dfw forward ct state invalid drop
add rule inet dfw forward ct state { related, established } accept
add table ip dfw
flush table ip dfw
add chain ip dfw prerouting { type nat hook prerouting priority -105 ; }
add chain ip dfw postrouting { type nat hook postrouting priority 95 ; }
add table ip6 dfw
flush table ip6 dfw
add chain ip6 dfw prerouting { type nat hook prerouting priority -105 ; }
add chain ip6 dfw postrouting { type nat hook postrouting priority 95 ; }
add rule inet dfw input meta iifname docker0 meta mark set 0xdf accept
add chain inet dfw forward { policy drop ; }
add rule inet dfw forward meta iifname $input=bridge oifname $output=bridge meta mark set 0xdf reject "$input" == "$output"
add rule inet dfw forward meta iifname $input=bridge oifname $output=bridge meta mark set 0xdf accept "$input" == "$output"
add rule inet dfw forward meta iifname $input=bridge oifname $output=bridge meta mark set 0xdf accept "$input" == "$output"
add rule inet dfw forward meta iifname $input=bridge oifname $output=bridge meta mark set 0xdf accept "$input" == "$output"
add rule inet dfw forward meta iifname $input=bridge oifname $output=bridge meta mark set 0xdf accept "$input" == "$output"
add rule inet dfw forward meta iifname $input=bridge oifname $output=bridge meta mark set 0xdf accept "$input" == "$output"
24 changes: 24 additions & 0 deletions src/iptables/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,30 @@ impl Process<Iptables> for ContainerToContainer {
rules.append(&mut ctc_rules);
}

if let Some(same_network_verdict) = self.same_network_verdict {
for network in ctx.network_map.values() {
let network_id = network.id.as_ref().expect("Docker network ID missing");
let bridge_name = get_bridge_name(network_id)?;
trace!(ctx.logger, "Got bridge name";
o!("network_name" => &network.name,
"bridge_name" => &bridge_name));

let ipt_rule = Rule::new("filter", DFW_FORWARD_CHAIN)
.in_interface(&bridge_name)
.out_interface(&bridge_name)
.jump(&same_network_verdict.to_string().to_uppercase())
.build()?;

debug!(ctx.logger, "Add forward rule for same network verdict for bridge";
o!("part" => "container_to_container",
"bridge_name" => bridge_name,
"same_network_verdict" => same_network_verdict,
"rule" => &ipt_rule));

rules.push(append_built_rule(IptablesRuleDiscriminants::V4, &ipt_rule));
}
}

// Enforce default policy for container-to-container communication.
rules.push(append_rule(
IptablesRuleDiscriminants::V4,
Expand Down
24 changes: 24 additions & 0 deletions src/nftables/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,30 @@ impl Process<Nftables> for ContainerToContainer {
rules.append(&mut ctc_rules);
}

if let Some(same_network_verdict) = self.same_network_verdict {
for network in ctx.network_map.values() {
let network_id = network.id.as_ref().expect("Docker network ID missing");
let bridge_name = get_bridge_name(network_id)?;
trace!(ctx.logger, "Got bridge name";
o!("network_name" => &network.name,
"bridge_name" => &bridge_name));

let rule = RuleBuilder::default()
.in_interface(&bridge_name)
.out_interface(&bridge_name)
.verdict(same_network_verdict)
.build()?;

debug!(ctx.logger, "Add forward rule for same network verdict for bridge";
o!("part" => "container_to_container",
"bridge_name" => bridge_name,
"same_network_verdict" => same_network_verdict,
"rule" => &rule));

rules.push(add_rule(Family::Inet, "dfw", "forward", &rule));
}
}

Ok(Some(rules))
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ where
.cloned();
let primary_external_network_interface = external_network_interfaces
.as_ref()
.and_then(|v| v.get(0))
.and_then(|v| v.first())
.map(|s| s.to_owned());

Ok(ProcessContext {
Expand Down
63 changes: 63 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,69 @@ pub struct ContainerToContainer {
///
/// To permanently set this configuration, take a look at `man sysctl.d` and `man sysctl.conf`.
pub default_policy: ChainPolicy,
/// Configure whether traffic between containers within the same network should be allowed or
/// not.
///
/// This option is more specific than [`default_policy`], applying to traffic between containers
/// on the same network, rather than also applying across networks. This option has precedence
/// over the [`default_policy`] for traffic on the same network.
///
/// ## Example
///
/// Setting the `same_network_verdict` to `accept` will allow all traffic between containers on
/// the same network to pass, regardless of the [`default_policy`] configured:
///
/// ```
/// # use dfw::nftables::Nftables;
/// # use dfw::types::*;
/// # use toml;
/// # toml::from_str::<DFW<Nftables>>(r#"
/// [container_to_container]
/// default_policy = "drop"
/// same_network_verdict = "accept"
/// # "#).unwrap();
/// ```
///
/// If you want to allow traffic between containers on the same networks in general, but want to
/// restrict traffic on some networks, you can additionally add [container-to-container rules]
/// to disallow traffic between containers for the desired networks:
///
/// ```
/// # use dfw::nftables::Nftables;
/// # use dfw::types::*;
/// # use toml;
/// # toml::from_str::<DFW<Nftables>>(r#"
/// [container_to_container]
/// default_policy = "drop"
/// same_network_verdict = "accept"
///
/// [[container_to_container.rules]]
/// network = "restricted_network"
/// verdict = "reject"
/// # "#).unwrap();
/// ```
///
/// [container-to-container rules]: struct.ContainerToContainerRule.html
///
/// ## Host configuration
///
/// Depending on how your host is configured, traffic whose origin and destination interface are
/// the same bridge (network) is _not_ filtered by the kernel netfilter module.
///
/// This means that this verdict is only honored if your kernel has the `br_netfilter`
/// kernel-module available and the sysctl `net.bridge.bridge-nf-call-iptables` is set to `1`.
/// Otherwise traffic between containers on the same network will always be allowed.
///
/// You can set the sysctl-value temporarily like this:
///
/// ```text
/// sysctl net.bridge.bridge-nf-call-iptables=1
/// ```
///
/// To permanently set this configuration, take a look at `man sysctl.d` and `man sysctl.conf`.
///
/// [`default_policy`]: #structfield.default_policy
pub same_network_verdict: Option<RuleVerdict>,
/// An optional list of rules, see
/// [`ContainerToContainerRule`](struct.ContainerToContainerRule.html).
///
Expand Down
1 change: 1 addition & 0 deletions tests/dfw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ dfw_tests!(
"05";
"06";
"07";
"ctc-network-policies";

R F "001_gh_166_01" "001-gh-166/01";
R F "001_gh_166_02" "001-gh-166/02";
Expand Down
4 changes: 3 additions & 1 deletion tests/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fn parse_conf_file() {
};
let container_to_container = ContainerToContainer {
default_policy: ChainPolicy::Drop,
same_network_verdict: None,
rules: Some(vec![ContainerToContainerRule {
network: "network".to_owned(),
src_container: Some("src_container".to_owned()),
Expand Down Expand Up @@ -161,6 +162,7 @@ fn parse_conf_path() {
};
let container_to_container = ContainerToContainer {
default_policy: ChainPolicy::Drop,
same_network_verdict: None,
rules: Some(vec![ContainerToContainerRule {
network: "network".to_owned(),
src_container: Some("src_container".to_owned()),
Expand Down Expand Up @@ -594,7 +596,7 @@ fn ensure_backwards_compatibility_v1() {
source_cidr_v4,
source_cidr_v6,
..
} = rules.get(0).unwrap();
} = rules.first().unwrap();
assert!(source_cidr_v4.is_some());
assert!(source_cidr_v6.is_none());
}
Expand Down
Loading