rework firewall setup

This commit is contained in:
Franzi 2023-09-24 20:59:58 +02:00
parent be62c1270f
commit cd48cf495d
Signed by: kunsi
GPG key ID: 12E3D2136B818350
30 changed files with 145 additions and 122 deletions

View file

@ -88,7 +88,7 @@ def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'179': atomic(sources), '179/tcp': atomic(sources),
}, },
}, },
} }

View file

@ -76,19 +76,19 @@ def import_database_settings_from_postfixadmin(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/143', 'firewall/port_rules',
'firewall/port_rules/993', 'firewall/port_rules',
'firewall/port_rules/4190', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
# imap(s) # imap(s)
'143': atomic(metadata.get('dovecot/restrict-to', {'*'})), '143/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
'993': atomic(metadata.get('dovecot/restrict-to', {'*'})), '993/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
# managesieve # managesieve
'4190': atomic(metadata.get('dovecot/restrict-to', {'*'})), '4190/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
}, },
}, },
} }

View file

@ -66,16 +66,16 @@ def get_static_allocations(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'nftables/rules/10-kea-dhcp-server', 'nftables/input/10-kea-dhcp-server',
) )
def nftables(metadata): def nftables(metadata):
rules = set() rules = set()
for iface in node.metadata.get('kea-dhcp-server/subnets', {}): for iface in node.metadata.get('kea-dhcp-server/subnets', {}):
rules.add(f'inet filter input udp dport {{ 67, 68 }} iif {iface} accept') rules.add(f'udp dport {{ 67, 68 }} iifname {iface} accept')
return { return {
'nftables': { 'nftables': {
'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.
'10-kea-dhcp-server': sorted(rules), '10-kea-dhcp-server': sorted(rules),
}, },

View file

@ -43,15 +43,15 @@ defaults = {
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/8080', 'firewall/port_rules',
'firewall/port_rules/9090', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'8080': atomic(metadata.get('kodi/restrict-to', {'*'})), '8080/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
'9090': atomic(metadata.get('kodi/restrict-to', {'*'})), '9090/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
}, },
}, },
} }

View file

@ -150,13 +150,13 @@ def heap_to_java_opts(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/25565', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'25565': atomic(metadata.get('minecraft/restrict-to', set())), '25565/tcp': atomic(metadata.get('minecraft/restrict-to', set())),
}, },
}, },
} }

View file

