From ecb67d012bfa5c292894c5a5a9554a85771f7d48 Mon Sep 17 00:00:00 2001 From: Franziska Kunsmann Date: Thu, 3 Jun 2021 13:57:50 +0200 Subject: [PATCH] bundles/nftables: introduce --- bundles/nftables/files/nftables.conf | 72 ++++++++++++++++++++++ bundles/nftables/items.py | 35 +++++++++++ bundles/nftables/metadata.py | 91 ++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 bundles/nftables/files/nftables.conf create mode 100644 bundles/nftables/items.py create mode 100644 bundles/nftables/metadata.py diff --git a/bundles/nftables/files/nftables.conf b/bundles/nftables/files/nftables.conf new file mode 100644 index 0000000..f417856 --- /dev/null +++ b/bundles/nftables/files/nftables.conf @@ -0,0 +1,72 @@ +#!/usb/sbin/nft -f + +flush ruleset + +table inet filter { + chain input { + type filter hook input priority 0 + policy drop + + tcp flags syn tcp option maxseg size 1-500 drop + + ct state { established, related } accept + ct state invalid drop + + iif lo accept + + icmp type timestamp-request drop + icmp type timestamp-reply drop + ip protocol icmp accept + + ip6 nexthdr ipv6-icmp accept +% for ruleset, rules in sorted(node.metadata.get('nftables/rules/input', {}).items()): + + # ${ruleset} +% for rule in rules: + ${rule} +% endfor + # / ${ruleset} +% endfor + } + + chain output { + type filter hook output priority 0 + policy accept + } + + chain forward { + type filter hook forward priority 0 + policy drop + + icmp type timestamp-request drop + icmp type timestamp-reply drop + +% for ruleset, rules in sorted(node.metadata.get('nftables/rules/forward', {}).items()): + + # ${ruleset} +% for rule in rules: + ${rule} +% endfor + # / ${ruleset} +% endfor + } +} + +table nat { + chain prerouting { + type nat hook prerouting priority -100 + +% for rule in node.metadata.get('nftables/rules/nat_prerouting', []): + ${rule} +% endfor + } + chain postrouting { + type nat hook postrouting priority 100 + +% for rule in node.metadata.get('nftables/rules/nat_postrouting', []): + ${rule} +% endfor + } +} + +include "/etc/nftables-rules.d/*-*" diff --git a/bundles/nftables/items.py b/bundles/nftables/items.py new file mode 100644 index 0000000..f755916 --- /dev/null +++ b/bundles/nftables/items.py @@ -0,0 +1,35 @@ +if node.has_bundle('pacman'): + package = 'pkg_pacman:nftables' +else: + package = 'pkg_apt:nftables' + +directories = { + # used by other bundles + '/etc/nftables-rules.d': { + 'purge': True, + 'triggers': { + 'svc_systemd:nftables:reload', + }, + }, +} + +files = { + '/etc/nftables.conf': { + 'content_type': 'mako', + 'needs': { + 'directory:/etc/nftables-rules.d', + }, + 'triggers': { + 'svc_systemd:nftables:reload', + }, + }, +} + +svc_systemd = { + 'nftables': { + 'needs': { + 'file:/etc/nftables.conf', + package, + }, + }, +} diff --git a/bundles/nftables/metadata.py b/bundles/nftables/metadata.py new file mode 100644 index 0000000..a960c32 --- /dev/null +++ b/bundles/nftables/metadata.py @@ -0,0 +1,91 @@ +from bundlewrap.exceptions import BundleError + +defaults = { + 'apt': { + 'packages': { + 'nftables': {}, + + # XXX remove after all systems have been migrated + }, + }, + 'pacman': { + 'packages': { + 'nftables': {}, + 'iptables-nft': { + # uninstalls iptables automatically + 'needed_by': { + 'pkg_pacman:nftables', + }, + }, + }, + }, +} + +if not node.has_bundle('vmhost'): + # see comment in bundles/vmhost/items.py + defaults['apt']['packages']['iptables'] = { + 'installed': False, + 'needed_by': { + 'pkg_apt:nftables', + }, + } + +@metadata_reactor.provides( + 'nftables/rules/input/port_rules', +) +def port_rules_to_nftables(metadata): + # Using this, bundles can simply set up port based rules. This + # reactor will then take care of converting those rules to actual + # nftables rules + ruleset = set() + + # Plese note we do not set any defaults for ports. Bundles are + # expected to know themselves which default to use. + for portdef, targets in metadata.get('firewall/port_rules', {}).items(): + if '/' in portdef: + port, proto = portdef.split('/', 2) + + if proto not in {'udp'}: + raise BundleError(f'firewall/port_rules: illegal identifier {portdef} in metadata for {node.name}') + else: + port = portdef + proto = 'tcp' + + for target in targets: + if port == '*' and target == '*': + raise BundleError('firewall/port_rules: setting both port and target to * is unsupported') + + comment = f'# port_rules {target}' + + if port != '*': + if ':' in port: + parts = port.split(':') + port_str = f'dport {{ {parts[0]}-{parts[1]} }} ' + else: + port_str = f'dport {port} ' + prefix = '' + else: + port_str = '' + prefix = 'meta l4proto ' + + if target == '*': + ruleset.add(f'{prefix}{proto} {port_str}accept {comment}') + else: + resolved = repo.libs.tools.resolve_identifier(repo, target) + + for address in resolved['ipv4']: + ruleset.add(f'{prefix}{proto} {port_str}ip saddr {address} accept {comment}') + + for address in resolved['ipv6']: + ruleset.add(f'{prefix}{proto} {port_str}ip6 saddr {address} accept {comment}') + + return { + 'nftables': { + 'rules': { + # order does not matter here. + 'input': { + 'port_rules': sorted(ruleset), + }, + }, + }, + }