bundles/nftables: store rules in dedicated files instead of nftables.conf
All checks were successful
kunsi/bundlewrap/pipeline/pr-main This commit looks good
kunsi/bundlewrap/pipeline/head This commit looks good

This commit is contained in:
Franzi 2021-12-14 14:03:13 +01:00
parent 1742f51778
commit 0101e0c92d
Signed by: kunsi
GPG key ID: 12E3D2136B818350
11 changed files with 77 additions and 102 deletions

View file

@ -37,20 +37,18 @@ def get_static_allocations(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'nftables/rules/input/dhcpd', 'nftables/rules/10-dhcpd',
) )
def nftables(metadata): def nftables(metadata):
rules = set() rules = set()
for iface in node.metadata.get('dhcpd/subnets', {}): for iface in node.metadata.get('dhcpd/subnets', {}):
rules.add(f'udp dport {{ 67, 68 }} iif {iface} accept') rules.add(f'inet filter input udp dport {{ 67, 68 }} iif {iface} accept')
return { return {
'nftables': { 'nftables': {
'rules': { 'rules': {
'input': {
# can't use port_rules here, because we're generating interface based rules. # can't use port_rules here, because we're generating interface based rules.
'dhcpd': sorted(rules), '10-dhcpd': sorted(rules),
},
}, },
} }
} }

View file

