10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Linuxでiptablesnftables使ってる人、設定ファイル読みにくくない?

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を試してみて

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?