07/12/2019
201912070800 ipfw shell
ipfw: modern FreeBSD
C'est pas moi qui le dit:
$ man ipfw ... RULE BODY ... The rule body has the following format:
[proto from src to dst] [options]
The first part (proto from src to dst) is for backward compatibility with earlier versions of FreeBSD. In modern FreeBSD any match pattern (including MAC headers, IP protocols, addresses and ports) can be specified in the options section.
Mes recherches de fichiers de règles basées sur la syntaxe [options] ne m'ayant pas donné satisfaction, j'ai décidé de dégainer mon $EDITOR. Mais avant de parler code, parlons contexte et objectifs:
- une machine avec 1 interface réseau
- ssh toujours actif, même en cas de rechargement du service
- syntaxe "options"
- ipv4 et ipv6
- debug facile
Configuration
Je n'utilise pas le fichier /etc/rc.conf, je lui préfère le répertoire /etc/rc.conf.d. Mon script de règles sera donc /etc/rc.conf.d/rc.firewall et mes variables seront réparties dans des fichiers placés dans /etc/rc.conf.d/ipfw/.
Version initiale
Je commence par ces quelques lignes:
$ cat -n /etc/rc.conf.d/rc.firewall
1 set -e
2
3 . /etc/rc.subr
4 load_rc_config ipfw
5
6 set -u
7
8 [ $# -eq 1 ] && [ $1 = "-d" ] && firewall_debug=YES
9
10 fwcmd=/sbin/ipfw
11 case ${firewall_quiet:-} in
12 [Yy][Ee][Ss]) fwcmd="$fwcmd -q";;
13 esac
14
15 case ${firewall_debug:-NO} in
16 [Yy][Ee][Ss]) fwcmd="echo $fwcmd";;
17 *) firewall_debug='NO';;
18 esac
19
20 $fwcmd -f flush
- 1: gstion des erreurs
- 3-4: chargement de la configuration du service ipfw
- 6: gestions des variables non définies (incompatible avec le chargement de configuration de service)
- 8: mode debug
- 11-13: mode verbeux
- 15-18: mode debug + mode trace (ligne 16)
- 20: suppression des règles courantes
On peut déjà utiliser le script:
$ /bin/sh /etc/rc.conf.d/rc.firewall -d /sbin/ipfw -f flush
Version minimale
$ cat -n /etc/rc.conf.d/rc.firewall
1 set -e
2
3 set_31() {
4 [ ${firewall_debug} = 'NO' -a $($fwcmd set 31 list | wc -l) -gt 1 ] && return
5
6 $add set 31 check-state
7 $add set 31 pass { dst-port 22 or src-port 22 }
8 }
9
10 . /etc/rc.subr
11 load_rc_config ipfw
12
13 set -u
14
15 [ $# -eq 1 ] && [ $1 = "-d" ] && firewall_debug=YES
16
17 fwcmd=/sbin/ipfw
18 case ${firewall_quiet:-} in
19 [Yy][Ee][Ss]) fwcmd="$fwcmd -q";;
20 esac
21
22 case ${firewall_debug:-NO} in
23 [Yy][Ee][Ss]) fwcmd="echo $fwcmd";;
24 *) firewall_debug='NO';;
25 esac
26
27 add="$fwcmd add"
28
29 set_31
30 $fwcmd -f flush
31
32 $add pass via lo0
33 $add pass out keep-state
Concernant l'utilisation du set 31:
$ man ipfw
...
Set 31 is special in that it cannot be disabled, and rules in set
31 are not deleted by the ipfw flush command (but you can delete
them with the ipfw delete set 31 command). Set 31 is also used
for the default rule.
Il ne faut *SURTOUT PAS* que le trafic "ssh" fasse l'objet de règles dynamiques car elles sont supprimées lors d'un /sbin/ipfw -f flush.
- 4: si je ne suis pas en mode debug et que des règles sont déjà présentes dans le set 31 je ne fais rien
- 6: vérifications des règles dynamiques
- 7: j'autorise le ssh entrant/sortant sans utiliser de règles dynamiques
- 29: mise en place des règles du set 31
- 32: je laisse circuler le trafic de l'interface lo0
- 33: je laisse tout sortir en utilisant des règles dynamiques
Je peux maintenant me connecter sur la machine distante et y faire des sudo service ipfw restart ou des /sbin/ipfw -f flush sans perdre la connexion.
SSH restreint
Je ne suis pas du genre à laisser mon port ssh ouvert aux 4 vents mais je peux aussi avoir des gros doigts. Comme mon set 31 n'est défini qu'une et une seule fois, je peux faire un maximum de tests et je n'hésite pas à jouer avec le mode trace (/bin/sh /etc/rc.conf.d/rc.firewall -d).
3 set_31() {
4 [ ${firewall_debug} = 'NO' -a $($fwcmd set 31 list | wc -l) -gt 1 ] && return
5
6 $add set 31 check-state
7 if [ -z "${firewall_ssh6:-}" -a -z "${firewall_ssh4:-}" ]; then
8 $add set 31 pass { dst-port 22 or src-port 22 }
9 else
10 $add set 31 pass src-port 22 out
11 [ -n "${firewall_ssh6:-}" ] && $add set 31 pass dst-port 22 in src-ip6 ${firewall_ssh6} || :
12 [ -n "${firewall_ssh4:-}" ] && $add set 31 pass dst-port 22 in src-ip ${firewall_ssh4} || :
13 fi
14 }
...
40 $add pass { proto ipv6-icmp or proto icmp }
41 $add deny log in
- 7-8: si mes variables ne sont pas définies j'ouvre aux 4 vents
- 10: un paquet avec un port source à 22 en sortie ne peut venir que de ma machine donc je laisse passer
- 11-12: si une variable est définie alors laisser rentrer le trafic à destination du port 22 si l'adresse source correspond à la variable
- 40: je laisse passer l'icmp (qu'il soit ipv6 ou ipv4)
- 41: je loggue tout ce que je bloque en entrée
Au final
$ sudo ipfw -d -S show
00100 0 0 set 31 check-state :default
00200 3780 1509406 set 31 allow src-port 22 out
00300 3675 341057 set 31 allow dst-port 22 in src-ip6 2001:db8::/64
00400 0 0 set 31 allow dst-port 22 in src-ip 192.168.99.0/24
00500 1169 97093 set 0 allow out keep-state :default
00600 0 0 set 0 allow via lo0
00700 60 4332 set 0 allow { proto ipv6-icmp or proto icmp }
00800 2 656 set 0 deny log in
65535 0 0 set 31 deny ip from any to any
Il reste à voir les tables, le nat et 2/3 trucs auxquels je n'ai pas encore pensé.
Commentaires: https://github.com/bsdsx/blog_posts/issues/1
![Validate my Atom 1.0 feed [Valid Atom 1.0]](/valid-atom.png)
![Validate my RSS feed [Valid RSS]](/valid-rss-rogers.png)