diff --git a/README.md b/README.md index 9c5e14e..9acc51b 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ extend: Using iptables.nat ================== -You can use nat for interface. +You can use nat for interface. This is supported for IPv4 alone. IPv6 deployments should not use NAT. ```yaml #Support nat @@ -126,3 +126,30 @@ You can use nat for interface. '192.168.18.0/24': - 10.20.0.2 ``` + +IPv6 Support +============ + +This formula supports IPv6 as long as it is activated with the option: + +``` +firewall: + ipv6: True +``` + +Services and whitelists are supported under the sections `services_ipv6` and `whitelist_ipv6`, as below: + +``` + services_ipv6: + ssh: + block_nomatch: False + ips_allow: + - 2a02:2028:773:d01:10a5:f34f:e7ff:f55b/64 + - 2a02:2028:773:d01:1814:28ef:e91b:70b8/64 + whitelist_ipv6: + networks: + ips_allow: + - 2a02:2028:773:d01:1814:28ef:e91b:70b8/64 +``` + +These sections are only processed if the ipv6 support is activated. \ No newline at end of file diff --git a/iptables/init.sls b/iptables/init.sls index be3a3d0..17dc01b 100644 --- a/iptables/init.sls +++ b/iptables/init.sls @@ -2,170 +2,207 @@ # vim: ft=sls # Firewall management module -{% from "iptables/map.jinja" import firewall with context %} -{% set install = firewall.install %} -{% set strict_mode = firewall.strict %} -{% set global_block_nomatch = firewall.block_nomatch %} -{% set packages = firewall.pkgs %} +{%- from "iptables/map.jinja" import firewall with context %} +{%- set install = firewall.install %} +{%- set strict_mode = firewall.strict %} +{%- set global_block_nomatch = firewall.block_nomatch %} +{%- set packages = firewall.pkgs %} +{%- set ipv4 = 'IPv4' %} +{%- set ipv6 = 'IPv6' %} +{%- set protocols = [ipv4] %} +{%- if firewall.get('ipv6', False) %} +{%- do protocols.append(ipv6) %} +{%- endif %} +{%- set suffixes = {ipv4: '', ipv6: '_ipv6'} %} {%- if firewall.enabled %} - {%- if install %} - # Install required packages for firewalling - iptables_packages: - pkg.installed: - - pkgs: - {%- for pkg in packages %} - - {{pkg}} - {%- endfor %} - {%- endif %} - - {%- if strict_mode %} - # If the firewall is set to strict mode, we'll need to allow some - # that always need access to anything - iptables_allow_localhost: - iptables.append: - - table: filter - - chain: INPUT - - jump: ACCEPT - - source: 127.0.0.1 - - save: True - - # Allow related/established sessions - iptables_allow_established: - iptables.append: - - table: filter - - chain: INPUT - - jump: ACCEPT - - match: conntrack - - ctstate: 'RELATED,ESTABLISHED' - - save: True +{%- if install %} +# Install required packages for firewalling +iptables_packages: + pkg.installed: + - pkgs: +{%- for pkg in packages %} + - {{pkg}} +{%- endfor %} +{%- endif %} - # Set the policy to deny everything unless defined - enable_reject_policy: - iptables.set_policy: - - table: filter - - chain: INPUT - - policy: DROP - - require: - - iptables: iptables_allow_localhost - - iptables: iptables_allow_established - {%- endif %} +{%- if strict_mode %} +# If the firewall is set to strict mode, we'll need to allow some +# that always need access to anything +{%- for protocol in protocols %} +iptables_allow_localhost{{suffixes[protocol]}}: + iptables.append: + - table: filter + - chain: INPUT + - jump: ACCEPT +{%- if protocol == ipv4 %} + - source: 127.0.0.1 +{%- else %} + - source: ::1 + - family: ipv6 +{%- endif %} + - save: True - # Generate ipsets for all services that we have information about - {%- for service_name, service_details in firewall.get('services', {}).items() %} - {% set block_nomatch = service_details.get('block_nomatch', False) %} - {% set interfaces = service_details.get('interfaces','') %} - {% set protos = service_details.get('protos',['tcp']) %} - {% if service_details.get('comment', False) %} - {% set comment = '- comment: ' + service_details.get('comment') %} - {% else %} - {% set comment = '' %} - {% endif %} +# Allow related/established sessions +iptables_allow_established{{suffixes[protocol]}}: + iptables.append: + - table: filter + - chain: INPUT + - jump: ACCEPT + - match: conntrack + - ctstate: 'RELATED,ESTABLISHED' +{%- if protocol == ipv6 %} + - family: ipv6 +{%- endif %} + - save: True - # Allow rules for ips/subnets - {%- for ip in service_details.get('ips_allow', ['0.0.0.0/0']) %} - {%- if interfaces == '' %} - {%- for proto in protos %} - iptables_{{service_name}}_allow_{{ip}}_{{proto}}: - iptables.insert: - - position: 1 - - table: filter - - chain: INPUT - - jump: ACCEPT - - source: {{ ip }} - - dport: {{ service_name }} - - proto: {{ proto }} - - save: True - {{ comment }} - {%- endfor %} - {%- else %} - {%- for interface in interfaces %} - {%- for proto in protos %} - iptables_{{service_name}}_allow_{{ip}}_{{proto}}_{{interface}}: - iptables.insert: - - position: 1 - - table: filter - - chain: INPUT - - jump: ACCEPT - - i: {{ interface }} - - source: {{ ip }} - - dport: {{ service_name }} - - proto: {{ proto }} - - save: True - {{ comment }} - {%- endfor %} - {%- endfor %} - {%- endif %} - {%- endfor %} +# Set the policy to deny everything unless defined +enable_reject_policy{{suffixes[protocol]}}: + iptables.set_policy: + - table: filter + - chain: INPUT + - policy: DROP +{%- if protocol == ipv6 %} + - family: ipv6 +{%- endif %} + - require: + - iptables: iptables_allow_localhost{{suffixes[protocol]}} + - iptables: iptables_allow_established{{suffixes[protocol]}} +{%- endfor %} +{%- endif %} - {%- if not strict_mode and global_block_nomatch or block_nomatch %} - # If strict mode is disabled we may want to block anything else - {%- if interfaces == '' %} - {%- for proto in protos %} - iptables_{{service_name}}_deny_other_{{proto}}: - iptables.append: - - position: last - - table: filter - - chain: INPUT - - jump: REJECT - - dport: {{ service_name }} - - proto: {{ proto }} - - save: True - {{ comment }} - {%- endfor %} - {%- else %} - {%- for interface in interfaces %} - {%- for proto in protos %} - iptables_{{service_name}}_deny_other_{{proto}}_{{interface}}: - iptables.append: - - position: last - - table: filter - - chain: INPUT - - jump: REJECT - - i: {{ interface }} - - dport: {{ service_name }} - - proto: {{ proto }} - - save: True - {{ comment }} - {%- endfor %} - {%- endfor %} - {%- endif %} +# Generate ipsets for all services that we have information about +{%- for protocol in protocols %} +{%- for service_name, service_details in firewall.get('services' + suffixes[protocol], {}).items() %} +{%- set block_nomatch = service_details.get('block_nomatch', False) %} +{%- set interfaces = service_details.get('interfaces','') %} +{%- set protos = service_details.get('protos',['tcp']) %} +{%- if service_details.get('comment', False) %} +{%- set comment = '- comment: ' + service_details.get('comment') %} +{%- else %} +{%- set comment = '' %} +{%- endif %} - {%- endif %} +# Allow rules for ips/subnets +{%- for ip in service_details.get('ips_allow', ['0.0.0.0/0']) %} +{%- if interfaces == '' %} +{%- for proto in protos %} +iptables_{{service_name}}_allow_{{ip}}_{{proto}}{{suffixes[protocol]}}: + iptables.insert: + - position: 1 + - table: filter + - chain: INPUT + - jump: ACCEPT + - source: {{ ip }} + - dport: {{ service_name }} + - proto: {{ proto }} +{%- if protocol == ipv6 %} + - family: ipv6 +{%- endif %} + - save: True + {{ comment }} +{%- endfor %} +{%- else %} +{%- for interface in interfaces %} +{%- for proto in protos %} +iptables_{{service_name}}_allow_{{ip}}_{{proto}}_{{interface}}{{suffixes[protocol]}}: + iptables.insert: + - position: 1 + - table: filter + - chain: INPUT + - jump: ACCEPT + - i: {{ interface }} + - source: {{ ip }} + - dport: {{ service_name }} + - proto: {{ proto }} +{%- if protocol == ipv6 %} + - family: ipv6 +{%- endif %} + - save: True + {{ comment }} +{%- endfor %} +{%- endfor %} +{%- endif %} +{%- endfor %} - {%- endfor %} +{%- if not strict_mode and global_block_nomatch or block_nomatch %} +# If strict mode is disabled we may want to block anything else +{%- if interfaces == '' %} +{%- for proto in protos %} +iptables_{{service_name}}_deny_other_{{proto}}{{suffixes[protocol]}}: + iptables.append: + - position: last + - table: filter + - chain: INPUT + - jump: REJECT + - dport: {{ service_name }} + - proto: {{ proto }} +{%- if protocol == ipv6 %} + - family: ipv6 +{%- endif %} + - save: True + {{ comment }} +{%- endfor %} +{%- else %} +{%- for interface in interfaces %} +{%- for proto in protos %} +iptables_{{service_name}}_deny_other_{{proto}}_{{interface}}{{suffixes[protocol]}}: + iptables.append: + - position: last + - table: filter + - chain: INPUT + - jump: REJECT + - i: {{ interface }} + - dport: {{ service_name }} + - proto: {{ proto }} +{%- if protocol == ipv6 %} + - family: ipv6 +{%- endif %} + - save: True + {{ comment }} +{%- endfor %} +{%- endfor %} +{%- endif %} +{%- endif %} +{%- endfor %} +{%- endfor %} - # Generate rules for NAT - {%- for service_name, service_details in firewall.get('nat', {}).items() %} - {%- for ip_s, ip_ds in service_details.get('rules', {}).items() %} - {%- for ip_d in ip_ds %} - iptables_{{service_name}}_allow_{{ip_s}}_{{ip_d}}: - iptables.append: - - table: nat - - chain: POSTROUTING - - jump: MASQUERADE - - o: {{ service_name }} - - source: {{ ip_s }} - - destination: {{ ip_d }} - - save: True - {%- endfor %} - {%- endfor %} - {%- endfor %} +# Generate rules for NAT +{%- for service_name, service_details in firewall.get('nat', {}).items() %} +{%- for ip_s, ip_ds in service_details.get('rules', {}).items() %} +{%- for ip_d in ip_ds %} +iptables_{{service_name}}_allow_{{ip_s}}_{{ip_d}}: + iptables.append: + - table: nat + - chain: POSTROUTING + - jump: MASQUERADE + - o: {{ service_name }} + - source: {{ ip_s }} + - destination: {{ ip_d }} + - save: True +{%- endfor %} +{%- endfor %} +{%- endfor %} - # Generate rules for whitelisting IP classes - {%- for service_name, service_details in firewall.get('whitelist', {}).items() %} - {%- for ip in service_details.get('ips_allow', []) %} - iptables_{{service_name}}_allow_{{ip}}: - iptables.append: - - table: filter - - chain: INPUT - - jump: ACCEPT - - source: {{ ip }} - - save: True - {%- endfor %} - {%- endfor %} +# Generate rules for whitelisting IP classes +{%- for protocol in protocols %} +{%- for service_name, service_details in firewall.get('whitelist' + suffixes[protocol], {}).items() %} +{%- for ip in service_details.get('ips_allow', []) %} +iptables_{{service_name}}_allow_{{ip}}{{suffixes[protocol]}}: + iptables.append: + - table: filter + - chain: INPUT + - jump: ACCEPT + - source: {{ ip }} + {%- if protocol == ipv6 %} + - family: ipv6 + {%- endif %} + - save: True +{%- endfor %} +{%- endfor %} +{%- endfor %} -{% else %} # Firewall is disabled by default +{%- else %} # Firewall is disabled by default firewall_disabled: test.show_notification: - name: Firewall is disabled by default diff --git a/pillar.example b/pillar.example index 31e1bd0..d9aeb25 100644 --- a/pillar.example +++ b/pillar.example @@ -2,6 +2,7 @@ firewall: install: True enabled: True strict: True + ipv6: True services: ssh: block_nomatch: True @@ -22,12 +23,36 @@ firewall: interfaces: - eth0 + services_ipv6: + ssh: + block_nomatch: False + ips_allow: + - 2a02:2028:773:d01:10a5:f34f:e7ff:f55b/64 + - 2a02:2028:773:d01:1814:28ef:e91b:70b8/64 + http: + block_nomatch: False + protos: + - udp + - tcp + snmp: + block_nomatch: False + protos: + - udp + - tcp + interfaces: + - eth0 + whitelist: networks: ips_allow: - 10.0.0.0/8 - #Suppport nat + whitelist_ipv6: + networks: + ips_allow: + - 2a02:2028:773:d01:1814:28ef:e91b:70b8/64 + + #Support nat (ipv4 only) # iptables -t nat -A POSTROUTING -o eth0 -s 192.168.18.0/24 -d 10.20.0.2 -j MASQUERADE # iptables -t nat -A POSTROUTING -o eth0 -s 192.168.18.0/24 -d 172.31.0.2 -j MASQUERADE nat: