use crate::startup::Interfaces; static UNIVERSE: &'static str = "0.0.0.0/0"; pub(crate) fn firewall_rules(interfaces: &Interfaces) -> String { filter(interfaces) + "\n" + &nat(interfaces) } // FILTER table rules fn filter(interfaces: &Interfaces) -> String { let mut filter = String::from( r#"*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] "#, ); // INPUT: Incoming traffic from various interfaces // Accept everything on loopback filter += &format!( "-A INPUT -i lo -s {universe} -d {universe} -j ACCEPT\n", universe = UNIVERSE ); // Allow internal machines to connect to anything for iface in interfaces.internal.iter().chain(&interfaces.vlan) { filter += &format!( "-A INPUT -i {intif} -s {intip}/{intmask} -d {universe} -j ACCEPT\n", intif = iface.interface, intip = iface.ip, intmask = iface.mask, universe = UNIVERSE ); } // Disallow IP spoofing, internal IPs should only come from the internal network // If an internal IP is seen by the external interface, it's BAD!!!! for iface in interfaces.internal.iter().chain(&interfaces.vlan) { filter += &format!( "-A INPUT -i {extif} -s {intip}/{intmask} -d {universe} -j REJECT\n", extif = interfaces.external.interface, intip = iface.ip, intmask = iface.mask, universe = UNIVERSE, ); } // Allow ICMP traffic on external interface filter += &format!( "-A INPUT -i {extif} -p ICMP -s {universe} -d {extip}/{extmask} -j ACCEPT\n", extif = interfaces.external.interface, universe = UNIVERSE, extip = interfaces.external.ip, extmask = interfaces.external.mask, ); // Don't prevent existing connections for continuing filter += &format!( "-A INPUT -i {extif} -s {universe} -d {extip}/{extmask} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT\n", extif = interfaces.external.interface, universe = UNIVERSE, extip = interfaces.external.ip, extmask = interfaces.external.mask, ); // Allow DHCP traffic for iface in interfaces.internal.iter().chain(&interfaces.vlan) { filter += &format!( "-A INPUT -i {intif} -p tcp --sport 68 --dport 67 -j ACCEPT\n", intif = iface.interface, ); filter += &format!( "-A INPUT -i {intif} -p udp --sport 68 --dport 67 -j ACCEPT\n", intif = iface.interface, ); } for iface in &interfaces.tunnel { filter += &format!( "-A INPUT -i {tunface} -j ACCEPT\n", tunface = iface.interface, ); } filter += &format!( "-A INPUT -s {universe} -d {universe} -j REJECT\n", universe = UNIVERSE ); // OUTPUT: Outgoing traffic from vairous interfaces // netfilter bug workaround filter += "-A OUTPUT -m conntrack -p icmp --ctstate INVALID -j DROP\n"; // Accept everything on loopback filter += &format!( "-A OUTPUT -o lo -s {universe} -d {universe} -j ACCEPT\n", universe = UNIVERSE, ); // Allow VLAN traffic only on VLAN for iface in &interfaces.vlan { // Allow internal traffic only on network associated with interface filter += &format!( "-A OUTPUT -o {intif} -s {extip}/{extmask} -d {intip}/{intmask} -j ACCEPT\n", intif = iface.interface, extip = interfaces.external.ip, extmask = interfaces.external.mask, intip = iface.ip, intmask = iface.mask, ); // Allow traffic from self to networks associated with interface filter += &format!( "-A OUTPUT -o {intif} -s {intip}/32 -d {intip}/{intmask} -j ACCEPT\n", intif = iface.interface, intip = iface.ip, intmask = iface.mask, ); } if interfaces.shared_internal { for iface in &interfaces.internal { // jface (jeans iface) for jface in &interfaces.internal { // Allow internal traffic across all internal interfaces filter += &format!( "-A OUTPUT -o {intif} -s {extip}/{extmask} -d {jntip}/{jntmask} -j ACCEPT\n", intif = iface.interface, extip = interfaces.external.ip, extmask = interfaces.external.mask, jntip = jface.ip, // jeans IP jntmask = jface.mask, // jeans mask ); // Allow internal traffic from self to internal networks filter += &format!( "-A OUTPUT -o {intif} -s {intip}/32 -d {jntip}/{jntmask} -j ACCEPT\n", intif = iface.interface, intip = iface.ip, jntip = jface.ip, // jeans IP jntmask = jface.mask, // jeans mask ); } } } else { for iface in &interfaces.internal { // Allow internal traffic only on network associated with interface filter += &format!( "-A OUTPUT -o {intif} -s {extip}/{extmask} -d {intip}/{intmask} -j ACCEPT\n", intif = iface.interface, extip = interfaces.external.ip, extmask = interfaces.external.mask, intip = iface.ip, intmask = iface.mask, ); // Allow traffic from self to networks associated with interface filter += &format!( "-A OUTPUT -o {intif} -s {intip}/32 -d {intip}/{intmask} -j ACCEPT\n", intif = iface.interface, intip = iface.ip, intmask = iface.mask, ); } } for iface in interfaces.internal.iter().chain(&interfaces.vlan) { // Deny traffic to internal networks on external interface filter += &format!( "-A OUTPUT -o {extif} -s {universe} -d {intip}/{intmask} -j REJECT\n", extif = interfaces.external.interface, universe = UNIVERSE, intip = iface.ip, intmask = iface.mask, ); } for iface in &interfaces.tunnel { filter += &format!( "-A OUTPUT -o {tunface} -j ACCEPT\n", tunface = iface.interface, ); } // Allow traffic out from external interface to anywhere filter += &format!( "-A OUTPUT -o {extif} -s {extip}/{extmask} -d {universe} -j ACCEPT\n", extif = interfaces.external.interface, extip = interfaces.external.ip, extmask = interfaces.external.mask, universe = UNIVERSE, ); for iface in interfaces.internal.iter().chain(&interfaces.vlan) { // Allow DHCP traffic out from internal interfaces filter += &format!( "-A OUTPUT -o {intif} -p tcp -s {intip} --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT\n", intif = iface.interface, intip = iface.ip, ); filter += &format!( "-A OUTPUT -o {intif} -p udp -s {intip} --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT\n", intif = iface.interface, intip = iface.ip, ); } // Reject traffic we don't care about filter += &format!( "-A OUTPUT -s {universe} -d {universe} -j REJECT\n", universe = UNIVERSE ); // FORWARD: Forwarding traffic across interfaces for nat_iface in &interfaces.nats { if let Some(iface) = interfaces .internal .iter() .chain(interfaces.tunnel.iter()) .chain(interfaces.vlan.iter()) .find(|iface| iface.interface == *nat_iface) { filter += &format!( "-A FORWARD -i {natif} -d {natip}/{natmask} -j ACCEPT\n", natif = iface.interface, natip = iface.ip, natmask = iface.mask, ); } for iface in interfaces .internal .iter() .chain(interfaces.tunnel.iter()) .chain(interfaces.vlan.iter()) { let is_nat_iface = *nat_iface == iface.interface; let has_nat_subnet = interfaces .internal .iter() .chain(interfaces.tunnel.iter()) .chain(interfaces.vlan.iter()) .any(|other_iface| { *nat_iface == other_iface.interface && other_iface.ip == iface.ip && other_iface.mask == iface.mask }); if !is_nat_iface && !has_nat_subnet { // Accept packets over NAT'd tunnel interfaces from pre-existing connections filter += &format!( "-A FORWARD -i {natif} -o {intif} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT\n", natif = nat_iface, intif = iface.interface, ); // Reject packets over NAT'd tunnel interfaces filter += &format!( "-A FORWARD -i {natif} -o {intif} -j REJECT --reject-with icmp-port-unreachable\n", natif = nat_iface, intif = iface.interface, ); } } } // Accept packets over tunnel interfaces for iface in &interfaces.tunnel { filter += &format!( "-A FORWARD -i {tunface} -j ACCEPT\n", tunface = iface.interface, ); filter += &format!( "-A FORWARD -o {tunface} -j ACCEPT\n", tunface = iface.interface, ); } // Accept TCP packets for iface in interfaces.internal.iter().chain(&interfaces.vlan) { filter += &format!( "-A FORWARD -i {extif} -o {intif} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT\n", extif = interfaces.external.interface, intif = iface.interface, ); } for iface in &interfaces.vlan { // Allow packets across vlan interface filter += &format!( "-A FORWARD -i {intif} -o {intif} -j ACCEPT\n", intif = iface.interface, ); } if interfaces.shared_internal { for iface in &interfaces.internal { // jface (jeans interface) for jface in &interfaces.internal { // Allow packets across internal interfaces filter += &format!( "-A FORWARD -i {intif} -o {jntif} -j ACCEPT\n", intif = iface.interface, jntif = jface.interface, // jntif (jeans intif) ); } } } else { for iface in &interfaces.internal { // Allow packets across internal interface filter += &format!( "-A FORWARD -i {intif} -o {intif} -j ACCEPT\n", intif = iface.interface, ); } } // Forward packets to the internet for iface in interfaces.internal.iter().chain(&interfaces.vlan) { filter += &format!( "-A FORWARD -i {intif} -o {extif} -j ACCEPT\n", intif = iface.interface, extif = interfaces.external.interface, ); } filter += "-A FORWARD -j REJECT\n"; filter += "COMMIT\n"; filter } // NAT Table rules fn nat(interfaces: &Interfaces) -> String { let mut nat = String::from( r#"*nat :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] "#, ); nat += &format!( "-A POSTROUTING -o {extif} -j MASQUERADE\n", extif = interfaces.external.interface ); for nat_iface in &interfaces.nats { for iface in interfaces.internal.iter().chain(interfaces.tunnel.iter()) { let is_nat_iface = *nat_iface == iface.interface; let has_nat_subnet = interfaces .internal .iter() .chain(interfaces.tunnel.iter()) .chain(interfaces.vlan.iter()) .any(|other_iface| { *nat_iface == other_iface.interface && other_iface.ip == iface.ip && other_iface.mask == iface.mask }); if !is_nat_iface && !has_nat_subnet { nat += &format!( "-A POSTROUTING -s {intip}/{intmask} -o {natiface} -j MASQUERADE\n", intip = iface.ip, intmask = iface.mask, natiface = nat_iface, ); } } } nat += "COMMIT\n"; nat } // TODO: SSH // # Internal interface, SSH traffic accepted on port 3128 // -A INPUT -i $INTIF -p tcp --dport 3128 -j ACCEPT // // # External interface, SSH traffic allowed on port 3128 // -A INPUT -i $EXTIF -m conntrack -p tcp -s $UNIVERSE -d $EXTIP --dport 3128 -j ACCEPT