@ -33,7 +33,7 @@ def firewall(metadata):
result = {} result = {}
for listener in metadata.get('mosquitto/listeners').keys(): for listener in metadata.get('mosquitto/listeners').keys():
result[listener] = atomic(sources) result[f'{listener}/tcp'] = atomic(sources)
return { return {
'firewall': { 'firewall': {

View file

@ -33,8 +33,8 @@ def firewall(metadata):
ips.add(share_target) ips.add(share_target)
rules = {} rules = {}
for port in ('111', '2049', '1110', '4045', '35295'): # TODO find out if we need more ports for port in ('111', '2049', '1110', '4045', '35295'):
for proto in ('', '/udp'): for proto in ('/tcp', '/udp'):
rules[port + proto] = atomic(ips) rules[port + proto] = atomic(ips)
return { return {

View file

@ -19,6 +19,13 @@ 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(input.items()):
# ${ruleset}
% for rule in rules:
${rule}
% endfor
% endfor
} }
chain output { chain output {
@ -32,15 +39,36 @@ 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(forward.items()):
# ${ruleset}
% for rule in rules:
${rule}
% endfor
% endfor
} }
} }
table nat { table nat {
chain prerouting { chain prerouting {
type nat hook prerouting priority -100 type nat hook prerouting priority -100
% for ruleset, rules in sorted(prerouting.items()):
# ${ruleset}
% for rule in rules:
${rule}
% endfor
% endfor
} }
chain postrouting { chain postrouting {
type nat hook postrouting priority 100 type nat hook postrouting priority 100
% for ruleset, rules in sorted(postrouting.items()):
# ${ruleset}
% for rule in rules:
${rule}
% endfor
% endfor
} }
} }

View file

@ -15,8 +15,12 @@ directories = {
files = { files = {
'/etc/nftables.conf': { '/etc/nftables.conf': {
'needs': { 'content_type': 'mako',
'directory:/etc/nftables-rules.d', 'context': {
'forward': node.metadata.get('nftables/forward', {}),
'input': node.metadata.get('nftables/input', {}),
'postrouting': node.metadata.get('nftables/postrouting', {}),
'prerouting': node.metadata.get('nftables/prerouting', {}),
}, },
'triggers': { 'triggers': {
'svc_systemd:nftables:reload', 'svc_systemd:nftables:reload',
@ -32,21 +36,6 @@ files = {
}, },
} }
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 = {
'nftables': { 'nftables': {
'needs': { 'needs': {

View file

@ -35,7 +35,7 @@ if not node.has_bundle('vmhost'):
} }
@metadata_reactor.provides( @metadata_reactor.provides(
'nftables/rules/99-port_rules', 'nftables/input/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
@ -49,46 +49,47 @@ def port_rules_to_nftables(metadata):
if '/' in portdef: if '/' in portdef:
port, proto = portdef.split('/', 2) port, proto = portdef.split('/', 2)
if proto not in {'udp'}: if proto not in ('tcp', 'udp'):
raise BundleError(f'firewall/port_rules: illegal identifier {portdef} in metadata for {node.name}') raise BundleError(f'firewall/port_rules: illegal identifier {portdef} in metadata for {node.name}')
else: else:
port = portdef port = portdef
proto = 'tcp' proto = None
for target in targets: for target in targets:
if port == '*' and target == '*': if (
raise BundleError('firewall/port_rules: setting both port and target to * is unsupported') (port == '*' and target == '*')
or (target == '*' and proto is None)
or (port != '*' and proto is None)
):
raise BundleError(f'firewall/port_rules: illegal combination of port, target and protocol: "{port}" "{target}" "{proto}"')
comment = f'comment "port_rules {target}"' comment = f'comment "port_rules {target}"'
if port != '*': if port != '*':
if ':' in port: if ':' in port:
parts = port.split(':') parts = port.split(':')
port_str = f'{proto} dport {{ {parts[0]}-{parts[1]} }}' port_str = f'{proto} dport {{ {parts[0]}-{parts[1]} }} '
else: else:
port_str = f'{proto} dport {port}' port_str = f'{proto} dport {port} '
elif proto is not None:
port_str = f'meta l4proto {proto} '
else: else:
port_str = f'meta l4proto {proto}' port_str = ''
if target in ('ipv4', 'ipv6'): if target == '*':
version_str = f'meta nfproto {target}' ruleset.add(f'{port_str}accept {comment}')
else:
version_str = ''
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, linklocal=True) resolved = repo.libs.tools.resolve_identifier(repo, target, linklocal=True)
for address in resolved['ipv4']: for address in resolved['ipv4']:
ruleset.add(f'inet filter input meta nfproto ipv4 {port_str} ip saddr {address} accept {comment}') ruleset.add(f'{port_str}ip saddr {address} accept {comment}')
for address in resolved['ipv6']: for address in resolved['ipv6']:
ruleset.add(f'inet filter input meta nfproto ipv6 {port_str} ip6 saddr {address} accept {comment}') ruleset.add(f'{port_str}ip6 saddr {address} accept {comment}')
return { return {
'nftables': { 'nftables': {
'rules': { 'input': {
# order does not matter here. # order does not matter here.
'99-port_rules': sorted(ruleset), '99-port_rules': sorted(ruleset),
}, },

View file

@ -172,15 +172,15 @@ def monitoring(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/80', 'firewall/port_rules',
'firewall/port_rules/443', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'80': atomic(metadata.get('nginx/restrict-to', {'*'})), '80/tcp': atomic(metadata.get('nginx/restrict-to', {'*'})),
'443': atomic(metadata.get('nginx/restrict-to', {'*'})), '443/tcp': atomic(metadata.get('nginx/restrict-to', {'*'})),
}, },
}, },
} }

View file

@ -10,13 +10,13 @@ defaults = {
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/113', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'113': atomic(metadata.get('oidentd/restrict-to', {'*'})), '113/tcp': atomic(metadata.get('oidentd/restrict-to', {'*'})),
}, },
}, },
} }

View file

@ -16,13 +16,13 @@ defaults = {
} }
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/22', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'22': atomic(metadata.get('openssh/restrict-to', {'*'})), '22/tcp': atomic(metadata.get('openssh/restrict-to', {'*'})),
}, },
}, },
} }

