ぼくの考えた最強のFreeBSD ぱけっとふぃるたー (pf.conf)
こんにちは、無能です。
今回はわたしの直近にがちゃがちゃしたパケットフィルターを紹介したいとおもいます。
現在の設定
こうなっています。
Packet Filterの場合は
NAT, rdr等の変換系のルールは先にマッチしたものが適応される
pass, block等のフィルタルールは 最後にマッチしたもの が適応される
という仕様になっています。
この場合、読みやすさからフィルタールールに関しては全て quick にしてその時点でマッチしたルールが適応されるように全て設定してます。
それぞれコメントに残せそうなものは残しておきます。
set skip on lo
set block-policy drop
set optimization conservative
set state-policy if-bound
set ruleset-optimization basic
wanint="vtnet0"
# WireGuard Configuration
wg_ifs = "{ wg0, wg1 }"
wg_net = "10.1.0.0/24"
wg_ports="{51820}"
table <wg_clients> const { 10.1.0.2, 10.1.0.4, 10.1.0.22 }
#### -- Scrub Rules -- ####
scrub in on $wanint all random-id max-mss 1360
scrub out on $wanint all random-id max-mss 1360
scrub in all
scrub out all
#### -- NAT Rules -- ####
# WireGuard Clients NAT
nat on $wanint inet from <wg_clients> to any -> ($wanint)
block all
#### -- Fail2Ban Rules -- ####
anchor "f2b/*"
pass out quick keep state
#### -- UDP Rules -- ####
pass in quick on $wg_ifs from $wg_net to any keep state
pass in quick on $wanint proto udp from any to ($wanint) port $wg_ports keep state
# HTTP/3 Protocol
pass in quick on $wanint proto udp to ($wanint) port 443 keep state
#### -- TCP Rules -- ####
# Mail Protocols
pass in quick proto tcp from any to ($wanint) port {25, 465, 993, 995} synproxy state
# HTTP
pass in quick on $wanint proto tcp to ($wanint) port {80, 443} modulate stateset block-policy drop
ルールにマッチしないものは全てパケットを drop します。return の場合はTCPの場合は RST を返して相手に通知して閉じます。
UDPの場合はそもそも TCP Connection 自体はもちろん無いので、 ICMP port unreachable を返すだけです。
set optimization conservative
最適化ルールでドキュメント上では保守的な設定とされています。aggressive の場合、優先的にコネクションを期限切れとしますが WireGuard のような常時接続状況を維持したいような環境だと新規パケットが来てないだけでコネクションが切られることになります。
また、satelite なんてもあってロマンありますね。
high-latency A high-latency environment (such as a satellite connec- tion). satellite Alias for high-latency.
set state-policy if-bound
state を維持してそれを記憶してパケットを通すことで不整合を防ぎます。pfctl -ssの結果が state であり例えば以下のような構成の場合
クライアント ←→ wg ←→ サーバ
state に「どのインターフェースで作られたか」という情報が紐付くため、同じインターフェースを通った通信のみを有効とすることで不整合を防ぐことになります。
普通の floating の場合は
入口: wg0
出口: vtnet0
戻り: vtnet0でもwg0でもOK
但し、 state-policy if-bound の場合は
入口: wg0
出口: vtnet0
戻り: 必ずwg0じゃないとNG
set ruleset-optimization basic
ルールの最適化をよしなにやってくれます。
公式ドキュメントから
remove duplicate rules
remove rules that are a subset of another rule
combine multiple rules into a table when advanta-geous
re-order the rules to improve evaluation perfor-mance
日本語の場合:
重複しているルールを削除する
他のルールに包含されているルールを削除する
複数のルールを、必要に応じてテーブルにまとめる
評価性能を向上させるためにルールの順序を並べ替える
つまりは雑に書いた pf のルールでもきれいにまとめてくれるということなのでこれを有効にしておくだけでそれぞれで読みやすいような pf ルールを書いてもよしなにルールを整理してくれる神機能。
変数定義
WANインターフェース名指定
wanint="vtnet0"WireGurad上の変数定義とテーブルの定義
wg_ifs = "{ wg0, wg1 }"
wg_net = "10.1.0.0/24"
wg_ports="{51820}"
table <wg_clients> const { 10.1.0.2, 10.1.0.4, 10.1.0.22 }Scrub Rules
この場合、WANからきたパケットも出ていくパケットもMSS値を指定all ramdom-id に関しては OS 自体がパケットから特定されるのを防ぐためらしいが sudo tcpdump -n -v -i $interface で GNU/Linux の場合は確認した限りはデフォルトで有効になっていそう
04:15:18.538048 IP (tos 0x0, ttl 52, id 61686, offset 0, flags [DF], proto TCP (6), length 52)
04:15:18.538049 IP (tos 0x0, ttl 52, id 61687, offset 0, flags [DF], proto TCP (6), length 131)
04:15:18.538179 IP (tos 0x0, ttl 64, id 65430, offset 0, flags [DF], proto TCP (6), length 52)
04:15:18.538223 IP (tos 0x0, ttl 52, id 61688, offset 0, flags [DF], proto TCP (6), length 131)また、WANインターフェース以外も scrub するように in all と out all も設定
scrub in on $wanint all random-id max-mss 1360
scrub out on $wanint all random-id max-mss 1360
scrub in all
scrub out allNAT Rules
特定の WireGuard のクライアント側は WireGuard サーバ側のWANインターフェースのNATを通って出る
但し、この場合はIPv4でしか出ない。なぜならWireGuradサーバ側で IPv6 アドレスを配っていないため inet from とだけしてる。
nat on $wanint inet from <wg_clients> to any -> ($wanint)Fail2Ban Rules
Fail2Ban で攻撃元IPを pf のテーブルに追加してブロックするようにするためのルール。最後に置いて全てのルールにマッチしなかったパケットを anchor "f2b/*" で pf のテーブルに追加されたIPからのパケットをFail2Banのルールに流しています。quick にしているフィルタールールよりも先にマッチする必要があるため上に置いています。
この直前にはデフォルトルールの block all があるため、ここでマッチしないパケットは全て block されることになります。
/usr/local/etc/fail2ban/action.d/pf.conf:
# Option: block
#
# The action you want pf to take.
# Probably, you want "block quick", but adjust as needed.
# If you want to log all blocked use "blog log quick"
block = block quickこの設定から block quick のFail2banのルールが追加されるため上位に置いています。
つまり最初に anchor "f2b/*" で pf のテーブルに追加されたIPからのパケットをFail2Banのルールに流して、次に
anchor "f2b/*"フィルタールールとして一番最初に出て行くパケットを許可するルールを置いています。
pass out quick keep stateUDP Rules
WireGurad サーバ用許可ルール。51820/udp を許可して $wg_ifs のWireGuard上の仮想NICは全て許可する
もっとここはもしものことがあった時用に厳しくしたほうがいいかも?
pass in quick on $wg_ifs from $wg_net to any keep state
pass in quick on $wanint proto udp from any to ($wanint) port $wg_ports keep stateこの場合、すでに state を維持しているため、WireGuardのトンネルを通ってきたパケットは全て許可されることになります。この順番が逆だと pass in quick on $wg_ifs from $wg_net to any keep state のルールは WireGuard のトンネルである前提なので逆だと成立しません。
HTTP/3 を有効化するため 443/udp を許可
# HTTP/3 Protocol
pass in quick on $wanint proto udp to ($wanint) port 443 keep stateTCP Rules
nmap など使ったことある人であればわかると思いますが、TCPの場合のポートスキャンは非常に高速で行われるため、ポートを変えるだけでは攻撃者はすぐに見つけてしまいます。 UDPポートの場合 nmap のスキャンには非常に時間がかかるのでよりセキュアにするようにWireGuardのトンネル経由でしかSSHにアクセスできないようにしてます。つまりは実質的にTCPをUDPでラップしているような構成にするのが一番セキュアな構成になると思います。
Fail2banを導入しているので、SSHの攻撃があった場合は攻撃元IPを pf のテーブルに追加してブロックするようにはしています。 これは実質的なハニーポッドみたいな感じです。
メールプロトコルに関しては synproxy を有効にして、SYN Flood 攻撃を防止するようにしています。
この場合のHTTPに関しては ($wanint) で宛先IPを指定しているのは、WANインターフェースに割り当てられたIPアドレスに対して動的にマッチさせるためです。 明示的に inet を指定していないのは、IPv4とIPv6両方を許可するためです。
IPv6で来たパケットは ($wanint) で宛先IPを指定しているため、WANインターフェースに割り当てられたIPv6アドレスに対して動的にマッチさせることになります。
# Mail Protocols
pass in quick proto tcp from any to ($wanint) port {25, 465, 993, 995} synproxy state
# HTTP
pass in quick on $wanint proto tcp to ($wanint) port {80, 443} modulate state以上、ながくなった・・・。