diff --git a/bundles/dhcpd/metadata.py b/bundles/dhcpd/metadata.py index 896d27d..bc0b253 100644 --- a/bundles/dhcpd/metadata.py +++ b/bundles/dhcpd/metadata.py @@ -56,13 +56,13 @@ def get_listen_interfaces(metadata): ) def iptables(metadata): rules = set() - for _, subnet in node.metadata.get('dhcpd/subnets', {}).items(): + for subnet in node.metadata.get('dhcpd/subnets', {}).values(): rules.add('iptables -A INPUT -i {} -p udp --dport 67:68 -j ACCEPT'.format(subnet['interface'])) return { 'iptables': { 'bundle_rules': { - # iptables bundle relies on this being a list. + # can't use port_rules here. We're generating interface based rules here. 'dhcpd': sorted(list(rules)), }, } diff --git a/bundles/icinga2/metadata.py b/bundles/icinga2/metadata.py index 6434909..265da64 100644 --- a/bundles/icinga2/metadata.py +++ b/bundles/icinga2/metadata.py @@ -1,6 +1,8 @@ from json import loads from os.path import join +from bundlewrap.metadata import atomic + defaults = { 'apt': { 'repos': { @@ -100,25 +102,13 @@ def add_users_from_json(metadata): @metadata_reactor.provides( - 'iptables/bundle_rules/icinga2', + 'iptables/port_rules/5665', ) def iptables(metadata): - identifiers = metadata.get('icinga2/restrict-to', set()) - rules = set() - - if identifiers: - for identifier in sorted(identifiers): - resolved = repo.libs.tools.resolve_identifier(repo, identifier) - - for address in resolved['ipv4']: - rules.add(f'iptables -A INPUT -p tcp -s {address} --dport 5665 -j ACCEPT') - else: - rules.add('iptables -A INPUT -p tcp --dport 5665 -j ACCEPT') - return { 'iptables': { - 'bundle_rules': { - 'icinga2': list(sorted(rules)), + 'port_rules': { + '5665': atomic(metadata.get('icinga2/restrict-to', set())), }, }, } diff --git a/bundles/iptables/metadata.py b/bundles/iptables/metadata.py new file mode 100644 index 0000000..40c5539 --- /dev/null +++ b/bundles/iptables/metadata.py @@ -0,0 +1,51 @@ +@metadata_reactor.provides( + 'iptables/bundle_rules/iptables', +) +def port_rules_to_iptables(metadata): + # Using this, bundles can simply set up port based rules. This + # reactor will then take care of converting those rules to actual + # iptables 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('iptables/port_rules', {}).items(): + if '/' in portdef: + port, proto = portdef.split('/', 2) + + if proto not in {'udp'}: + raise Exception(f'iptables/port_rules: illegal identifier {portdef} in metadata for {node.name}') + else: + port = portdef + proto = 'tcp' + + for target in targets: + if port == '*' and target == '*': + raise Exception('iptables/port_rules: setting both port and target to * is unsupported') + + comment = f'-m comment --comment "iptables port_rules {target}"' + + if port != '*': + port_str = f'--dport {port}' + else: + port_str = '' + + if target == '*': + ruleset.add(f'iptables_both -A INPUT -p {proto} {port_str} {comment} -j ACCEPT') + else: + resolved = repo.libs.tools.resolve_identifier(repo, target) + + for address in resolved['ipv4']: + ruleset.add(f'iptables -A INPUT -p {proto} -s {address} {port_str} {comment} -j ACCEPT') + + for address in resolved['ipv6']: + ruleset.add(f'ip6tables -A INPUT -p {proto} -s {address} {port_str} {comment} -j ACCEPT') + + return { + 'iptables': { + 'bundle_rules': { + # order does not matter here. + 'iptables': list(sorted(ruleset)), + }, + }, + } diff --git a/bundles/netdata/metadata.py b/bundles/netdata/metadata.py index 2446657..d51d2ff 100644 --- a/bundles/netdata/metadata.py +++ b/bundles/netdata/metadata.py @@ -1,3 +1,5 @@ +from bundlewrap.metadata import atomic + defaults = { 'apt': { 'packages': { @@ -17,25 +19,13 @@ defaults = { @metadata_reactor.provides( - 'iptables/bundle_rules/netdata', + 'iptables/port_rules/19999', ) def iptables(metadata): - identifiers = metadata.get('netdata/restrict-to', set()) - rules = set() - - if identifiers: - for identifier in sorted(identifiers): - resolved = repo.libs.tools.resolve_identifier(repo, identifier) - - for address in resolved['ipv4']: - rules.add(f'iptables -A INPUT -p tcp -s {address} --dport 19999 -j ACCEPT') - else: - rules.add('iptables -A INPUT -p tcp --dport 19999 -j ACCEPT') - return { 'iptables': { - 'bundle_rules': { - 'netdata': list(sorted(rules)), + 'port_rules': { + '19999': atomic(metadata.get('netdata/restrict-to', set('*'))), }, }, } diff --git a/bundles/nginx/metadata.py b/bundles/nginx/metadata.py index 0b7e73b..2402abc 100644 --- a/bundles/nginx/metadata.py +++ b/bundles/nginx/metadata.py @@ -1,3 +1,5 @@ +from bundlewrap.metadata import atomic + defaults = { 'apt': { 'repos': { @@ -150,31 +152,15 @@ def monitoring(metadata): @metadata_reactor.provides( - 'iptables/bundle_rules/nginx', + 'iptables/port_rules/80', + 'iptables/port_rules/443', ) def iptables(metadata): - identifiers = metadata.get('nginx/restrict-to', set()) - rules = set() - - if identifiers: - for identifier in sorted(identifiers): - resolved = repo.libs.tools.resolve_identifier(repo, identifier) - - for address in resolved['ipv4']: - rules.add(f'iptables -A INPUT -p tcp -s {address} --dport 80 -j ACCEPT') - rules.add(f'iptables -A INPUT -p tcp -s {address} --dport 443 -j ACCEPT') - - for address in resolved['ipv6']: - rules.add(f'ip6tables -A INPUT -p tcp -s {address} --dport 80 -j ACCEPT') - rules.add(f'ip6tables -A INPUT -p tcp -s {address} --dport 443 -j ACCEPT') - else: - rules.add('iptables_both -A INPUT -p tcp --dport 80 -j ACCEPT') - rules.add('iptables_both -A INPUT -p tcp --dport 443 -j ACCEPT') - return { 'iptables': { - 'bundle_rules': { - 'nginx': list(sorted(rules)), + 'port_rules': { + '80': atomic(metadata.get('nginx/restrict-to', set('*'))), + '443': atomic(metadata.get('nginx/restrict-to', set('*'))), }, }, } diff --git a/bundles/transmission/metadata.py b/bundles/transmission/metadata.py index 10d44ea..dfe8d68 100644 --- a/bundles/transmission/metadata.py +++ b/bundles/transmission/metadata.py @@ -1,3 +1,5 @@ +from bundlewrap.metadata import atomic + defaults = { 'apt': { 'packages': { @@ -34,37 +36,15 @@ defaults = { @metadata_reactor.provides( - 'iptables/bundle_rules/transmission', + 'iptables/port_rules', ) def iptables(metadata): - identifiers = metadata.get('transmission/restrict-to', set()) - rules = set() - - rules.add('iptables_both -A INPUT -p udp --dport {} -j ACCEPT'.format( - metadata.get('transmission/config/peer-port'), - )) - rules.add('iptables_both -A INPUT -p tcp --dport {} -j ACCEPT'.format( - metadata.get('transmission/config/peer-port'), - )) - - if identifiers: - for identifier in sorted(identifiers): - resolved = repo.libs.tools.resolve_identifier(repo, identifier) - - for address in resolved['ipv4']: - rules.add('iptables -A INPUT -p tcp -s {} --dport {} -j ACCEPT'.format( - address, - metadata.get('transmission/config/rpc-port'), - )) - else: - rules.add('iptables -A INPUT -p tcp --dport {} -j ACCEPT'.format( - metadata.get('transmission/config/rpc-port'), - )) - return { 'iptables': { - 'bundle_rules': { - 'transmission': list(sorted(rules)), + 'port_rules': { + str(metadata.get('transmission/config/peer-port')): set('*'), + str(metadata.get('transmission/config/peer-port')) + '/udp': set('*'), + str(metadata.get('transmission/config/rpc-port')): atomic(metadata.get('transmission/restrict-to', set('*'))), }, }, } diff --git a/bundles/unbound/metadata.py b/bundles/unbound/metadata.py index c417911..a950996 100644 --- a/bundles/unbound/metadata.py +++ b/bundles/unbound/metadata.py @@ -1,3 +1,5 @@ +from bundlewrap.metadata import atomic + defaults = { 'apt': { 'packages': { @@ -38,28 +40,14 @@ def cpu_cores_to_config_values(metadata): @metadata_reactor.provides( - 'iptables/bundle_rules/unbound', + 'iptables/port_rules', ) def iptables(metadata): - identifiers = metadata.get('unbound/restrict-to', set()) - rules = set() - - if identifiers: - for identifier in sorted(identifiers): - resolved = repo.libs.tools.resolve_identifier(repo, identifier) - - for address in resolved['ipv4']: - rules.add(f'iptables -A INPUT -p tcp -s {address} --dport 53 -j ACCEPT') - rules.add(f'iptables -A INPUT -p udp -s {address} --dport 53 -j ACCEPT') - - for address in resolved['ipv6']: - rules.add(f'ip6tables -A INPUT -p tcp -s {address} --dport 53 -j ACCEPT') - rules.add(f'ip6tables -A INPUT -p udp -s {address} --dport 53 -j ACCEPT') - return { 'iptables': { - 'bundle_rules': { - 'unbound': list(sorted(rules)), + 'port_rules': { + '53': atomic(metadata.get('unbound/restrict-to', set())), + '53/udp': atomic(metadata.get('unbound/restrict-to', set())), }, }, } diff --git a/groups/os.py b/groups/os.py index 729a741..cce9359 100644 --- a/groups/os.py +++ b/groups/os.py @@ -39,6 +39,16 @@ groups['linux'] = { 'backup-client': { 'server': 'franzi-home.kunbox.net:2022', }, + 'iptables': { + 'port_rules': { + '*': { + 'ovh.icinga2', + }, + '*/udp': { + 'ovh.icinga2', + }, + }, + }, }, 'pip_command': 'pip3', }