From 39e1db9c32425e082742a4e5d028efae8fa0deec Mon Sep 17 00:00:00 2001 From: Patrik Lundin Date: Mon, 31 Mar 2025 17:19:29 +0200 Subject: [PATCH] Add basic firewall setup for l4lb namespace Also teach sunet-l4lb-namespace to load the nft ruleset if it exists. While here modify the script so instead of running "once per netns config file" we merge the interface config from each json file into the same dict per namespace. Without this we would attempt to load the nft ruleset twice (once per file that mentioned the namespace) or warn twice if the file did not exist etc. --- .../cdn/files/l4lb/sunet-l4lb-namespace | 22 +++++++++- .../etc/puppet/modules/cdn/manifests/l4lb.pp | 8 ++++ .../cdn/templates/l4lb/nft-l4lb.conf.erb | 40 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 global/overlay/etc/puppet/modules/cdn/templates/l4lb/nft-l4lb.conf.erb diff --git a/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace b/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace index 2463f63..cc68c55 100755 --- a/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace +++ b/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace @@ -15,6 +15,7 @@ mypy --strict sunet-l4lb-namespace import ipaddress import json +import os import shlex import subprocess import sys @@ -142,6 +143,15 @@ def setup_namespaces(netns_data: dict[str, dict[str, dict[str, list[str]]]]) -> # Make localhost available run_command(f"ip netns exec {namespace} ip link set lo up") + # (Re)load the nft ruleset for the given namespace + nft_ruleset = f"/opt/sunet-cdn/l4lb/conf/nft-{namespace}.conf" + if os.path.isfile(nft_ruleset): + run_command(f"ip netns exec {namespace} nft -f {nft_ruleset}") + else: + print( + f"WARNING: no nft ruleset found for namespace '{namespace}' ({nft_ruleset}), the namespace will not be firewalled" # pylint: disable=line-too-long + ) + configure_interfaces(namespace, if_data) @@ -175,14 +185,24 @@ def main() -> None: "/opt/sunet-cdn/l4lb/conf/netns-sunet-cdn-agent.json", ] + merged_netns_data: dict[str, dict[str, dict[str, list[str]]]] = {} for input_file in input_files: try: with open(input_file, encoding="utf-8") as f: netns_data = json.load(f) + + # Combine interface config from multiple files belonging to the same namespace + for ns, ns_data in netns_data.items(): + if ns in merged_netns_data: + merged_netns_data[ns].update(ns_data) + else: + merged_netns_data[ns] = ns_data + except FileNotFoundError: print(f"skipping nonexistant file '{input_file}'") continue - setup_namespaces(netns_data) + + setup_namespaces(merged_netns_data) if __name__ == "__main__": diff --git a/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp b/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp index 7b28cb4..c666b2d 100644 --- a/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp +++ b/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp @@ -64,6 +64,14 @@ class cdn::l4lb( content => template('cdn/l4lb/netns-base.json.erb'), } + file { '/opt/sunet-cdn/l4lb/conf/nft-l4lb.conf.erb': + ensure => file, + owner => 'root', + group => 'root', + mode => '0644', + content => template('cdn/l4lb/nft-l4lb.conf.erb'), + } + file { '/usr/local/bin/sunet-l4lb-namespace': ensure => file, owner => 'root', diff --git a/global/overlay/etc/puppet/modules/cdn/templates/l4lb/nft-l4lb.conf.erb b/global/overlay/etc/puppet/modules/cdn/templates/l4lb/nft-l4lb.conf.erb new file mode 100644 index 0000000..a65391a --- /dev/null +++ b/global/overlay/etc/puppet/modules/cdn/templates/l4lb/nft-l4lb.conf.erb @@ -0,0 +1,40 @@ +#!/usr/sbin/nft -f + +flush ruleset + +table inet filter { + chain input { + type filter hook input priority 0; policy drop; + + # accept any localhost traffic + iif lo counter accept + + # accept icmp + ip protocol icmp counter accept + ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, + parameter-problem, echo-request, mld-listener-query, + nd-router-solicit, nd-router-advert, nd-neighbor-solicit, + nd-neighbor-advert } counter accept + + # accept traffic originated from us + ct state established counter accept + # silently drop invalid packets + ct state invalid counter drop + } + chain forward { + type filter hook forward priority 0; policy drop; + } + chain output { + type filter hook output priority 0; + } +} + +# HTTP and HTTPS +add rule inet filter input tcp dport 80 counter accept comment "l4lb HTTP" +add rule inet filter input tcp dport 443 counter accept comment "l4lb HTTPS" + +# BGP +add rule inet filter input ip saddr { 130.242.64.232 } tcp dport 179 counter accept comment "tug-r11-v4" +add rule inet filter input ip saddr { 130.242.64.234 } tcp dport 179 counter accept comment "tug-r12-v4" +add rule inet filter input ip6 saddr { 2001:6b0:2006:74:: } tcp dport 179 counter accept comment "tug-r11-v6" +add rule inet filter input ip6 saddr { 2001:6b0:2006:75:: } tcp dport 179 counter accept comment "tug-r12-v6"