@ -19,14 +19,6 @@ table inet filter {
ip protocol icmp accept ip protocol icmp accept
ip6 nexthdr ipv6-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 { chain output {
@ -40,32 +32,15 @@ table inet filter {
icmp type timestamp-request drop icmp type timestamp-request drop
icmp type timestamp-reply 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 { table nat {
chain prerouting { chain prerouting {
type nat hook prerouting priority -100 type nat hook prerouting priority -100
% for rule in sorted(node.metadata.get('nftables/rules/nat_prerouting', [])):
${rule}
% endfor
} }
chain postrouting { chain postrouting {
type nat hook postrouting priority 100 type nat hook postrouting priority 100
% for rule in sorted(node.metadata.get('nftables/rules/nat_postrouting', [])):
${rule}
% endfor
} }
} }

View file

@ -0,0 +1,3 @@
% for rule in rules:
add rule ${rule}
% endfor

View file

@ -15,7 +15,6 @@ directories = {
files = { files = {
'/etc/nftables.conf': { '/etc/nftables.conf': {
'content_type': 'mako',
'needs': { 'needs': {
'directory:/etc/nftables-rules.d', 'directory:/etc/nftables-rules.d',
}, },
@ -30,7 +29,21 @@ files = {
'svc_systemd:nftables:reload', 'svc_systemd:nftables:reload',
}, },
}, },
}
for ruleset, rules in node.metadata.get('nftables/rules', {}).items():
files[f'/etc/nftables-rules.d/{ruleset}'] = {
'source': 'rules-template',
'content_type': 'mako',
'context': {
'rules': rules,
},
'needed_by': {
'svc_systemd:nftables',
},
'triggers': {
'svc_systemd:nftables:reload',
},
} }
svc_systemd = { svc_systemd = {

View file

@ -9,12 +9,13 @@ defaults = {
'pacman': { 'pacman': {
'packages': { 'packages': {
'nftables': {}, 'nftables': {},
'iptables': { # https://github.com/bundlewrap/bundlewrap/issues/688
'installed': False, # 'iptables': {
'needed_by': { # 'installed': False,
'pkg_pacman:iptables-nft', # 'needed_by': {
}, # 'pkg_pacman:iptables-nft',
}, # },
# },
'iptables-nft': { 'iptables-nft': {
'needed_by': { 'needed_by': {
'pkg_pacman:nftables', 'pkg_pacman:nftables',
@ -34,7 +35,7 @@ if not node.has_bundle('vmhost'):
} }
@metadata_reactor.provides( @metadata_reactor.provides(
'nftables/rules/input/port_rules', 'nftables/rules/99-port_rules',
) )
def port_rules_to_nftables(metadata): def port_rules_to_nftables(metadata):
# Using this, bundles can simply set up port based rules. This # Using this, bundles can simply set up port based rules. This
@ -63,36 +64,33 @@ def port_rules_to_nftables(metadata):
if port != '*': if port != '*':
if ':' in port: if ':' in port:
parts = port.split(':') parts = port.split(':')
port_str = f'dport {{ {parts[0]}-{parts[1]} }} ' port_str = f'{proto} dport {{ {parts[0]}-{parts[1]} }}'
else: else:
port_str = f'dport {port} ' port_str = f'{proto} dport {port}'
prefix = ''
else: else:
port_str = '' port_str = f'meta l4proto {proto}'
prefix = 'meta l4proto '
if target == '*': if target in ('ipv4', 'ipv6'):
ruleset.add(f'{prefix}{proto} {port_str}accept {comment}') version_str = f'meta nfproto {target}'
elif target == 'ipv4': else:
ruleset.add(f'{prefix}{proto} {port_str}ip version 4 accept {comment}') version_str = ''
elif target == 'ipv6':
ruleset.add(f'{prefix}{proto} {port_str}ip6 version 6 accept {comment}') if target in ('*', 'ipv4', 'ipv6'):
ruleset.add(f'inet filter input {version_str} {port_str} accept {comment}')
else: else:
resolved = repo.libs.tools.resolve_identifier(repo, target) resolved = repo.libs.tools.resolve_identifier(repo, target)
for address in resolved['ipv4']: for address in resolved['ipv4']:
ruleset.add(f'{prefix}{proto} {port_str}ip saddr {address} accept {comment}') ruleset.add(f'inet filter input meta nfproto ipv4 {port_str} ip saddr {address} accept {comment}')
for address in resolved['ipv6']: for address in resolved['ipv6']:
ruleset.add(f'{prefix}{proto} {port_str}ip6 saddr {address} accept {comment}') ruleset.add(f'inet filter input meta nfproto ipv6 {port_str} ip6 saddr {address} accept {comment}')
return { return {
'nftables': { 'nftables': {
'rules': { 'rules': {
# order does not matter here. # order does not matter here.
'input': { '99-port_rules': sorted(ruleset),
'port_rules': sorted(ruleset),
},
}, },
}, },
} }

View file

@ -6,14 +6,12 @@ defaults = {
}, },
'nftables': { 'nftables': {
'rules': { 'rules': {
'input': { '10-wide-dhcp6c': [
'wide-dhcp6c': [ 'inet filter input udp dport { 546, 547 } ip6 saddr ff00::/12 accept',
'udp dport { 546, 547 } ip6 saddr ff00::/12 accept', 'inet filter input udp dport { 546, 547 } ip6 saddr fe80::/10 accept',
'udp dport { 546, 547 } ip6 saddr fe80::/10 accept',
], ],
}, },
}, },
},
'icinga2_api': { 'icinga2_api': {
'wide-dhcp6c': { 'wide-dhcp6c': {
'services': { 'services': {

View file

@ -18,16 +18,6 @@ defaults = {
}, },
}, },
}, },
'nftables': {
'rules': {
'forward': {
'wireguard': [
'iif wg0 accept',
'oif wg0 accept',
],
},
},
},
'wireguard': { 'wireguard': {
'privatekey': repo.libs.keys.gen_privkey(repo, f'{node.name} wireguard privatekey'), 'privatekey': repo.libs.keys.gen_privkey(repo, f'{node.name} wireguard privatekey'),
}, },
@ -221,17 +211,20 @@ def interface_ips(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'nftables/rules/nat_postrouting', 'nftables/rules/10-wireguard',
) )
def snat(metadata): def snat(metadata):
if not node.has_bundle('nftables'): if not node.has_bundle('nftables'):
raise DoNotRunAgain raise DoNotRunAgain
rules = set() rules = {
'inet filter forward iif wg0 accept',
'inet filter forward oif wg0 accept',
}
for config in metadata.get('wireguard/peers', {}).values(): for config in metadata.get('wireguard/peers', {}).values():
if 'snat_to' in config: if 'snat_to' in config:
rules.add('ip saddr {} ip daddr != {} snat to {}'.format( rules.add('nat postrouting ip saddr {} ip daddr != {} snat to {}'.format(
config['my_ip'], config['my_ip'],
config['their_ip'], config['their_ip'],
config['snat_to'], config['snat_to'],
@ -240,7 +233,7 @@ def snat(metadata):
return { return {
'nftables': { 'nftables': {
'rules': { 'rules': {
'nat_postrouting': rules, '10-wireguard': sorted(rules),
}, },
}, },
} }

View file

@ -102,22 +102,18 @@ nodes['home.router'] = {
}, },
'nftables': { 'nftables': {
'rules': { 'rules': {
'forward': { '50-router': [
'router': [
# This is a router. Allow forwarding traffic for internal networks. # This is a router. Allow forwarding traffic for internal networks.
'ct state { related, established } accept', 'inet filter forward ct state { related, established } accept',
'iif enp1s0.23 oif ppp0 accept', 'inet filter forward iif enp1s0.23 oif ppp0 accept',
'iif enp1s0.42 accept', 'inet filter forward iif enp1s0.42 accept',
# yaaaaay, IPv6! No NAT! # yaaaaay, IPv6! No NAT!
'ip6 nexthdr ipv6-icmp accept', 'inet filter forward ip6 nexthdr ipv6-icmp accept',
'tcp dport 22 accept', 'inet filter forward tcp dport 22 accept',
'nat prerouting tcp dport 2022 dnat 172.19.138.20:22',
], ],
}, },
'nat_prerouting': {
'tcp dport 2022 dnat 172.19.138.20:22',
},
},
}, },
'nginx': { 'nginx': {
'restrict-to': { 'restrict-to': {

View file

@ -51,6 +51,11 @@ nodes['htz-cloud.influxdb'] = {
}, },
}, },
}, },
#'openssh': {
# 'restrict-to': {
# 'versatel',
# },
#},
'vm': { 'vm': {
'cpu': 1, 'cpu': 1,
'ram': 2, 'ram': 2,

View file

@ -181,14 +181,12 @@ nodes['htz-cloud.miniserver'] = {
}, },
'nftables': { 'nftables': {
'rules': { 'rules': {
'input': { '50-sophie-weechat': [
'sophie-weechat': [ 'inet filter input udp dport { 60000-61000 } accept',
'udp dport { 60000-61000 } accept', 'inet filter input tcp dport 9001 accept',
'tcp dport 9001 accept',
], ],
}, },
}, },
},
'nginx': { 'nginx': {
'vhosts': { 'vhosts': {
'matrix-dimension': { 'matrix-dimension': {

View file

@ -275,14 +275,12 @@ nodes['rx300'] = {
}, },
'nftables': { 'nftables': {
'rules': { 'rules': {
'input': { '50-kunsi-weechat': [
'kunsi-weechat': [ 'inet filter input udp dport { 60000-61000 } accept',
'udp dport { 60000-61000 } accept', 'inet filter input tcp dport 9001 accept',
'tcp dport 9001 accept',
], ],
}, },
}, },
},
'nginx': { 'nginx': {
'security.txt': { 'security.txt': {
'contact': 'mailto:security@kunsmann.eu', 'contact': 'mailto:security@kunsmann.eu',