From 592d3a630a18fd89871278ff745e9410a1c61832 Mon Sep 17 00:00:00 2001 From: Jeltz Date: Wed, 10 Mar 2021 03:16:51 +0100 Subject: [PATCH] Create role for nftables router --- host_vars/vpn-ovh-ng.auro.re.yml | 2 + roles/nftables_router/handlers/main.yml | 7 + roles/nftables_router/tasks/main.yml | 38 ++++ .../templates/nftables.conf.j2 | 5 + .../templates/nftables.d/10-vars.conf.j2 | 55 ++++++ .../templates/nftables.d/20-blacklist.conf.j2 | 31 ++++ .../templates/nftables.d/30-rp-filter.conf.j2 | 20 +++ .../templates/nftables.d/40-signup.conf.j2 | 48 +++++ .../templates/nftables.d/50-filter.conf.j2 | 164 ++++++++++++++++++ .../templates/nftables.d/60-nat.conf.j2 | 24 +++ 10 files changed, 394 insertions(+) create mode 100644 roles/nftables_router/handlers/main.yml create mode 100644 roles/nftables_router/tasks/main.yml create mode 100644 roles/nftables_router/templates/nftables.conf.j2 create mode 100644 roles/nftables_router/templates/nftables.d/10-vars.conf.j2 create mode 100644 roles/nftables_router/templates/nftables.d/20-blacklist.conf.j2 create mode 100644 roles/nftables_router/templates/nftables.d/30-rp-filter.conf.j2 create mode 100644 roles/nftables_router/templates/nftables.d/40-signup.conf.j2 create mode 100644 roles/nftables_router/templates/nftables.d/50-filter.conf.j2 create mode 100644 roles/nftables_router/templates/nftables.d/60-nat.conf.j2 diff --git a/host_vars/vpn-ovh-ng.auro.re.yml b/host_vars/vpn-ovh-ng.auro.re.yml index 2c290b6..6ab60dd 100644 --- a/host_vars/vpn-ovh-ng.auro.re.yml +++ b/host_vars/vpn-ovh-ng.auro.re.yml @@ -26,6 +26,8 @@ bird_router_prefsrc: 10.132.0.254 bird_ospf_ifaces: ens19: stub: true + ens20: + stub: true gs: type: pointopoint cost: 2000 diff --git a/roles/nftables_router/handlers/main.yml b/roles/nftables_router/handlers/main.yml new file mode 100644 index 0000000..0629211 --- /dev/null +++ b/roles/nftables_router/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Reload nftables + become: true + systemd: + name: nftables.service + state: reloaded +... diff --git a/roles/nftables_router/tasks/main.yml b/roles/nftables_router/tasks/main.yml new file mode 100644 index 0000000..ac00d92 --- /dev/null +++ b/roles/nftables_router/tasks/main.yml @@ -0,0 +1,38 @@ +--- +- name: Install nftables + become: true + apt: + name: nftables + state: latest + +- name: Create nftables.d directory + become: true + file: + path: /etc/nftables.d + state: directory + owner: root + group: root + mode: u=rwx,g=rx,o= + +- name: Configure nftables + become: true + template: + src: "{{ item }}.j2" + dest: "/etc/{{ item }}" + loop: + - nftables.d/10-vars.conf + - nftables.d/20-blacklist.conf + - nftables.d/30-rp-filter.conf + - nftables.d/40-signup.conf + - nftables.d/50-filter.conf + - nftables.d/60-nat.conf + - nftables.conf + notify: Reload nftables + +- name: Enable and start nftables + become: true + systemd: + name: nftables.service + state: started + enabled: true +... diff --git a/roles/nftables_router/templates/nftables.conf.j2 b/roles/nftables_router/templates/nftables.conf.j2 new file mode 100644 index 0000000..c778ac9 --- /dev/null +++ b/roles/nftables_router/templates/nftables.conf.j2 @@ -0,0 +1,5 @@ +{{ ansible_managed | comment }} + +flush ruleset + +include "/etc/nftables.d/*.conf" diff --git a/roles/nftables_router/templates/nftables.d/10-vars.conf.j2 b/roles/nftables_router/templates/nftables.d/10-vars.conf.j2 new file mode 100644 index 0000000..aa02fbe --- /dev/null +++ b/roles/nftables_router/templates/nftables.d/10-vars.conf.j2 @@ -0,0 +1,55 @@ +{{ ansible_managed | comment }} + +## Interconnexion + +# Réseaux d'interconnexion +define interco_v4 = { 192.168.0.0/31, 192.168.0.2/31, 10.129.0.0/16 } +define interco_v6 = { 2a09:6840:129::0/48 } + + +## Administration + +# Réseaux d'administration +define adm_v4 = { 10.128.0.0/16, 10.133.0.0/16 } +define adm_v6 = { 2a09:6840:128::0/48, 2a09:6840:133::0/48 } + +# Serveurs de centralisation des journaux +define syslog_adm_v4 = { 10.128.0.51 } +define syslog_adm_v6 = { 2a09:6840:128::251 } + +# Adresses des bastions autorisés +define bastion_v4 = { 10.128.0.224, 10.133.0.250 } +define bastion_v6 = { 2a09:6840:133::250 } + + +## Services + +# Réseaux de services privés +define svc_v4 = { 10.132.0.0/16 } +define svc_v6 = { 2a09:6840:132::0/48 } + + +## Adhérents + +# Réseaux des adhérents +define member_v4 = { 10.50.0.0/16 } +define member_v6 = { 2a09:6840:50::0/48 } + +# Sous-réseau d'inscription des adhérents +define signup_v4 = { 10.50.0.0/16 } +define signup_v6 = { 2a09:6840:50::0/48 } + +# Hôtes déclencheurs d'accès à Internet pour inscription +define signup_trigger_v4 = { 1.1.1.1 } +define signup_trigger_v6 = { 2606:4700:4700::1111 } + + +## NAT + +# Interface sur laquelle appliquer le NAT +define wan_iface = "ens18" + +define member_priv_v4 = { 10.50.0.0/16 } +define member_nat_v4 = 92.222.211.198 + +define any_nat_v4 = 92.222.211.198 diff --git a/roles/nftables_router/templates/nftables.d/20-blacklist.conf.j2 b/roles/nftables_router/templates/nftables.d/20-blacklist.conf.j2 new file mode 100644 index 0000000..6c6a5cf --- /dev/null +++ b/roles/nftables_router/templates/nftables.d/20-blacklist.conf.j2 @@ -0,0 +1,31 @@ +{{ ansible_managed | comment }} + +table inet blacklist { + + set blacklist_v4 { + type ipv4_addr + flags interval + } + + set blacklist_v6 { + type ipv6_addr + flags interval + } + + # Compteur des paquets ignorés car les adresses étaient en liste noire + counter blacklist {} + + # Cette chaîne est appliquée très tôt (avant le conntrack entre autres) + # afin de limiter autant que possible l'impact des hôtes en liste noire + # (notamment en cas d'attaque par déni de service) + chain filter { + type filter hook prerouting priority -310 + policy accept + + # On ne journalise pas pour limiter la charge sur les serveurs de + # journalisation + ip saddr @blacklist_v4 counter name blacklist drop + ip6 saddr @blacklist_v6 counter name blacklist drop + } + +} diff --git a/roles/nftables_router/templates/nftables.d/30-rp-filter.conf.j2 b/roles/nftables_router/templates/nftables.d/30-rp-filter.conf.j2 new file mode 100644 index 0000000..c55ea64 --- /dev/null +++ b/roles/nftables_router/templates/nftables.d/30-rp-filter.conf.j2 @@ -0,0 +1,20 @@ +{{ ansible_managed | comment }} + +# Simule le comportement de rp_filter=1, mais avec support d'IPv6 (ce qui +# n'est pas le cas de l'implémentation du noyau) +# +# https://wiki.nftables.org/wiki-nftables/index.php/Routing_information +# Le "eq 0" n'est pas très joli, mais ça semble être la façon +# "normale" de le faire +# Voir : https://netdevconf.info/1.2/slides/oct6/08_nft_netdev12_florian.pdf +table inet reverse_path_filter { + + chain filter { + type filter hook prerouting priority -300 + policy accept + + fib saddr . iif oif eq 0 \ + log prefix "rp-filter" group 0 counter drop + } + +} diff --git a/roles/nftables_router/templates/nftables.d/40-signup.conf.j2 b/roles/nftables_router/templates/nftables.d/40-signup.conf.j2 new file mode 100644 index 0000000..e2e067d --- /dev/null +++ b/roles/nftables_router/templates/nftables.d/40-signup.conf.j2 @@ -0,0 +1,48 @@ +{{ ansible_managed | comment }} + +table inet signup { + + set triggered { + type ether_addr + timeout 24h + } + + set allowed { + type ether_addr + timeout 30m + } + + chain trigger { + log prefix "signup-trigger" group 0 + add @triggered { ether saddr } + add @allowed { ether saddr } + } + + chain filter { + # Si l'adresse MAC est temporairement autorisée, on ne bloque pas + ether saddr @allowed return + + # Si l'adresse n'est pas autorisée (cf. règle précédente) mais qu'elle + # a accédé récemment à un déclencheur, cela signifie qu'elle a déjà + # « consommé son crédit », donc on bloque + ether saddr @triggered drop + + # Si la machine tente de se connecter à un des hôtes déclencheurs, + # on enregistre son adresse MAC et on laisse passer la connexion + ip daddr $signup_trigger_v4 goto trigger + ip6 daddr $signup_trigger_v6 goto trigger + + # La machine a tenté de se connecter vers une destination qui ne + # déclenche pas l'accès à Internet, donc on bloque + drop + } + + chain forward { + type filter hook forward priority -10 + policy accept + + ip saddr $signup_v4 goto filter + ip6 saddr $signup_v6 goto filter + } + +} diff --git a/roles/nftables_router/templates/nftables.d/50-filter.conf.j2 b/roles/nftables_router/templates/nftables.d/50-filter.conf.j2 new file mode 100644 index 0000000..63e5ebc --- /dev/null +++ b/roles/nftables_router/templates/nftables.d/50-filter.conf.j2 @@ -0,0 +1,164 @@ +{{ ansible_managed | comment }} + +table inet filter { + + chain conntrack { + ct state invalid counter drop + ct state { established, related } counter accept + } + + chain input_from_anywhere { + # C'est pas gentil de bloquer ICMP(v6), alors on le fait pas + ip protocol icmp counter accept + ip6 nexthdr icmpv6 counter accept + + # Wireguard + udp dport { 5412, 5413 } counter accept + + # Temporaire + tcp dport 22 counter accept + } + + chain input_from_interco { + # Il faut n'accepter que le multicast OSPF et des trucs + # comme ça + counter accept + } + + chain input_from_member { + log prefix "in-from-member" group 0 + } + + chain input_from_svc { + log prefix "in-from-svc" group 0 + } + + chain input_from_adm { + log prefix "in-from-adm" group 0 + + tcp dport 22 counter accept + } + + chain input { + type filter hook input priority 0 + policy drop + + iif lo accept + + jump conntrack + + jump input_from_anywhere + + ip saddr $interco_v4 goto input_from_interco + ip6 saddr $interco_v6 goto input_from_interco + + ip saddr $member_v4 goto input_from_member + ip6 saddr $member_v6 goto input_from_member + + ip saddr $svc_v4 goto input_from_svc + ip6 saddr $svc_v6 goto input_from_svc + + ip saddr $adm_v4 goto input_from_adm + ip6 saddr $adm_v6 goto input_from_adm + } + + chain output { + type filter hook output priority 0 + policy accept + } + + chain forward_to_interco { + ip saddr $interco_v4 accept + ip6 saddr $interco_v6 accept + } + + chain forward_to_member_re2o_ports { + # TODO + } + + chain forward_to_member { + # Les adhérents peuvent communiquer entre eux + ip saddr $member_v4 accept + ip6 saddr $member_v6 accept + + # L'administration n'a pas accès à l'extérieur + ip saddr $adm_v4 drop + ip6 saddr $adm_v6 drop + + # Les ouvertures de ports sont générées par re2o + goto forward_to_member_re2o_ports + } + + chain forward_to_svc { + } + + chain forward_to_adm { + log prefix "fwd-to-adm" group 0 + + # Seules les machines du réseau d'administration peuvent accéder au + # réseau d'administration + ip saddr != $adm_v4 drop + ip6 saddr != $adm_v6 drop + + # Les bastions ont accès à toute l'administration + ip saddr $bastion_v4 accept + ip6 saddr $bastion_v6 accept + + # Tous les serveurs ont accès au collecteur de logs + ip daddr $syslog_adm_v4 tcp dport 20514 accept + ip daddr $syslog_adm_v4 udp dport 514 accept + ip6 daddr $syslog_adm_v6 tcp dport 20514 accept + ip6 daddr $syslog_adm_v6 udp dport 514 accept + + # ntp + apt + dns + } + + chain forward_to_inet { + log prefix "fwd-to-inet" group 0 + + # On évite certains problèmes de spam + ip saddr $member_v4 udp dport 25 drop + ip6 saddr $member_v6 udp dport 25 drop + + # Les adhérents ont accès à internet + ip saddr $member_v4 accept + ip6 saddr $member_v6 accept + + # Les réseaux de services ont accès à Internet + ip saddr $svc_v4 accept + ip6 saddr $svc_v6 accept + } + + # Remarque : on utilise 'drop' et pas 'reject' pour conntrackd + chain forward { + type filter hook forward priority 0 + policy drop + + iif lo accept + + jump conntrack + + # http://lists.netfilter.org/pipermail/netfilter-buglog/2017-August/003868.html + #ip daddr vmap { + # $interco_v4 : goto forward_to_interco, + # $member_v4 : goto forward_to_member, + # $svc_v4 : goto forward_to_svc, + # $adm_v4 : goto forward_to_adm, + #} + + ip daddr $interco_v4 goto forward_to_interco + ip6 daddr $interco_v6 goto forward_to_interco + + ip daddr $member_v4 goto forward_to_member + ip6 daddr $member_v6 goto forward_to_member + + ip daddr $svc_v4 goto forward_to_svc + ip6 daddr $svc_v6 goto forward_to_svc + + ip daddr $adm_v4 goto forward_to_adm + ip6 daddr $adm_v6 goto forward_to_adm + + goto forward_to_inet + } + +} diff --git a/roles/nftables_router/templates/nftables.d/60-nat.conf.j2 b/roles/nftables_router/templates/nftables.d/60-nat.conf.j2 new file mode 100644 index 0000000..6e4fe12 --- /dev/null +++ b/roles/nftables_router/templates/nftables.d/60-nat.conf.j2 @@ -0,0 +1,24 @@ +{{ ansible_managed | comment }} + +table ip nat { + + chain prerouting { + type nat hook prerouting priority -100 + policy accept + } + + chain snat_to_wan { + log prefix "snat-to-wan" group 0 + + ip saddr $member_priv_v4 snat $member_nat_v4 persistent + snat $any_nat_v4 persistent + } + + chain postrouting { + type nat hook prerouting priority 100 + policy accept + + # oifname $wan_iface goto snat_to_wan + } + +}