View file

@ -96,10 +96,10 @@ def letsencrypt(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'firewall/port_rules/25', 'firewall/port_rules',
'firewall/port_rules/465', 'firewall/port_rules',
'firewall/port_rules/587', 'firewall/port_rules',
'firewall/port_rules/2525', 'firewall/port_rules',
) )
def firewall(metadata): def firewall(metadata):
if node.has_bundle('postfixadmin'): if node.has_bundle('postfixadmin'):
@ -108,13 +108,13 @@ def firewall(metadata):
default = metadata.get('postfix/mynetworks', set()) default = metadata.get('postfix/mynetworks', set())
rules = { rules = {
'25': atomic(metadata.get('postfix/restrict-to', default)), '25/tcp': atomic(metadata.get('postfix/restrict-to', default)),
'465': atomic(metadata.get('postfix/restrict-to', default)), '465/tcp': atomic(metadata.get('postfix/restrict-to', default)),
} }
if node.has_bundle('postfixadmin'): if node.has_bundle('postfixadmin'):
rules['587'] = atomic(metadata.get('postfix/restrict-to', default)) rules['587/tcp'] = atomic(metadata.get('postfix/restrict-to', default))
rules['2525'] = atomic(metadata.get('postfix/restrict-to', default)) rules['2525/tcp'] = atomic(metadata.get('postfix/restrict-to', default))
return { return {
'firewall': { 'firewall': {

View file

@ -201,9 +201,9 @@ def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'53': atomic(metadata.get('powerdns/restrict-to/dns', {'*'})), '53/tcp': atomic(metadata.get('powerdns/restrict-to/dns', {'*'})),
'53/udp': atomic(metadata.get('powerdns/restrict-to/dns', {'*'})), '53/udp': atomic(metadata.get('powerdns/restrict-to/dns', {'*'})),
'8081': atomic(metadata.get('powerdns/restrict-to/api', set())), '8081/tcp': atomic(metadata.get('powerdns/restrict-to/api', set())),
}, },
}, },
} }

View file

@ -2,7 +2,7 @@
INTERFACE=$1 INTERFACE=$1
echo "add rule nat postrouting oif $INTERFACE masquerade" > /etc/nftables-rules.d/90-pppd echo "add rule nat postrouting oifname $INTERFACE masquerade" > /etc/nftables-rules.d/90-pppd
% for rule in sorted(nftables): % for rule in sorted(nftables):
echo "add rule ${rule}" >> /etc/nftables-rules.d/90-pppd echo "add rule ${rule}" >> /etc/nftables-rules.d/90-pppd
% endfor % endfor

View file

@ -25,7 +25,7 @@ def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'514': atomic(metadata.get('rsyslogd/restrict-to', set())), '514/tcp': atomic(metadata.get('rsyslogd/restrict-to', set())),
'514/udp': atomic(metadata.get('rsyslogd/restrict-to', set())), '514/udp': atomic(metadata.get('rsyslogd/restrict-to', set())),
}, },
}, },

View file

@ -55,9 +55,9 @@ def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
str(metadata.get('transmission/config/peer-port')): atomic({'*'}), f"{metadata.get('transmission/config/peer-port')}/tcp": atomic({'*'}),
str(metadata.get('transmission/config/peer-port')) + '/udp': atomic({'*'}), f"{metadata.get('transmission/config/peer-port')}/udp": atomic({'*'}),
str(metadata.get('transmission/config/rpc-port')): atomic(metadata.get('transmission/restrict-to', {'*'})), f"{metadata.get('transmission/config/rpc-port')}/tcp": atomic(metadata.get('transmission/restrict-to', {'*'})),
}, },
}, },
} }

