use crate::{de::*, nftables, FirewallBackend, Process};
use derive_builder::Builder;
use serde::Deserialize;
use std::str::FromStr;
use strum::{Display, EnumString};
const DEFAULT_PROTOCOL: &str = "tcp";
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct DFW<B>
where
B: FirewallBackend,
DFW<B>: Process<B>,
{
#[serde(default, alias = "defaults")]
pub global_defaults: GlobalDefaults,
#[serde(default)]
pub backend_defaults: Option<B::Defaults>,
#[deprecated(
since = "1.2.0",
note = "Provide the initialization in the nftables backend-defaults section instead. This \
field will be removed with release 2.0.0."
)]
pub initialization: Option<nftables::types::Initialization>,
pub container_to_container: Option<ContainerToContainer>,
pub container_to_wider_world: Option<ContainerToWiderWorld>,
pub container_to_host: Option<ContainerToHost>,
pub wider_world_to_container: Option<WiderWorldToContainer>,
pub container_dnat: Option<ContainerDNAT>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct GlobalDefaults {
#[serde(default, deserialize_with = "option_string_or_seq_string")]
pub external_network_interfaces: Option<Vec<String>>,
#[serde(default)]
pub default_docker_bridge_to_host_policy: ChainPolicy,
#[deprecated(
since = "1.2.0",
note = "Provide the custom tables in the nftables backend-defaults section instead. This \
field will be removed with release 2.0.0."
)]
#[serde(default, deserialize_with = "option_struct_or_seq_struct")]
pub custom_tables: Option<Vec<nftables::types::Table>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerToContainer {
pub default_policy: ChainPolicy,
pub same_network_verdict: Option<RuleVerdict>,
pub rules: Option<Vec<ContainerToContainerRule>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerToContainerRule {
pub network: String,
pub src_container: Option<String>,
pub dst_container: Option<String>,
pub matches: Option<String>,
#[serde(alias = "action")]
pub verdict: RuleVerdict,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerToWiderWorld {
pub default_policy: RuleVerdict,
pub rules: Option<Vec<ContainerToWiderWorldRule>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerToWiderWorldRule {
pub network: Option<String>,
pub src_container: Option<String>,
pub matches: Option<String>,
#[serde(alias = "action")]
pub verdict: RuleVerdict,
pub external_network_interface: Option<String>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerToHost {
pub default_policy: RuleVerdict,
pub rules: Option<Vec<ContainerToHostRule>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerToHostRule {
pub network: String,
pub src_container: Option<String>,
pub matches: Option<String>,
#[serde(alias = "action")]
pub verdict: RuleVerdict,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct WiderWorldToContainer {
pub rules: Option<Vec<WiderWorldToContainerRule>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct WiderWorldToContainerRule {
pub network: String,
pub dst_container: String,
#[serde(deserialize_with = "single_or_seq_string_or_struct")]
pub expose_port: Vec<ExposePort>,
pub external_network_interface: Option<String>,
#[serde(default = "default_wwtcr_expose_via_ipv6")]
pub expose_via_ipv6: bool,
#[serde(
default,
deserialize_with = "option_string_or_seq_string",
alias = "source_cidr"
)]
pub source_cidr_v4: Option<Vec<String>>,
#[serde(default, deserialize_with = "option_string_or_seq_string")]
pub source_cidr_v6: Option<Vec<String>>,
}
fn default_wwtcr_expose_via_ipv6() -> bool {
true
}
#[derive(Deserialize, Debug, Clone, Default, Builder, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ExposePort {
#[builder(field(public))]
pub host_port: u16,
#[builder(field(public), default = "self.default_container_port()")]
pub container_port: Option<u16>,
#[serde(default = "default_expose_port_family")]
#[builder(field(public), default = "self.default_family()")]
pub family: String,
}
impl ExposePortBuilder {
fn client_and_host_port(&mut self, value: &str) -> Result<&mut Self, String> {
let split: Vec<&str> = value.split(':').collect();
match split.len() {
1 => self.host_port = Some(split[0].parse().map_err(|e| format!("{}", e))?),
2 => {
self.host_port = Some(split[0].parse().map_err(|e| format!("{}", e))?);
self.container_port = Some(Some(split[1].parse().map_err(|e| format!("{}", e))?));
}
_ => return Err(format!("port string has invalid format '{}'", value)),
}
Ok(self)
}
fn default_container_port(&self) -> Option<u16> {
None
}
fn default_family(&self) -> String {
DEFAULT_PROTOCOL.to_owned()
}
}
impl FromStr for ExposePort {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split: Vec<&str> = s.split('/').collect();
Ok(match split.len() {
1 => ExposePortBuilder::default()
.client_and_host_port(split[0])?
.build()
.map_err(|error| format!("{}", error))?,
2 => ExposePortBuilder::default()
.client_and_host_port(split[0])?
.family(split[1].to_owned())
.build()
.map_err(|error| format!("{}", error))?,
_ => return Err(format!("port string has invalid format '{}'", s)),
})
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerDNAT {
pub rules: Option<Vec<ContainerDNATRule>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ContainerDNATRule {
pub src_network: Option<String>,
pub src_container: Option<String>,
pub dst_network: String,
pub dst_container: String,
#[serde(deserialize_with = "single_or_seq_string_or_struct")]
pub expose_port: Vec<ExposePort>,
}
fn default_expose_port_family() -> String {
DEFAULT_PROTOCOL.to_owned()
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Display, EnumString)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "snake_case")]
pub enum ChainPolicy {
#[strum(to_string = "accept", serialize = "ACCEPT")]
#[serde(alias = "ACCEPT")]
#[default]
Accept,
#[strum(to_string = "drop", serialize = "DROP")]
#[serde(alias = "DROP")]
Drop,
}
impl slog::Value for ChainPolicy {
fn serialize(
&self,
record: &slog::Record,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
self.to_string().serialize(record, key, serializer)
}
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Display, EnumString)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "snake_case")]
pub enum RuleVerdict {
#[serde(alias = "ACCEPT")]
#[strum(to_string = "accept", serialize = "ACCEPT")]
#[default]
Accept,
#[serde(alias = "DROP")]
#[strum(to_string = "drop", serialize = "DROP")]
Drop,
#[serde(alias = "REJECT")]
#[strum(to_string = "reject", serialize = "REJECT")]
Reject,
}
impl slog::Value for RuleVerdict {
fn serialize(
&self,
record: &slog::Record,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
self.to_string().serialize(record, key, serializer)
}
}
#[cfg(test)]
mod test {
use super::{ChainPolicy, RuleVerdict};
use std::str::FromStr;
#[test]
fn chainpolicy_fromstr() {
assert_eq!(ChainPolicy::Accept, FromStr::from_str("accept").unwrap());
assert_eq!(ChainPolicy::Accept, FromStr::from_str("ACCEPT").unwrap());
assert_eq!(ChainPolicy::Drop, FromStr::from_str("drop").unwrap());
assert_eq!(ChainPolicy::Drop, FromStr::from_str("DROP").unwrap());
}
#[test]
fn chainpolicy_tostring() {
assert_eq!("accept", &ChainPolicy::Accept.to_string());
assert_eq!("drop", &ChainPolicy::Drop.to_string());
}
#[test]
fn ruleverdict_fromstr() {
assert_eq!(RuleVerdict::Accept, FromStr::from_str("accept").unwrap());
assert_eq!(RuleVerdict::Accept, FromStr::from_str("ACCEPT").unwrap());
assert_eq!(RuleVerdict::Drop, FromStr::from_str("drop").unwrap());
assert_eq!(RuleVerdict::Drop, FromStr::from_str("DROP").unwrap());
assert_eq!(RuleVerdict::Reject, FromStr::from_str("reject").unwrap());
assert_eq!(RuleVerdict::Reject, FromStr::from_str("REJECT").unwrap());
}
#[test]
fn ruleverdict_tostring() {
assert_eq!("accept", &RuleVerdict::Accept.to_string());
assert_eq!("drop", &RuleVerdict::Drop.to_string());
assert_eq!("reject", &RuleVerdict::Reject.to_string());
}
}