From da45c7c4090531c806b672f8357729caaba95175 Mon Sep 17 00:00:00 2001 From: NaeiKinDus Date: Sat, 6 Jan 2024 00:00:00 +0000 Subject: [PATCH] refactor(security): reworked firewall configuration and added support for DNS, HTTP and ICMP rules; added autoconf for resolv.conf to match FW rules --- .../molecule/default/platform_vars.yml | 7 +++++ .../roles/common/defaults/main.yml | 1 + .../roles/common/tasks/main.yml | 31 +++++++++++++++++++ .../common/templates/system/resolv.conf.j2 | 10 ++++++ .../roles/security/defaults/main.yml | 7 ++--- .../roles/security/handlers/main.yml | 2 +- .../roles/security/tasks/firewall.yml | 9 +++++- .../templates/system/debian/nftables.conf.j2 | 9 ++++-- .../system/nftables.d/egress_http.nft.j2 | 5 +++ .../system/nftables.d/egress_icmp.nft.j2 | 14 +++++++++ .../system/nftables.d/ingress_icmp.nft.j2 | 14 +++++++++ .../templates/system/nftables/01-nat.table.j2 | 8 ++--- .../system/nftables/02-mangle.table.j2 | 29 ++++++++--------- .../system/nftables/03-filter.table.j2 | 16 ++++++---- inventory/group_vars/all.yml | 2 -- inventory/group_vars/all/vars.yml | 9 ++++++ inventory/host_vars/actinium/vars.yml | 15 +++++++++ inventory/host_vars/lithium/vars.yml | 2 ++ inventory/host_vars/unobtainium/vars.yml | 8 +++++ inventory/vault.yml.dist | 3 -- playbooks/external.yml | 8 ++--- playbooks/internal.yml | 8 ++--- 22 files changed, 169 insertions(+), 48 deletions(-) create mode 100644 collections/ansible_collections/nullified/infrastructure/roles/common/templates/system/resolv.conf.j2 create mode 100644 collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_http.nft.j2 create mode 100644 collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_icmp.nft.j2 create mode 100644 collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/ingress_icmp.nft.j2 delete mode 100644 inventory/group_vars/all.yml create mode 100644 inventory/group_vars/all/vars.yml create mode 100644 inventory/host_vars/actinium/vars.yml delete mode 100644 inventory/vault.yml.dist diff --git a/collections/ansible_collections/nullified/infrastructure/extensions/molecule/default/platform_vars.yml b/collections/ansible_collections/nullified/infrastructure/extensions/molecule/default/platform_vars.yml index 2dac9e4..0843e4b 100644 --- a/collections/ansible_collections/nullified/infrastructure/extensions/molecule/default/platform_vars.yml +++ b/collections/ansible_collections/nullified/infrastructure/extensions/molecule/default/platform_vars.yml @@ -1 +1,8 @@ custom_base_user_account: 'vagrant' +custom_security: + firewall: + mangle: + drop_privatenets: false +custom_common: + configure_resolv_conf: true +ip_dualstack: false diff --git a/collections/ansible_collections/nullified/infrastructure/roles/common/defaults/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/common/defaults/main.yml index cbda208..6ef011c 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/common/defaults/main.yml +++ b/collections/ansible_collections/nullified/infrastructure/roles/common/defaults/main.yml @@ -14,6 +14,7 @@ common: install_fonts: false sysctl: {} user_account: "{{ custom_base_user_account | default('root') }}" + configure_resolv_conf: false custom_common: {} recursive_combine: true diff --git a/collections/ansible_collections/nullified/infrastructure/roles/common/tasks/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/common/tasks/main.yml index 05438a7..81bed5d 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/common/tasks/main.yml +++ b/collections/ansible_collections/nullified/infrastructure/roles/common/tasks/main.yml @@ -4,6 +4,37 @@ common: "{{ common | combine(custom_common, recursive=recursive_combine) }}" changed_when: false +- name: '[system] setup DNS server' + block: + - name: disable resolv.conf updates from dhclient + ansible.builtin.copy: + dest: /etc/dhcp/dhclient-enter-hooks.d/nodnsupdate + content: | + #!/bin/sh + make_resolv_conf(){ + : + } + owner: root + group: root + mode: '0755' + - name: update resolv.conf + ansible.builtin.template: + src: ../templates/system/resolv.conf.j2 + dest: /etc/resolv.conf + mode: '0644' + owner: root + group: root + become: true + when: common.configure_resolv_conf is truthy + +- name: '[system] re-allow DHCP client to setup DNS resolvers' + become: true + ansible.builtin.file: + path: /etc/dhcp/dhclient-enter-hooks.d/nodnsupdate + state: absent + failed_when: false + when : common.configure_resolv_conf is falsy + - name: '[apt] verify components of default sources' become: true block: diff --git a/collections/ansible_collections/nullified/infrastructure/roles/common/templates/system/resolv.conf.j2 b/collections/ansible_collections/nullified/infrastructure/roles/common/templates/system/resolv.conf.j2 new file mode 100644 index 0000000..e21ecaa --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/common/templates/system/resolv.conf.j2 @@ -0,0 +1,10 @@ +{% if dns[dns.type].dns4 is defined and dns[dns.type].dns4 | length > 0 -%} +{% for server in dns[dns.type].dns4 -%} +nameserver {{ server }} +{% endfor %} +{% endif %} +{% if ip_dualstack | default(false) and dns[dns.type].dns6 is defined and dns[dns.type].dns6 | length > 0 -%} +{% for server in dns[dns.type].dns6 -%} +nameserver {{ server }} +{% endfor %} +{% endif %} diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/defaults/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/security/defaults/main.yml index 0fbc085..623c296 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/defaults/main.yml +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/defaults/main.yml @@ -11,23 +11,20 @@ security: policy: prerouting: accept input: accept - postrouting: accept output: accept - additional_rules: "" + postrouting: accept mangle: drop_privatenets: true policy: prerouting: accept - postrouting: accept output: accept forward: drop - additional_rules: "" + postrouting: accept filter: policy: input: drop output: drop forward: drop - additional_rules: "" custom_security: {} recursive_combine: true diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/handlers/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/security/handlers/main.yml index 09d63bd..55320d3 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/handlers/main.yml +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/handlers/main.yml @@ -1,7 +1,7 @@ --- - name: '[firewall] restart service' become: true - systemd_service: + ansible.builtin.systemd_service: name: nftables.service enabled: true state: restarted diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/tasks/firewall.yml b/collections/ansible_collections/nullified/infrastructure/roles/security/tasks/firewall.yml index 8f4bd8f..3451c0d 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/tasks/firewall.yml +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/tasks/firewall.yml @@ -45,7 +45,7 @@ dest: /etc/nftables.conf mode: '0700' - - name: common firewall rules + - name: base tables definition ansible.builtin.template: src: "../templates/system/nftables/{{ item }}.table.j2" dest: "/etc/nftables.d/{{ item }}.table" @@ -56,3 +56,10 @@ - 01-nat - 02-mangle - 03-filter + + - name: common firewall rules + ansible.builtin.template: + src: "{{ item }}" + dest: "/etc/nftables.d/{{ (item.split('/') | last)[:-3] }}" + mode: '0600' + loop: "{{ q('fileglob', '../templates/system/nftables.d/*.j2') }}" diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/debian/nftables.conf.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/debian/nftables.conf.j2 index c0a241e..c08297d 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/debian/nftables.conf.j2 +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/debian/nftables.conf.j2 @@ -4,8 +4,12 @@ flush ruleset define ansible_controller_ip = {{ supervisor_ip | default('127.0.0.1', true) }} define ansible_controller_ip6 = {{ supervisor_ip6 | default('fe80::', true) }} -define dns_server = {{ dns_server | default('9.9.9.9', true) }} -define dns_server6 = {{ dns_server | default('2620:fe::fe', true) }} +define dns_servers = { + {{ dns[dns.type].dns4 | default(["9.9.9.9", "149.112.112.112"], true) | join(", ") | wordwrap(40, wrapstring="\n\t") }} +} +define dns_servers6 = { + {{ dns[dns.type].dns6 | default(["2620:fe::fe", "2620:fe::9"], true) | join(", ") | wordwrap(40, wrapstring="\n\t") }} +} define private_nets = { 10.0.0.0/8, 100.64.0.0/10, 172.16.0.0/12, 192.0.0.0/24, 192.168.0.0/16, 198.18.0.0/15 @@ -15,6 +19,7 @@ define reserved_nets = { 192.88.99.0/24, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 233.252.0.0/24, 240.0.0.0/4 } +define ssh_localport = {{ ssh_localport | default(22, true) }} include "/etc/nftables.d/01-nat.table" include "/etc/nftables.d/02-mangle.table" diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_http.nft.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_http.nft.j2 new file mode 100644 index 0000000..25e3f19 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_http.nft.j2 @@ -0,0 +1,5 @@ +table inet filter { + chain output { + meta nfproto { ipv4, ipv6 } tcp dport { http, https } accept + } +} diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_icmp.nft.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_icmp.nft.j2 new file mode 100644 index 0000000..d680b03 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/egress_icmp.nft.j2 @@ -0,0 +1,14 @@ +table inet filter { + chain output { + icmp type { + echo-reply, destination-unreachable, source-quench, redirect, echo-request, + time-exceeded, parameter-problem, timestamp-request, timestamp-reply, info-request, + info-reply, address-mask-request, address-mask-reply, router-advertisement, router-solicitation + } accept + icmpv6 type { + destination-unreachable, packet-too-big, time-exceeded, echo-request, echo-reply, mld-listener-query, + mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, + nd-neighbor-advert, parameter-problem, mld2-listener-report + } accept + } +} diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/ingress_icmp.nft.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/ingress_icmp.nft.j2 new file mode 100644 index 0000000..cc2fd00 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables.d/ingress_icmp.nft.j2 @@ -0,0 +1,14 @@ +table inet filter { + chain input { + icmp type { + echo-reply, destination-unreachable, source-quench, redirect, echo-request, + time-exceeded, parameter-problem, timestamp-request, timestamp-reply, info-request, + info-reply, address-mask-request, address-mask-reply, router-advertisement, router-solicitation + } accept + icmpv6 type { + destination-unreachable, packet-too-big, time-exceeded, echo-request, echo-reply, mld-listener-query, + mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, + nd-neighbor-advert, parameter-problem, mld2-listener-report + } accept + } +} diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/01-nat.table.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/01-nat.table.j2 index a037fb9..20ff37a 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/01-nat.table.j2 +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/01-nat.table.j2 @@ -8,11 +8,11 @@ table inet nat { type nat hook input priority 100; policy {{ firewall.nat.policy.input }}; } - chain postrouting { - type nat hook postrouting priority 100; policy {{ firewall.nat.policy.postrouting }}; - } - chain output { type nat hook output priority -100; policy {{ firewall.nat.policy.output }}; } + + chain postrouting { + type nat hook postrouting priority 100; policy {{ firewall.nat.policy.postrouting }}; + } } diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/02-mangle.table.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/02-mangle.table.j2 index 026a1b0..3f05bfb 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/02-mangle.table.j2 +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/02-mangle.table.j2 @@ -2,15 +2,10 @@ table inet mangle { chain prerouting { type filter hook prerouting priority -150; policy {{ firewall.mangle.policy.prerouting }}; - ip saddr $ansible_controller_ip tcp dport 22 accept - ip6 saddr $ansible_controller_ip6 tcp dport 22 accept - ip daddr $ansible_controller_ip tcp sport 22 accept - ip6 daddr $ansible_controller_ip6 tcp sport 22 accept - ip protocol icmp accept ip frag-off & 0x1fff != 0 counter drop ct state invalid counter drop - tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop + tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop @@ -26,6 +21,12 @@ table inet mangle { tcp flags & (fin|syn|rst|psh|ack|urg) == fin|psh|urg counter drop tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|psh|urg counter drop tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|ack|urg counter drop + + ip saddr $ansible_controller_ip tcp dport $ssh_localport accept + ip6 saddr $ansible_controller_ip6 tcp dport $ssh_localport accept + ip daddr $ansible_controller_ip tcp sport $ssh_localport accept + ip6 daddr $ansible_controller_ip6 tcp sport $ssh_localport accept + {% if firewall.mangle.drop_privatenets -%} ip saddr $private_nets counter drop {% endif -%} @@ -33,14 +34,6 @@ table inet mangle { iifname != "lo" ip saddr 127.0.0.0/8 counter drop } - chain postrouting { - type filter hook postrouting priority -150; policy {{ firewall.mangle.policy.postrouting }}; - ip saddr $ansible_controller_ip tcp dport 22 accept - ip6 saddr $ansible_controller_ip6 tcp dport 22 accept - ip daddr $ansible_controller_ip tcp sport 22 accept - ip6 daddr $ansible_controller_ip6 tcp sport 22 accept - } - chain output { type route hook output priority -150; policy {{ firewall.mangle.policy.output }}; } @@ -48,4 +41,12 @@ table inet mangle { chain forward { type filter hook forward priority -150; policy {{ firewall.mangle.policy.forward }}; } + + chain postrouting { + type filter hook postrouting priority -150; policy {{ firewall.mangle.policy.postrouting }}; + ip saddr $ansible_controller_ip tcp dport $ssh_localport accept + ip6 saddr $ansible_controller_ip6 tcp dport $ssh_localport accept + ip daddr $ansible_controller_ip tcp sport $ssh_localport accept + ip6 daddr $ansible_controller_ip6 tcp sport $ssh_localport accept + } } diff --git a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/03-filter.table.j2 b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/03-filter.table.j2 index 7196b73..ab2c3f3 100644 --- a/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/03-filter.table.j2 +++ b/collections/ansible_collections/nullified/infrastructure/roles/security/templates/system/nftables/03-filter.table.j2 @@ -2,22 +2,26 @@ table inet filter { chain input { type filter hook input priority 0; policy {{ firewall.filter.policy.input }}; - ip saddr $ansible_controller_ip tcp dport 22 accept - ip6 saddr $ansible_controller_ip6 tcp dport 22 accept + ip saddr $ansible_controller_ip tcp dport $ssh_localport accept + ip6 saddr $ansible_controller_ip6 tcp dport $ssh_localport accept iifname "lo" counter accept ct state related,established counter accept - tcp dport 22 limit rate 10/hour burst 5 packets counter accept + tcp dport $ssh_localport limit rate 10/hour burst 5 packets counter accept } chain output { type filter hook output priority 0; policy {{ firewall.filter.policy.output }}; - ip daddr $ansible_controller_ip tcp sport 22 accept - ip6 daddr $ansible_controller_ip6 tcp sport 22 accept + ip daddr $ansible_controller_ip tcp sport $ssh_localport accept + ip6 daddr $ansible_controller_ip6 tcp sport $ssh_localport accept oifname "lo" counter accept ct state related,established counter accept - tcp sport 22 counter accept + tcp sport $ssh_localport counter accept + + # Allow DNS queries using UDP, DoT and DoH + ip daddr $dns_servers meta l4proto { tcp, udp } th dport { 53, 443, 953 } accept + ip6 daddr $dns_servers6 meta l4proto { tcp, udp } th dport { 53, 443, 953 } accept } chain forward { diff --git a/inventory/group_vars/all.yml b/inventory/group_vars/all.yml deleted file mode 100644 index c70a274..0000000 --- a/inventory/group_vars/all.yml +++ /dev/null @@ -1,2 +0,0 @@ -custom_base_user_account: '{{ vault_custom_base_user_account }}' -custom_github_token: '{{ vault_custom_github_token }}' diff --git a/inventory/group_vars/all/vars.yml b/inventory/group_vars/all/vars.yml new file mode 100644 index 0000000..97e276d --- /dev/null +++ b/inventory/group_vars/all/vars.yml @@ -0,0 +1,9 @@ +custom_base_user_account: '{{ vault_custom_base_user_account }}' +custom_github_token: '{{ vault_custom_github_token }}' +dns: + type: "dot" + udp: '{{ vault_groups.defaults.udp }}' + dot: '{{ vault_groups.defaults.dot }}' + doh: '{{ vault_groups.defaults.doh }}' +network: "external" +ip_dualstack: true diff --git a/inventory/host_vars/actinium/vars.yml b/inventory/host_vars/actinium/vars.yml new file mode 100644 index 0000000..7f2f649 --- /dev/null +++ b/inventory/host_vars/actinium/vars.yml @@ -0,0 +1,15 @@ +ansible_become_password: "{{ vault_root_pass }}" +ansible_host: "{{ vault_ansible_host }}" +ansible_user: "{{ vault_ssh_user }}" + +custom_security: + firewall: + mangle: + drop_privatenets: false + policy: + forward: accept +dns: + type: "udp" + udp: "{{ vault_groups.network.internal }}" +network: "internal" +ip_dualstack: false diff --git a/inventory/host_vars/lithium/vars.yml b/inventory/host_vars/lithium/vars.yml index e7c8a02..1fdb193 100644 --- a/inventory/host_vars/lithium/vars.yml +++ b/inventory/host_vars/lithium/vars.yml @@ -1,3 +1,5 @@ ansible_become_password: "{{ vault_root_pass }}" ansible_host: "{{ vault_ansible_host }}" ansible_user: "{{ vault_ssh_user }}" +network: "external" +ip_dualstack: true diff --git a/inventory/host_vars/unobtainium/vars.yml b/inventory/host_vars/unobtainium/vars.yml index 6d3920b..b8c13b4 100644 --- a/inventory/host_vars/unobtainium/vars.yml +++ b/inventory/host_vars/unobtainium/vars.yml @@ -32,3 +32,11 @@ custom_security: drop_privatenets: false policy: forward: accept + +dns: + type: "udp" + udp: + dns4: "{{ vault_groups.network.internal.dns4 }}" + dns6: "{{ vault_groups.network.internal.dns6 }}" +network: "internal" +ip_dualstack: false diff --git a/inventory/vault.yml.dist b/inventory/vault.yml.dist deleted file mode 100644 index 32f12d1..0000000 --- a/inventory/vault.yml.dist +++ /dev/null @@ -1,3 +0,0 @@ -vault_custom_base_user_account: "" -vault_custom_github_token: "" -vault_custom_dockerhub_password: "" diff --git a/playbooks/external.yml b/playbooks/external.yml index 255f0a9..2c4684a 100644 --- a/playbooks/external.yml +++ b/playbooks/external.yml @@ -2,15 +2,13 @@ - name: setup external infrastructure hosts: external gather_facts: false - vars_files: ../inventory/vault.yml tasks: - - include_vars: ../inventory/vault.yml - - name: include common role - ansible.builtin.include_role: - name: nullified.infrastructure.common - name: include security role ansible.builtin.include_role: name: nullified.infrastructure.security + - name: include common role + ansible.builtin.include_role: + name: nullified.infrastructure.common - name: setup servers hosts: external:&server diff --git a/playbooks/internal.yml b/playbooks/internal.yml index dd66eaf..23315f4 100644 --- a/playbooks/internal.yml +++ b/playbooks/internal.yml @@ -2,15 +2,13 @@ - name: setup internal infrastructure hosts: internal gather_facts: false - vars_files: ../inventory/vault.yml tasks: - - include_vars: ../inventory/vault.yml - - name: include common role - ansible.builtin.include_role: - name: nullified.infrastructure.common - name: include security role ansible.builtin.include_role: name: nullified.infrastructure.security + - name: include common role + ansible.builtin.include_role: + name: nullified.infrastructure.common - name: setup servers hosts: internal:&server