21/11/2020
202011211200 ipfw shell
ipfw: modern FreeBSD (reloaded)
Après plusieurs mois de production, petit retour sur mon script de mur de feu.
Rappels
Ma configuration générale se trouve dans /etc/rc.conf.d et je n'ai pas de /etc/rc.conf. La configuration du mur de feu se trouve donc dans le répertoire /etc/rc.conf.d/ipfw. Jouer à distance avec un mur de feu peut vite devenir périlleux: une typo, une mauvaise règle et patatra le mur s'écroule, emportant avec lui la connexion ssh. Pour éviter ces désagréments:
- pas de règle dynamique pour le ssh
- uiliser le set 31
Plus d'explication dans mon premier billet où on trouvera aussi le pourquoi du modern FreeBSD. Attention, ce script ne convient pas à un hôte de type routeur.
Le script
Après quelques évolutions, j'arrive à ce résultat http://download.bsdsx.fr/rc.firewall:
$ cat -n /etc/rc.conf.d/rc.firewall
1 set -e
2
3 table_create() {
4 $fwcmd table $1 create type ${2:-addr} ${3:-}
5 }
6
7 table_load_addr() {
8 local table=$1
9 local files=""
10 local addrs=""
11
12 for arg in $@; do
13 case $arg in
14 /*)
15 [ -f $arg ] || continue
16 files="$files $arg"
17 ;;
18 [0-9a-fA-F]*)
19 addrs="$addrs $arg";;
20 esac
21 done
22 (echo $addrs; cat ${files:-/dev/null}) | sed -E -n -e 's/#.*//' -e 's/([[:xdigit:]:\.\/]+)/\1 0/gp' | tr ',' "\n" | sort --unique --ignore-leading-blanks | xargs $fwcmd table $table atomic add
23 }
24
25 set_31() {
26 [ ${firewall_debug} = 'NO' -a $($fwcmd set 31 list | wc -l) -gt 1 ] && return
27
28 table_create t_pouilleux
29 table_create t_ssh
30
31 table_load_addr t_ssh ${firewall_ssh6:-} ${firewall_ssh4:-} ${firewall_t_ssh:-}
32 table_load_addr t_pouilleux ${firewall_t_pouilleux:-}
33
34 $add set 31 check-state :KS
35 if $fwcmd table t_ssh info | grep -q 'items: 0'; then
36 $add set 31 deny in lookup src-ip t_pouilleux
37 $add set 31 pass { dst-port ssh or src-port ssh }
38 else
39 $add set 31 pass src-port ssh
40 $add set 31 pass in dst-port ssh lookup src-ip t_ssh
41 $add set 31 deny in dst-port ssh
42 $add set 31 deny in lookup src-ip t_pouilleux
43 fi
44 $add set 31 pass { proto ipv6-icmp or proto icmp }
45 }
46 . /etc/rc.subr
47 load_rc_config ipfw
48
49 set -u
50
51 [ $# -eq 1 ] && [ $1 = "-d" ] && firewall_debug=YES
52
53 fwcmd=/sbin/ipfw
54 case ${firewall_quiet:-} in
55 [Yy][Ee][Ss]) fwcmd="$fwcmd -q";;
56 esac
57
58 case ${firewall_debug:-NO} in
59 [Yy][Ee][Ss]) fwcmd="echo $fwcmd";;
60 *) firewall_debug='NO';;
61 esac
62
63 add="$fwcmd add"
64
65 set_31
66
67 KS="keep-state :KS"
68
69 $fwcmd -f flush
70
71 $add pass via lo*
72
73 [ -f $0.$(hostname) ] && . $0.$(hostname)
74
75 $add pass out $KS // after NAT
76 case ${firewall_logging:-} in
77 [Yy][Ee][Ss])
78 for ports in ${firewall_nologports:-}; do
79 $add deny in dst-port $ports
80 done
81 ;;
82 esac
83 $add deny log in
84
85 # vim: set sw=4 sts=4 ts=4 ft=sh:
- 7-23: cette fonction me permet de charger une table de type addr à partir de variables et/ou de fichiers, les listes pouvant utiliser l'espace et/ou la virgule en tant que séparateur
- 25-45: définition du set 31 qui doit me permettre de garder ma connexion ssh en toute circonstance. Si le chargement de la table a échoué (ligne 35) alors l'accès ssh n'est pas restreint
- 51-61: debug / verbose
- 67: je précise le nom du flow des règles dynamiques, en prévision du nat
- 71: les interfaces loopback. A l'écriture de ce billet, je me demande si sa place ne serait pas dans le set 31 (au moins pour lo0, je verrais plus tard comment utiliser les jails et le nat sur lo1)
- 73: les règles de l'hôte. Utiliser le nom d'hôte me permet de centraliser/diffuser/copier sans risque ma configuration
- 75: le traffic sortant initié par l'hôte. Le commentaire a son importance: les futures règles de nat devront se placer avant celle-ci
- 76-82: traffic exempt de log uniquement en cas d'utilisation de syslog (pour éviter de le saturer)
Avec ce script et sans configuration je ne peux pas perdre l'accès ssh mais ce service est ouvert à tous les pouilleux, rabouins, peignes-cul et autres pénibles du nain ternet.
La configuration de base
$ cat -n /etc/rc.conf.d/ipfw/ipfw 1 firewall_enable="YES" 2 firewall_script="/etc/rc.conf.d/rc.firewall" 3 4 # logging == syslogd, logif == tcpdump 5 #firewall_logging="YES" 6 firewall_logif="YES" 7 8 ip6_dead="2001:db8:dead::/48" 9 ip6_beef="2001:db8:beef::/48" 10 ip6_cafe="2001:db8:cafe:cafe::/64" 11 12 ip4_maison="192.168.42.0/24 192.168.84.0/24, 10.10.0.0/16" 13 14 firewall_ssh6="$ip6_dead, $ip6_beef $ip6_cafe" 15 firewall_ssh4="$ip4_maison"
Pour tester et vérifier (ne pas hésiter à faire volontairement des typos dans les adresses et observer le résultat):
$ /bin/sh /etc/rc.conf.d/rc.firewall -d
/sbin/ipfw table t_pouilleux create type addr
/sbin/ipfw table t_ssh create type addr
/sbin/ipfw table t_ssh atomic add 10.10.0.0/16 0 2001:db8:beef::/48 0 2001:db8:cafe:cafe::/64 0 192.168.42.0/24 0 192.168.84.0/24 0 2001:db8:dead::/48 0
/sbin/ipfw add set 31 check-state :KS
/sbin/ipfw add set 31 pass src-port ssh
/sbin/ipfw add set 31 pass in dst-port ssh lookup src-ip t_ssh
/sbin/ipfw add set 31 deny in dst-port ssh
/sbin/ipfw add set 31 deny in lookup src-ip t_pouilleux
/sbin/ipfw add set 31 pass { proto ipv6-icmp or proto icmp }
/sbin/ipfw -f flush
/sbin/ipfw add pass via lo*
/sbin/ipfw add pass out keep-state :KS // after NAT
/sbin/ipfw add deny log in
Une configuration xen (non finalisée)
$ cat /etc/rc.conf.d/rc.firewall.xen.bsdsx.fr
table_create t_unbound addr or-flush && table_load_addr t_unbound ${firewall_unbound6:-} ${firewall_unbound4:-} ${firewall_t_unbound:-}
$add pass in dst-port domain lookup src-ip t_unbound $KS
$add pass in dst-port http $KS
$add pass in src-port bootpc via xnb*
$add pass in dst-port ntp,syslog,546 via xnb*
$add pass in src-port bootps via vlan22
Cette machine fournit un service dns à accès restreint, un service http non restreint et laisse passer du traffic en provenance/destination des domU (j'y reviendrais certainement dans un autre billet).
A venir
J'ai abandonné l'idée de mur de feu entièrement pilotable à coup de variables, le code nécessaire pour définir un service était beaucoup trop volumineux. L'utilisation d'un script local à l'hôte me parait plus simple. La prochaine évolution concernera le nat et des règles qui, je l'espère, sortiront des sentiers battus.
Commentaires: https://github.com/bsdsx/blog_posts/issues/6
![Validate my Atom 1.0 feed [Valid Atom 1.0]](/valid-atom.png)
![Validate my RSS feed [Valid RSS]](/valid-rss-rogers.png)