View file

@ -70,7 +70,7 @@ def firewall(metadata):
return { return {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'53': atomic(metadata.get('unbound/restrict-to', set())), '53/tcp': atomic(metadata.get('unbound/restrict-to', set())),
'53/udp': atomic(metadata.get('unbound/restrict-to', set())), '53/udp': atomic(metadata.get('unbound/restrict-to', set())),
}, },
}, },

View file

@ -20,9 +20,9 @@ defaults = {
}, },
}, },
'nftables': { 'nftables': {
'rules': { 'input': {
'weechat-mosh': { '10-weechat': {
'inet filter input udp dport { 60000-61000 } accept', 'udp dport { 60000-61000 } accept',
}, },
}, },
}, },

View file

@ -5,10 +5,10 @@ defaults = {
}, },
}, },
'nftables': { 'nftables': {
'rules': { 'input': {
'10-wide-dhcp6c': [ '10-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',
], ],
}, },
}, },

View file

@ -264,7 +264,8 @@ def interface_ips(metadata):
@metadata_reactor.provides( @metadata_reactor.provides(
'nftables/rules/10-wireguard', 'nftables/forward/10-wireguard',
'nftables/postrouting/10-wireguard',
) )
def snat(metadata): def snat(metadata):
if not node.has_bundle('nftables') or node.os == 'arch': if not node.has_bundle('nftables') or node.os == 'arch':
@ -272,13 +273,14 @@ def snat(metadata):
snat_ip = metadata.get('wireguard/snat_ip', None) snat_ip = metadata.get('wireguard/snat_ip', None)
rules = set() forward = set()
postrouting = set()
for peer, config in sorted(metadata.get('wireguard/peers', {}).items()): for peer, config in sorted(metadata.get('wireguard/peers', {}).items()):
rules.add(f'inet filter forward iifname wg_{config["iface"]} accept') forward.add(f'iifname wg_{config["iface"]} accept')
rules.add(f'inet filter forward oifname wg_{config["iface"]} accept') forward.add(f'oifname wg_{config["iface"]} accept')
if snat_ip: if snat_ip:
rules.add('nat postrouting ip saddr {} ip daddr != {} snat to {}'.format( postrouting.add('ip saddr {} ip daddr != {} snat to {}'.format(
config['my_ip'], config['my_ip'],
config['their_ip'], config['their_ip'],
snat_ip, snat_ip,
@ -286,8 +288,11 @@ def snat(metadata):
return { return {
'nftables': { 'nftables': {
'rules': { 'forward': {
'10-wireguard': sorted(rules), '10-wireguard': sorted(forward),
},
'postrouting': {
'10-wireguard': sorted(postrouting),
}, },
}, },
} }

View file

@ -41,9 +41,6 @@ groups['linux'] = {
'*': { '*': {
'icinga2', 'icinga2',
}, },
'*/udp': {
'icinga2',
},
}, },
}, },
}, },

View file

@ -67,15 +67,15 @@ nodes['home.nas'] = {
}, },
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
'4679': { # Dell ULNM '4679/tcp': { # Dell ULNM
'172.19.136.0/25', '172.19.136.0/25',
'172.19.138.0/24', '172.19.138.0/24',
}, },
'5060': { # yate SIP '5060/tcp': { # yate SIP
'home.snom-wohnzimmer', 'home.snom-wohnzimmer',
'home.mitel-rfp35', 'home.mitel-rfp35',
}, },
'5061': { # yate SIPS '5061/tcp': { # yate SIPS
'home.snom-wohnzimmer', 'home.snom-wohnzimmer',
'home.mitel-rfp35', 'home.mitel-rfp35',
}, },

View file

