mod common;
use common::resource;
use dfw::{
process::{Process, ProcessContext},
types::*,
util::*,
FirewallBackend,
};
use serde::Deserialize;
#[derive(Debug, Eq, PartialEq)]
struct TestBackend;
impl FirewallBackend for TestBackend
where
DFW<Self>: Process<Self>,
{
type Rule = String;
type Defaults = TestBackendDefaults;
fn apply(_rules: Vec<Self::Rule>, _ctx: &ProcessContext<Self>) -> dfw::errors::Result<()> {
unimplemented!()
}
}
impl Process<TestBackend> for DFW<TestBackend> {
fn process(
&self,
_ctx: &ProcessContext<TestBackend>,
) -> Result<Option<Vec<String>>, failure::Error> {
unimplemented!()
}
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
struct TestBackendDefaults {
test: String,
}
#[test]
fn parse_conf_file() {
let global_defaults = GlobalDefaults {
external_network_interfaces: Some(vec!["eni".to_owned()]),
default_docker_bridge_to_host_policy: ChainPolicy::Accept,
..Default::default()
};
let backend_defaults = TestBackendDefaults {
test: "custom backend defaults".to_owned(),
};
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()),
dst_container: Some("dst_container".to_owned()),
matches: Some("FILTER".to_owned()),
verdict: RuleVerdict::Accept,
}]),
};
let container_to_wider_world = ContainerToWiderWorld {
default_policy: RuleVerdict::Accept,
rules: Some(vec![ContainerToWiderWorldRule {
network: Some("network".to_owned()),
src_container: Some("src_container".to_owned()),
matches: Some("FILTER".to_owned()),
verdict: RuleVerdict::Accept,
external_network_interface: Some("eni".to_owned()),
}]),
};
let container_to_host = ContainerToHost {
default_policy: RuleVerdict::Accept,
rules: Some(vec![ContainerToHostRule {
network: "network".to_owned(),
src_container: Some("src_container".to_owned()),
matches: Some("FILTER".to_owned()),
verdict: RuleVerdict::Accept,
}]),
};
let wider_world_to_container = WiderWorldToContainer {
rules: Some(vec![
WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
}],
external_network_interface: Some("eni".to_owned()),
expose_via_ipv6: false,
source_cidr_v4: None,
source_cidr_v6: None,
},
WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 22,
container_port: None,
family: "tcp".to_owned(),
}],
external_network_interface: Some("eni".to_owned()),
expose_via_ipv6: true,
source_cidr_v4: Some(vec!["192.0.2.1/32".to_owned(), "192.0.2.2/32".to_owned()]),
source_cidr_v6: Some(vec![
"2001:db8::1/128".to_owned(),
"2001:db8::2/128".to_owned(),
]),
},
]),
};
let container_dnat = ContainerDNAT {
rules: Some(vec![ContainerDNATRule {
src_network: Some("src_network".to_owned()),
src_container: Some("src_container".to_owned()),
dst_network: "dst_network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
}],
}]),
};
#[cfg_attr(crate_major_version = "1", allow(deprecated))]
let expected: DFW<TestBackend> = DFW {
global_defaults,
backend_defaults: Some(backend_defaults),
initialization: None,
container_to_container: Some(container_to_container),
container_to_wider_world: Some(container_to_wider_world),
container_to_host: Some(container_to_host),
wider_world_to_container: Some(wider_world_to_container),
container_dnat: Some(container_dnat),
};
let actual = load_file(&resource("conf-file.toml").unwrap()).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn parse_conf_path() {
let global_defaults = GlobalDefaults {
external_network_interfaces: Some(vec!["eni".to_owned()]),
default_docker_bridge_to_host_policy: ChainPolicy::Accept,
..Default::default()
};
let backend_defaults = TestBackendDefaults {
test: "custom backend defaults".to_owned(),
};
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()),
dst_container: Some("dst_container".to_owned()),
matches: Some("FILTER".to_owned()),
verdict: RuleVerdict::Accept,
}]),
};
let container_to_wider_world = ContainerToWiderWorld {
default_policy: RuleVerdict::Accept,
rules: Some(vec![ContainerToWiderWorldRule {
network: Some("network".to_owned()),
src_container: Some("src_container".to_owned()),
matches: Some("FILTER".to_owned()),
verdict: RuleVerdict::Accept,
external_network_interface: Some("eni".to_owned()),
}]),
};
let container_to_host = ContainerToHost {
default_policy: RuleVerdict::Accept,
rules: Some(vec![ContainerToHostRule {
network: "network".to_owned(),
src_container: Some("src_container".to_owned()),
matches: Some("FILTER".to_owned()),
verdict: RuleVerdict::Accept,
}]),
};
let wider_world_to_container = WiderWorldToContainer {
rules: Some(vec![
WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
}],
external_network_interface: Some("eni".to_owned()),
expose_via_ipv6: false,
source_cidr_v4: None,
source_cidr_v6: None,
},
WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 22,
container_port: None,
family: "tcp".to_owned(),
}],
external_network_interface: Some("eni".to_owned()),
expose_via_ipv6: true,
source_cidr_v4: Some(vec!["192.0.2.1/32".to_owned(), "192.0.2.2/32".to_owned()]),
source_cidr_v6: Some(vec![
"2001:db8::1/128".to_owned(),
"2001:db8::2/128".to_owned(),
]),
},
]),
};
let container_dnat = ContainerDNAT {
rules: Some(vec![ContainerDNATRule {
src_network: Some("src_network".to_owned()),
src_container: Some("src_container".to_owned()),
dst_network: "dst_network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
}],
}]),
};
#[cfg_attr(crate_major_version = "1", allow(deprecated))]
let expected: DFW<TestBackend> = DFW {
global_defaults,
backend_defaults: Some(backend_defaults),
initialization: None,
container_to_container: Some(container_to_container),
container_to_wider_world: Some(container_to_wider_world),
container_to_host: Some(container_to_host),
wider_world_to_container: Some(wider_world_to_container),
container_dnat: Some(container_dnat),
};
let actual = load_path(&resource("conf_path").unwrap()).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn parse_expose_port_single_int() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = 80
"#;
let expected = WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
}],
external_network_interface: None,
expose_via_ipv6: true,
source_cidr_v4: None,
source_cidr_v6: None,
};
let actual: WiderWorldToContainerRule = toml::from_str(fragment).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn parse_expose_port_seq_int() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = [80, 81]
"#;
let expected = WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![
ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
},
ExposePort {
host_port: 81,
container_port: None,
family: "tcp".to_owned(),
},
],
external_network_interface: None,
expose_via_ipv6: true,
source_cidr_v4: None,
source_cidr_v6: None,
};
let actual: WiderWorldToContainerRule = toml::from_str(fragment).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn parse_expose_port_single_string() {
for &(port, family) in &[(80, "tcp"), (53, "udp"), (1234, "other")] {
let fragment = format!(
r#"
network = "network"
dst_container = "dst_container"
expose_port = "{}/{}"
"#,
port, family
);
let expected = WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: port.to_owned(),
container_port: None,
family: family.to_owned(),
}],
external_network_interface: None,
expose_via_ipv6: true,
source_cidr_v4: None,
source_cidr_v6: None,
};
let actual: WiderWorldToContainerRule = toml::from_str(&fragment).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn parse_expose_port_seq_string() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = ["80/tcp", "53/udp", "1234/other"]
"#;
let expected = WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![
ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
},
ExposePort {
host_port: 53,
container_port: None,
family: "udp".to_owned(),
},
ExposePort {
host_port: 1234,
container_port: None,
family: "other".to_owned(),
},
],
external_network_interface: None,
expose_via_ipv6: true,
source_cidr_v4: None,
source_cidr_v6: None,
};
let actual: WiderWorldToContainerRule = toml::from_str(fragment).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn parse_expose_port_single_struct() {
for port in &[
"{ host_port = 80 }",
r#"{ host_port = 80, family = "tcp" }"#,
] {
let fragment = format!(
r#"
network = "network"
dst_container = "dst_container"
expose_port = {}
"#,
port
);
let expected = WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
}],
external_network_interface: None,
expose_via_ipv6: true,
source_cidr_v4: None,
source_cidr_v6: None,
};
let actual: WiderWorldToContainerRule = toml::from_str(&fragment).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn parse_expose_port_seq_struct() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = [
{ host_port = 80 },
{ host_port = 8080, container_port = 80 },
{ host_port = 8081, container_port = 81, family = "udp" },
{ host_port = 8082, container_port = 82, family = "other" },
]
"#;
let expected = WiderWorldToContainerRule {
network: "network".to_owned(),
dst_container: "dst_container".to_owned(),
expose_port: vec![
ExposePort {
host_port: 80,
container_port: None,
family: "tcp".to_owned(),
},
ExposePort {
host_port: 8080,
container_port: Some(80),
family: "tcp".to_owned(),
},
ExposePort {
host_port: 8081,
container_port: Some(81),
family: "udp".to_owned(),
},
ExposePort {
host_port: 8082,
container_port: Some(82),
family: "other".to_owned(),
},
],
external_network_interface: None,
expose_via_ipv6: true,
source_cidr_v4: None,
source_cidr_v6: None,
};
let actual: WiderWorldToContainerRule = toml::from_str(fragment).unwrap();
assert_eq!(expected, actual);
}
#[test]
#[should_panic(expected = "port string has invalid format")]
fn parse_expose_port_string_invalid_format() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = "80/tcp/what"
"#;
toml::from_str::<WiderWorldToContainerRule>(fragment).unwrap();
}
#[test]
#[should_panic(expected = "invalid digit found in string")]
fn parse_expose_port_string_invalid_int() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = "noint"
"#;
toml::from_str::<WiderWorldToContainerRule>(fragment).unwrap();
}
#[test]
#[should_panic(expected = "invalid digit found in string")]
fn parse_expose_port_string_invalid_int2() {
let fragment = r#"
network = "network"
dst_container = "dst_container"
expose_port = "noint/tcp"
"#;
toml::from_str::<WiderWorldToContainerRule>(fragment).unwrap();
}
#[test]
fn parse_external_network_interfaces_single() {
let fragment = r#"external_network_interfaces = "eni""#;
let expected = GlobalDefaults {
external_network_interfaces: Some(vec!["eni".to_owned()]),
default_docker_bridge_to_host_policy: ChainPolicy::Accept,
..Default::default()
};
let actual: GlobalDefaults = toml::from_str(fragment).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn parse_external_network_interfaces_seq() {
let fragment = r#"external_network_interfaces = ["eni1", "eni2"]"#;
let expected = GlobalDefaults {
external_network_interfaces: Some(vec!["eni1".to_owned(), "eni2".to_owned()]),
default_docker_bridge_to_host_policy: ChainPolicy::Accept,
..Default::default()
};
let actual: GlobalDefaults = toml::from_str(fragment).unwrap();
assert_eq!(expected, actual);
}
#[cfg_attr(crate_major_version = "1", allow(deprecated))]
#[cfg_attr(not(crate_major_version = "1"), should_panic)]
#[test]
fn ensure_backwards_compatibility_v1() {
toml::from_str::<DFW<TestBackend>>(
r#"
[defaults]
default_docker_bridge_to_host_policy = "accept"
"#,
)
.unwrap();
toml::from_str::<DFW<TestBackend>>(
r#"
[initialization]
rules = [
"custom rule 1",
"custom rule 2",
]
"#,
)
.unwrap();
toml::from_str::<DFW<TestBackend>>(
r#"
[container_to_container]
default_policy = "accept"
[[container_to_container.rules]]
network = ""
action = "accept"
[container_to_wider_world]
default_policy = "accept"
[[container_to_wider_world.rules]]
action = "accept"
[container_to_host]
default_policy = "accept"
[[container_to_host.rules]]
network = ""
action = "accept"
"#,
)
.unwrap();
{
let dfw = toml::from_str::<DFW<TestBackend>>(
r#"
[[wider_world_to_container.rules]]
network = ""
dst_container = ""
expose_port = 0
source_cidr = "127.0.0.0/8"
"#,
)
.unwrap();
let rules = dfw.wider_world_to_container.unwrap().rules.unwrap();
let WiderWorldToContainerRule {
source_cidr_v4,
source_cidr_v6,
..
} = rules.first().unwrap();
assert!(source_cidr_v4.is_some());
assert!(source_cidr_v6.is_none());
}
let _ = GlobalDefaults {
custom_tables: None,
..Default::default()
};
}