mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-10 18:38:22 +00:00
Implement nfTables Rule Generation
This commit is contained in:
parent
716fa43ade
commit
d95f2d9f01
4 changed files with 361 additions and 0 deletions
|
@ -2,6 +2,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
pub mod chrony;
|
pub mod chrony;
|
||||||
pub mod networkd;
|
pub mod networkd;
|
||||||
|
pub mod nftables;
|
||||||
pub mod unbound;
|
pub mod unbound;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
268
src/apply/nftables.rs
Normal file
268
src/apply/nftables.rs
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
use super::ApplyError;
|
||||||
|
use crate::definitions::firewall::{SNATType, Verdict};
|
||||||
|
use crate::definitions::object::{Address, AddressType, PortDefinition, Service, ServiceType};
|
||||||
|
use crate::{definitions::config::Config, templates};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::{error::Error, io::Write};
|
||||||
|
use tera::Context;
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
const NFTABLES_CONFIG_PATH: &str = "/etc/nftables/nfsense.conf";
|
||||||
|
const NFTABLES_TEMPLATE_PATH: &str = "nftables/nftables.conf";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Rule {
|
||||||
|
pub name: String,
|
||||||
|
pub services: Vec<String>,
|
||||||
|
pub addresses: String,
|
||||||
|
pub counter: bool,
|
||||||
|
pub verdict: Option<String>,
|
||||||
|
pub destination_nat_action: Option<String>,
|
||||||
|
pub source_nat_action: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_addresses_to_strings(addresses: Vec<Address>) -> Vec<String> {
|
||||||
|
let mut list = vec![];
|
||||||
|
for address in addresses {
|
||||||
|
match address.address_type {
|
||||||
|
AddressType::Host { address } => list.push(address.to_string()),
|
||||||
|
AddressType::Range { range } => list.push(range.to_string()),
|
||||||
|
AddressType::Network { network } => list.push(network.to_string()),
|
||||||
|
AddressType::Group { .. } => {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_list_to_set(list: Vec<String>) -> String {
|
||||||
|
if list.len() == 0 {
|
||||||
|
return "".to_string();
|
||||||
|
} else if list.len() == 1 {
|
||||||
|
return list[0].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = "{ ".to_string();
|
||||||
|
|
||||||
|
for (index, element) in list.iter().enumerate() {
|
||||||
|
res += element;
|
||||||
|
if index < list.len() - 1 {
|
||||||
|
res += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += " }";
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_address_matcher(
|
||||||
|
source_addresses: Vec<Address>,
|
||||||
|
destination_addresses: Vec<Address>,
|
||||||
|
) -> Result<String, ApplyError> {
|
||||||
|
let source_list = convert_addresses_to_strings(source_addresses);
|
||||||
|
let destination_list = convert_addresses_to_strings(destination_addresses);
|
||||||
|
let mut res = "".to_string();
|
||||||
|
|
||||||
|
if source_list.len() > 0 {
|
||||||
|
res += "ip saddr ";
|
||||||
|
res += &convert_list_to_set(source_list);
|
||||||
|
res += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if destination_list.len() > 0 {
|
||||||
|
res += "ip daddr ";
|
||||||
|
res += &convert_list_to_set(destination_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_port_matcher(
|
||||||
|
protocol: &str,
|
||||||
|
source: PortDefinition,
|
||||||
|
destination: PortDefinition,
|
||||||
|
) -> String {
|
||||||
|
let source_string = match source {
|
||||||
|
PortDefinition::Any => "".to_string(),
|
||||||
|
PortDefinition::Single { port } => {
|
||||||
|
protocol.to_string() + &" sport ".to_string() + &port.to_string()
|
||||||
|
}
|
||||||
|
PortDefinition::Range {
|
||||||
|
start_port,
|
||||||
|
end_port,
|
||||||
|
} => {
|
||||||
|
protocol.to_string()
|
||||||
|
+ &" sport ".to_string()
|
||||||
|
+ &start_port.to_string()
|
||||||
|
+ " - "
|
||||||
|
+ &end_port.to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let destination_string = match destination {
|
||||||
|
PortDefinition::Any => "".to_string(),
|
||||||
|
PortDefinition::Single { port } => {
|
||||||
|
protocol.to_string() + &" dport ".to_string() + &port.to_string()
|
||||||
|
}
|
||||||
|
PortDefinition::Range {
|
||||||
|
start_port,
|
||||||
|
end_port,
|
||||||
|
} => {
|
||||||
|
protocol.to_string()
|
||||||
|
+ &" dport ".to_string()
|
||||||
|
+ &start_port.to_string()
|
||||||
|
+ " - "
|
||||||
|
+ &end_port.to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if source_string.len() != 0 && destination_string.len() != 0 {
|
||||||
|
source_string + " " + &destination_string
|
||||||
|
} else {
|
||||||
|
source_string + &destination_string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service_matchers(services: Vec<Service>) -> Result<Vec<String>, ApplyError> {
|
||||||
|
let mut list = vec![];
|
||||||
|
for service in services {
|
||||||
|
match service.service_type {
|
||||||
|
ServiceType::TCP {
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
} => list.push(generate_port_matcher("tcp", source, destination)),
|
||||||
|
ServiceType::UDP {
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
} => list.push(generate_port_matcher("udp", source, destination)),
|
||||||
|
ServiceType::ICMP { code } => list.push("icmp codes ".to_string() + &code.to_string()),
|
||||||
|
ServiceType::Group { .. } => (
|
||||||
|
//TODO
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_destination_nat_action(
|
||||||
|
dnat_address: Option<Address>,
|
||||||
|
dnat_service: Option<Service>,
|
||||||
|
) -> Result<String, ApplyError> {
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_source_nat_action(
|
||||||
|
snat_address: Option<Address>,
|
||||||
|
snat_service: Option<Service>,
|
||||||
|
) -> Result<String, ApplyError> {
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_nftables(pending_config: Config, _current_config: Config) -> Result<(), ApplyError> {
|
||||||
|
let config_data;
|
||||||
|
let mut context = Context::new();
|
||||||
|
|
||||||
|
// Forward Rules
|
||||||
|
let mut forward_rules = vec![];
|
||||||
|
for rule in &pending_config.firewall.forward_rules {
|
||||||
|
forward_rules.push(Rule {
|
||||||
|
name: rule.name.clone(),
|
||||||
|
counter: rule.counter,
|
||||||
|
addresses: generate_address_matcher(
|
||||||
|
rule.source_addresses(pending_config.clone()),
|
||||||
|
rule.destination_addresses(pending_config.clone()),
|
||||||
|
)?,
|
||||||
|
services: generate_service_matchers(rule.services(pending_config.clone()))?,
|
||||||
|
verdict: Some(match rule.verdict {
|
||||||
|
Verdict::Accept => "accept".to_string(),
|
||||||
|
Verdict::Drop => "drop".to_string(),
|
||||||
|
Verdict::Continue => "continue".to_string(),
|
||||||
|
}),
|
||||||
|
destination_nat_action: None,
|
||||||
|
source_nat_action: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
context.insert("forward_rules", &forward_rules);
|
||||||
|
|
||||||
|
// Destination Nat Rules
|
||||||
|
let mut destination_nat_rules = vec![];
|
||||||
|
for rule in &pending_config.firewall.destination_nat_rules {
|
||||||
|
destination_nat_rules.push(Rule {
|
||||||
|
name: rule.name.clone(),
|
||||||
|
counter: rule.counter,
|
||||||
|
addresses: generate_address_matcher(
|
||||||
|
rule.source_addresses(pending_config.clone()),
|
||||||
|
rule.destination_addresses(pending_config.clone()),
|
||||||
|
)?,
|
||||||
|
services: generate_service_matchers(rule.services(pending_config.clone()))?,
|
||||||
|
verdict: None,
|
||||||
|
destination_nat_action: Some(generate_destination_nat_action(
|
||||||
|
rule.dnat_address(pending_config.clone()),
|
||||||
|
rule.dnat_service(pending_config.clone()),
|
||||||
|
)?),
|
||||||
|
source_nat_action: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
context.insert("destination_nat_rules", &destination_nat_rules);
|
||||||
|
|
||||||
|
// Source Nat Rules
|
||||||
|
let mut source_nat_rules = vec![];
|
||||||
|
for rule in &pending_config.firewall.source_nat_rules {
|
||||||
|
source_nat_rules.push(Rule {
|
||||||
|
name: rule.name.clone(),
|
||||||
|
counter: rule.counter,
|
||||||
|
addresses: generate_address_matcher(
|
||||||
|
rule.source_addresses(pending_config.clone()),
|
||||||
|
rule.destination_addresses(pending_config.clone()),
|
||||||
|
)?,
|
||||||
|
services: generate_service_matchers(rule.services(pending_config.clone()))?,
|
||||||
|
verdict: None,
|
||||||
|
destination_nat_action: None,
|
||||||
|
source_nat_action: Some(match rule.snat_type.clone() {
|
||||||
|
SNATType::Masquerade => "masquerade".to_string(),
|
||||||
|
SNATType::SNAT { .. } => generate_source_nat_action(
|
||||||
|
rule.snat_type.address(pending_config.clone()),
|
||||||
|
rule.snat_type.service(pending_config.clone()),
|
||||||
|
)?,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
context.insert("source_nat_rules", &source_nat_rules);
|
||||||
|
|
||||||
|
match templates::TEMPLATES.render(NFTABLES_TEMPLATE_PATH, &context) {
|
||||||
|
Ok(s) => config_data = s,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error: {}", e);
|
||||||
|
let mut cause = e.source();
|
||||||
|
while let Some(e) = cause {
|
||||||
|
error!("Reason: {}", e);
|
||||||
|
cause = e.source();
|
||||||
|
}
|
||||||
|
return Err(ApplyError::TemplateError(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Deleting old nftables Config");
|
||||||
|
std::fs::remove_file(NFTABLES_CONFIG_PATH)?;
|
||||||
|
|
||||||
|
info!("Writing new nftables Config");
|
||||||
|
let mut f = std::fs::File::create(NFTABLES_CONFIG_PATH)?;
|
||||||
|
f.write_all(config_data.as_bytes())?;
|
||||||
|
|
||||||
|
info!("Restarting nftables");
|
||||||
|
match Command::new("systemctl")
|
||||||
|
.arg("restart")
|
||||||
|
.arg("nftables")
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(out) => {
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ApplyError::ServiceRestartFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(ApplyError::IOError(err)),
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ static APPLY_FUNCTIONS: &'static [fn(
|
||||||
current_config: Config,
|
current_config: Config,
|
||||||
) -> Result<(), super::apply::ApplyError>] = &[
|
) -> Result<(), super::apply::ApplyError>] = &[
|
||||||
super::apply::networkd::apply_networkd,
|
super::apply::networkd::apply_networkd,
|
||||||
|
super::apply::nftables::apply_nftables,
|
||||||
super::apply::chrony::apply_chrony,
|
super::apply::chrony::apply_chrony,
|
||||||
super::apply::unbound::apply_unbound,
|
super::apply::unbound::apply_unbound,
|
||||||
];
|
];
|
||||||
|
|
91
src/templates/nftables/nftables.conf
Normal file
91
src/templates/nftables/nftables.conf
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/sbin/nft -f
|
||||||
|
|
||||||
|
flush ruleset
|
||||||
|
|
||||||
|
# nfsense nftables inet (ipv4 + ipv6) table
|
||||||
|
table inet nfsense_inet {
|
||||||
|
|
||||||
|
# Rule Counters for Forward Rules
|
||||||
|
{% for rule in forward_rules -%}
|
||||||
|
{% if rule.counter -%}
|
||||||
|
counter fw_{{ loop.index }} {
|
||||||
|
comment "{{ rule.name }}"
|
||||||
|
}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
# Rule Counters for Destination NAT Rules
|
||||||
|
{% for rule in destination_nat_rules -%}
|
||||||
|
{% if rule.counter -%}
|
||||||
|
counter dnat_{{ loop.index }} {
|
||||||
|
comment "{{ rule.name }}"
|
||||||
|
}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
# Rule Counters for Source NAT Rules
|
||||||
|
{% for rule in source_nat_rules -%}
|
||||||
|
{% if rule.counter -%}
|
||||||
|
counter snat_{{ loop.index }} {
|
||||||
|
comment "{{ rule.name }}"
|
||||||
|
}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
# Inbound Rules
|
||||||
|
chain inbound {
|
||||||
|
type filter hook input priority 0; policy drop;
|
||||||
|
|
||||||
|
# Allow traffic from established and related packets, drop invalid
|
||||||
|
ct state vmap { established : accept, related : accept, invalid : drop }
|
||||||
|
|
||||||
|
# Allow loopback traffic
|
||||||
|
iifname lo accept
|
||||||
|
|
||||||
|
# temp Allow Inbound traffic
|
||||||
|
counter accept comment "temp inbound allow"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Forward Rules
|
||||||
|
chain forward {
|
||||||
|
type filter hook forward priority 0; policy drop;
|
||||||
|
|
||||||
|
# Allow traffic from established and related packets, drop invalid
|
||||||
|
ct state vmap { established : accept, related : accept, invalid : drop }
|
||||||
|
|
||||||
|
# Generated Forward Rules
|
||||||
|
{% for rule in forward_rules -%}
|
||||||
|
{% set index = loop.index -%}
|
||||||
|
{% for service in rule.services -%}
|
||||||
|
{{ rule.addresses }} {{ service }} {% if rule.counter %} counter name fw_{{ index }} {% endif %} {{ rule.verdict }}
|
||||||
|
{% endfor -%}
|
||||||
|
{% endfor -%}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Destination NAT Rules
|
||||||
|
chain prerouting {
|
||||||
|
type nat hook prerouting priority -100; policy accept;
|
||||||
|
|
||||||
|
# Generated Destination NAT Rules
|
||||||
|
{% for rule in destination_nat_rules -%}
|
||||||
|
{% set index = loop.index -%}
|
||||||
|
{% for service in rule.services -%}
|
||||||
|
{{ rule.addresses }} {{ service }} {% if rule.counter %} counter name dnat_{{ index }} {% endif %} {{ rule.destination_nat_action }}
|
||||||
|
{% endfor -%}
|
||||||
|
{% endfor -%}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Source NAT Rules
|
||||||
|
chain postrouting {
|
||||||
|
type nat hook postrouting priority 100; policy accept;
|
||||||
|
|
||||||
|
# Generated Source NAT Rules
|
||||||
|
{% for rule in source_nat_rules -%}
|
||||||
|
{% set index = loop.index -%}
|
||||||
|
{% for service in rule.services -%}
|
||||||
|
{{ rule.addresses }} {{ service }} {% if rule.counter %} counter name snat_{{ index }} {% endif %} {{ rule.source_nat_action }}
|
||||||
|
{% endfor -%}
|
||||||
|
{% endfor -%}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue