はじめに
Linuxでiptablesやnftables使ってる人、設定ファイル読みにくくない?
FreeBSD(とOpenBSD)のPF (Packet Filter)は人間が読める設定ファイルで有名。
今回はPFの基本から実用的な設定まで解説する。
PFとは
┌─────────────────────────────────────────────────────────────┐
│ パケットフロー │
│ │
│ [Internet] → [NIC] → [PF] → [アプリケーション] │
│ ↑ │
│ フィルタリング │
│ NAT │
│ QoS │
│ ロギング │
└─────────────────────────────────────────────────────────────┘
特徴:
- OpenBSD生まれ(最も監査されたOS)
- 設定が読みやすい
- アトミックなルールロード
- 状態管理(ステートフル)
PFの有効化
# /etc/rc.confに追加
sysrc pf_enable="YES"
sysrc pflog_enable="YES"
# 設定ファイルを作成
touch /etc/pf.conf
# 起動
service pf start
service pflog start
基本設定
vi /etc/pf.conf
# マクロ(変数)
ext_if = "em0" # 外部インターフェース
int_if = "em1" # 内部インターフェース
tcp_services = "{ ssh, http, https }"
icmp_types = "{ echoreq, unreach }"
# テーブル(IPアドレスのリスト)
table <bruteforce> persist
table <trusted> { 192.168.1.0/24, 10.0.0.0/8 }
# オプション
set skip on lo0 # ループバックはスキップ
set block-policy drop # ブロック時はドロップ(返答なし)
set loginterface $ext_if # ログ対象インターフェース
# スクラブ(パケット正規化)
scrub in all
# NAT(内部→外部)
nat on $ext_if from $int_if:network to any -> ($ext_if)
# デフォルトルール:全てブロック
block all
# ループバックは許可
pass quick on lo0
# 外部からの接続
pass in on $ext_if proto tcp to port $tcp_services
pass in on $ext_if proto icmp icmp-type $icmp_types
# 内部ネットワークは全て許可
pass in on $int_if from $int_if:network
pass out on $ext_if from $int_if:network
# アウトバウンドは許可
pass out on $ext_if proto { tcp, udp, icmp }
# 確立済み接続は許可(状態追跡)
pass in on $ext_if proto tcp from any to any flags S/SA keep state
ルール構文
action [direction] [log] [quick] [on interface] [af] [proto protocol]
[from src_addr [port src_port]] [to dst_addr [port dst_port]]
[flags] [state]
例:
# SSHを許可
pass in on em0 proto tcp from any to any port 22
# 192.168.1.0/24からHTTPを許可
pass in on em0 proto tcp from 192.168.1.0/24 to any port 80
# UDPのDNSを許可(53番ポート)
pass in on em0 proto udp from any to any port 53
よく使うパターン
Webサーバー
# 基本設定
ext_if = "em0"
set skip on lo0
scrub in all
block all
# SSH(特定IPのみ)
pass in on $ext_if proto tcp from 203.0.113.0/24 to port ssh
# HTTP/HTTPS
pass in on $ext_if proto tcp to port { http, https }
# アウトバウンド
pass out on $ext_if
ルーター/NATゲートウェイ
ext_if = "em0" # WAN
int_if = "em1" # LAN
set skip on lo0
scrub in all
# NAT
nat on $ext_if from $int_if:network to any -> ($ext_if)
block all
# LAN → WAN
pass out on $ext_if from $int_if:network
# WAN → LAN(確立済み接続)
pass in on $ext_if proto { tcp, udp } keep state
# LANは全て許可
pass on $int_if
ポートフォワーディング
# 外部の80番ポートを内部の192.168.1.10:80に転送
rdr on $ext_if proto tcp from any to ($ext_if) port 80 -> 192.168.1.10 port 80
# 転送されたパケットを許可
pass in on $ext_if proto tcp to 192.168.1.10 port 80
テーブル(動的IPリスト)
# テーブル定義
table <whitelist> { 192.168.1.0/24, 10.0.0.0/8 }
table <blacklist> persist file "/etc/pf.blocklist"
table <bruteforce> persist
# ルールで使用
pass in from <whitelist>
block in quick from <blacklist>
block in quick from <bruteforce>
テーブル操作
# IPを追加
pfctl -t blacklist -T add 1.2.3.4
# IPを削除
pfctl -t blacklist -T delete 1.2.3.4
# テーブル内容を表示
pfctl -t blacklist -T show
# テーブルをフラッシュ
pfctl -t blacklist -T flush
ブルートフォース対策
table <bruteforce> persist
# 1分間に5回以上のSSH接続でブロック
block in quick from <bruteforce>
pass in on $ext_if proto tcp to port ssh \
flags S/SA keep state \
(max-src-conn 10, max-src-conn-rate 5/60, \
overload <bruteforce> flush global)
オプション説明:
-
max-src-conn 10: 1IPあたり最大10接続 -
max-src-conn-rate 5/60: 60秒間に5接続まで -
overload <bruteforce>: 超過したらテーブルに追加 -
flush global: 既存の接続も切断
自動解除(expiretable)
pkg install expiretable
# 1時間後に自動解除
expiretable -t 3600 -d bruteforce
crontabに追加:
*/5 * * * * /usr/local/sbin/expiretable -t 3600 bruteforce
アンカー(動的ルール)
# メイン設定でアンカーを定義
anchor "ssh_rules"
load anchor "ssh_rules" from "/etc/pf.anchors/ssh"
# /etc/pf.anchors/ssh
pass in on em0 proto tcp to port 22
動的に変更:
# アンカーにルール追加
echo "block in from 1.2.3.4" | pfctl -a ssh_rules -f -
# アンカーのルールを表示
pfctl -a ssh_rules -sr
# アンカーをフラッシュ
pfctl -a ssh_rules -F rules
ロギング
pflogの有効化
# ルールにlogを追加
pass in log on $ext_if proto tcp to port 22
block in log on $ext_if
ログの確認
# リアルタイムで確認
tcpdump -n -e -ttt -i pflog0
# 保存されたログを確認
tcpdump -n -e -ttt -r /var/log/pflog
pfctlコマンド
# 設定をリロード
pfctl -f /etc/pf.conf
# PFを有効化
pfctl -e
# PFを無効化
pfctl -d
# 現在のルールを表示
pfctl -sr
# NAT/リダイレクトルールを表示
pfctl -sn
# 状態テーブルを表示
pfctl -ss
# 統計を表示
pfctl -si
# 全情報を表示
pfctl -sa
# テスト(実際にロードしない)
pfctl -nf /etc/pf.conf
# 詳細出力でテスト
pfctl -nvf /etc/pf.conf
iptablesとの比較
| やりたいこと | iptables | PF |
|---|---|---|
| SSH許可 | iptables -A INPUT -p tcp --dport 22 -j ACCEPT |
pass in proto tcp to port 22 |
| 全てブロック | iptables -P INPUT DROP |
block all |
| NAT | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE |
nat on em0 from lan:network to any -> (em0) |
| ポートフォワード | iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to 192.168.1.10:80 |
rdr on em0 proto tcp to port 80 -> 192.168.1.10 |
PFの方が読みやすいと思わない?
トラブルシューティング
ルールが適用されない
# 文法チェック
pfctl -nf /etc/pf.conf
# ルールの順序を確認(quickに注意)
pfctl -sr
接続できない
# 状態テーブルを確認
pfctl -ss | grep 192.168.1.10
# ログを確認
tcpdump -i pflog0
パフォーマンス問題
# 状態テーブルの上限を確認
pfctl -sm
# states hard limit 10000
# 上限を増やす
set limit states 100000
まとめ
PFは:
- 設定が読みやすい(人間向け)
- テーブルで動的なIP管理
- アンカーで動的なルール管理
- ブルートフォース対策が簡単
- OpenBSD由来の信頼性
iptablesに疲れたらPFを試してみて。
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!