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