@ -77,15 +77,16 @@ nodes['home.router'] = {
#'vars.notification.sms': True #'vars.notification.sms': True
}, },
'nftables': { 'nftables': {
'rules': { 'forward': {
'50-router': [ '50-router': [
# This is a router. Allow forwarding traffic for internal networks. 'ct state { related, established } accept',
'inet filter forward ct state { related, established } accept', 'ip6 nexthdr ipv6-icmp accept',
'tcp dport 22 accept',
# yaaaaay, IPv6! No NAT! ],
'inet filter forward ip6 nexthdr ipv6-icmp accept', },
'inet filter forward tcp dport 22 accept', 'prerouting': {
'nat prerouting tcp dport 2022 dnat 172.19.138.20:22', '50-router': [
'tcp dport 2022 dnat 172.19.138.20:22',
], ],
}, },
}, },

View file

@ -164,10 +164,10 @@ nodes['htz-cloud.miniserver'] = {
'2a01:4f8:0:1::add:9898', '2a01:4f8:0:1::add:9898',
}, },
'nftables': { 'nftables': {
'rules': { 'input': {
'50-sophie-weechat': [ '50-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',
], ],
}, },
}, },

View file

@ -38,14 +38,18 @@ nodes['htz-cloud.wireguard'] = {
}, },
}, },
'nftables': { 'nftables': {
'rules': { 'input': {
'50-router': [ '50-router': [
'inet filter forward ct state { related, established } accept', 'ct state { related, established } accept',
'inet filter forward oif eth0 accept', 'oifname eth0 accept',
'nat postrouting oif eth0 masquerade',
], ],
'wg_special': [ '50-wireguard': [
'inet filter input udp dport 51819 accept', 'udp dport 51819 accept',
],
},
'postrouting': {
'50-router': [
'oifname eth0 masquerade',
], ],
}, },
}, },

View file

@ -41,10 +41,10 @@ nodes['htz-hel.backup-sophie'] = {
'zfs-base': 'tank/backups', 'zfs-base': 'tank/backups',
}, },
'nftables': { 'nftables': {
'rules': { 'input': {
'50-sophie-misc': [ '50-sophie-misc': [
'inet filter input udp dport { 60000-61000 } accept', 'udp dport { 60000-61000 } accept',
'inet filter input tcp dport 5201 accept', 'tcp dport 5201 accept',
], ],
}, },
}, },

View file

@ -34,7 +34,7 @@ nodes['kunsi-p14s'] = {
# '192.168.0.0/16', # '192.168.0.0/16',
#}, #},
# For the occasional file-share using `python -m http.server` # For the occasional file-share using `python -m http.server`
'8000': {'*'}, '8000/tcp': {'*'},
}, },
}, },
'interfaces': { 'interfaces': {
@ -68,7 +68,6 @@ nodes['kunsi-p14s'] = {
'openssh': { 'openssh': {
'restrict-to': { 'restrict-to': {
'rfc1918', 'rfc1918',
'ipv6',
}, },
}, },
'openvpn-client': { 'openvpn-client': {

View file

@ -36,13 +36,13 @@ nodes['kunsi-t470'] = {
'firewall': { 'firewall': {
'port_rules': { 'port_rules': {
# obs websocket thingie - just allow all RFC1918 ips here # obs websocket thingie - just allow all RFC1918 ips here
'4444': { '4444/tcp': {
'10.0.0.0/8', '10.0.0.0/8',
'172.16.0.0/12', '172.16.0.0/12',
'192.168.0.0/16', '192.168.0.0/16',
}, },
# For the occasional file-share using `python -m http.server` # For the occasional file-share using `python -m http.server`
'8000': {'*'}, '8000/tcp': {'*'},
}, },
}, },
'locale': { 'locale': {
@ -73,7 +73,6 @@ nodes['kunsi-t470'] = {
'10.0.0.0/8', '10.0.0.0/8',
'172.16.0.0/12', '172.16.0.0/12',
'192.168.0.0/16', '192.168.0.0/16',
'ipv6',
}, },
}, },
'pacman': { 'pacman': {