FreeBSDのパケットフィルター設定

FreeBSDにはパケットフィルター(ファイアウォール)機能があります。既にさくらのVPSでパケットフィルターを設定していますが、FreeBSDにも設定することでより細かい制御を行うことができます。

注意FreeBSDのハンドブックではファイアウォール(Firewalls)となっていますが、ここではさくらのVPSの設定と対比するため、パケットフィルターという表記に統一しています。

接続先ポート番号と接続元ポート番号

一般的にパケットフィルターでは、出ていく(outbount)通信は遮断せず、入ってくる(inbound)通信をブロックするように設定します。しかし、TCP/UDPの通信では接続先のポート番号と同様に、接続元のポート番号があります。例えばHTTPの通信であれば、クライアントから見るとサーバへの接続先ポート番号は80ですが、サーバがそのレスポンスをクライアントに返すための接続元ポート番号があります。

さくらのVPSから外のサーバへHTTPで通信する(パッケージの更新を行う)と、サーバの接続元ポート番号へのinbound通信が発生します。つまり、この通信を許可するように設定しないと、サーバから外へHTTP通信ができなくなってしまいます。しかし、HTTPの接続先ポート80と違って接続元ポートは不定なので事前にポート番号を指定できないのです。

ステートレスとステートフル

パケットフィルターにはステートレスステートフルの2種類があります。

ステートレスのパケットフィルターは、サーバからの通信に対するレスポンスの通信で使われるであろうこういった一時的なポートをあらかじめ許可しておく必要があります。その代わり、ステートレスのパケットフィルターはシンプルで負荷が軽くなります。

ステートフルのパケットフィルターは、許可された通信に対するレスポンスの通信も許可することができます。上記の例で言えば、サーバからHTTPで通信した際のレスポンスを、そのレスポンスを受信している間だけは許可するようになります。その通信が終わると、許可していた接続元ポートにアクセスしてもブロックされます。ステートフルではこのように接続の状態(ステート)を管理する必要があるため、パケットフィルターの処理負荷が後述のステートレスに比べて高くなります。

さくらのVPSのパケットフィルター

さくらのVPSのパケットフィルターはステートレスです。そのため、あらかじめ許可されていて変更できないプロトコルやポートがあります。詳細は注意事項で確認できます。

TCP/UDPの通信の接続元ポート(エフェメラルポートやダイナミックポート、動的ポートといいます)はIANAがポート49152~65535を推奨しています。FreeBSDやWindowsはこの範囲を使います。一方でLinuxはポート32768~61000を使います。なお、いずれもOSの設定で変更は可能です。これが、さくらのVPSのパケットフィルターはデフォルトでTCP/UDPともポート32768〜65535を許可している理由です。

したがって、さくらのVPSでパケットフィルターを有効にしていてもTCP/UDPのポート32768〜65535への通信は常にオープンであるということです。この範囲のポートへの攻撃をブロックできるようにするのが、さくらのVPSのパケットフィルターに加えて、FreeBSDであえてパケットフィルターを設定する理由です。

FreeBSDのパケットフィルター

FreeBSDには以下のパケットフィルターが用意されています。昔はパケットフィルターを有効にするにはそれぞれの機能をkernelで有効にして作成し直す必要がありましたが、今は好きなものをいつでも有効にできます。

ipfw

ipfwはFreeBSDに古くからあるパケットフィルターで、インターネット上にもFreeBSDのパケットフィルターとしてドキュメントが豊富にあります。ipfwコマンドでルールを1つずつ設定できるのが特徴です。複雑なルールを用いずいくつかのルールしか使わない場合や、シェルスクリプトでルールを生成してそのまま実行する場合には便利です。

ipf(ipfilter)

ipfはFreeBSD以外にNetBSDやOpenBSD、Solarisなどでも使えるパケットフィルターです。そのため、SolarisやFreeBSD以外の*BSDで設定などを含めてインターネット上にはノウハウや例が豊富です。逆にFreeBSDだけの設定を探そうとするとipfwよりも少ないかもしれません。また、ipfはルールファイルを作成して、ipfコマンドでルールファイルを指定して設定します。そのため、ルールを変更する時は一度ルールを削除してルールファイルを読み込み直すことになります。決まったルールを異なる環境で同じように設定する際は便利です。

pf

pfはOpenBSDがipfを参考に開発したパケットフィルターです。ipfと同様にルールファイルを作成して、pfctlコマンドでそのルールファイルを指定することで設定します。しかし、ルールファイルの書式はipfと似ているようで異なります。そのため、ipfに慣れている人であっても戸惑うことが多いと思います。

また、pfはOpenBSDが発祥のパケットフィルターであることもあり、ノウハウや例を見付けるのが難しいのが実情です。ドキュメントもFreeBSDのハンドブックでもOpenBSDを参照するように書かれていたりと、始めて触ろうとするとハードルが高く感じられます。しかし、セキュリティ至上主義のOpenBSDが開発したパケットフィルターですので、堅牢性と安定性は他のパケットフィルターに勝るとも劣りません。ここではpfの布教を兼ねて、pfの設定の仕方を紹介します。

pfctlコマンド

OSのパケットフィルターは設定を間違えると最悪の場合はsshでログインできなくなってしまう場合があります。そのため、いざという時に備えて最低限の復旧方法を覚えておいた方がよいです。

  • pfを有効化
% sudo pfctl -e
  • pfを無効化
% sudo pfctl -d
  • pfルールを削除して再読み込み
% sudo pfctl -F all -f /etc/pf.conf
  • pfのルール表示
% sudo pfctl -s rules
  • pfルールのチェック(設定はしない)
% sudo pfctl -vnf /etc/pf.conf

また、pfルールを設定する際には意図的にsleepを入れたコマンドを一行で実行して、もしも入力がなかった場合にはpfルールを削除するようなこともできます。以下の例では、pfルールを設定した後で5秒待ち、その間にCtrl-Cを押せば中断できます。もしもpfルールで通信がブロックされてターミナルの入力を受け付けなくなってしまった場合には、そのままsleepが終了してpfルールがクリアされるという仕組みです。

% sudo pfctl -F all -f /etc/pf.conf; echo Ctrl-C to break; sleep 5; sudo pfctl -F all

pfの設定ファイル

設定の前提条件として以下を想定しています。

  • インタフェースは1つ(グローバルIP)だけである
  • Inboundは SSH/HTTP/HTTPS および、Outboundに対する戻り(ステートフル)を許可する
  • Outboundは全て許可する

pfの設定は /etc/pf.conf に記述しますが、デフォルトのファイルが存在しません。したがって新規に作ることが必要です。/usr/share/examples/pf/pf.conf が用意されているので、これをサンプルとして修正するのがおすすめです。まず、それぞれの項目の内容を説明します。

#       $FreeBSD$
#       $OpenBSD: pf.conf,v 1.34 2007/02/24 19:30:59 millert Exp $
#
# See pf.conf(5) and /usr/share/examples/pf for syntax and examples.
# Remember to set gateway_enable="YES" and/or ipv6_gateway_enable="YES"
# in /etc/rc.conf if packets are to be forwarded between interfaces.

#ext_if="ext0"   # 外向けのインタフェース名。さくらのVPSではvtnet0となっている。
#int_if="int0"   # 内向けのインタフェース名。さくらのVPSではvtnet1となっているが、今回は不要。

#table <spamd-white> persist  # ルールを再読み込みしないで使えるテーブル機能。今回は不要。

#set skip on lo  # ループバックへの適用をスキップする。

#scrub in        # IPパケットのサニタイズを行う。

#nat-anchor "ftp-proxy/*"  # FreeBSDをNATルータとする例。不要。
#rdr-anchor "ftp-proxy/*"  # 同上。不要。
#nat on $ext_if inet from !($ext_if) -> ($ext_if:0)  # 同上。不要。
#rdr pass on $int_if proto tcp to port ftp -> 127.0.0.1 port 8021  # 同上。不要。
#no rdr on $ext_if proto tcp from <spamd-white> to any port smtp   # SMTPサーバの設定。不要。
#rdr pass on $ext_if proto tcp from any to any port smtp \         # 同上。不要。
#       -> 127.0.0.1 port spamd                                    # 同上。不要。

#anchor "ftp-proxy/*"  # FreeBSDをNATルータとする例。不要。
#block in              # inboundをデフォルトblockする。
#pass out              # outboundをデフォルトpassする。

#pass quick on $int_if no state      # 内向けインタフェースからの接続をステートレスで許可。今回は不要。
#antispoof quick for { lo $int_if }  # ループバックと内向けインタフェースに対してIP偽装パケットをブロック。

#pass in on $ext_if proto tcp to ($ext_if) port ssh             # 外向けインタフェースへのsshを許可。
#pass in log on $ext_if proto tcp to ($ext_if) port smtp        # SMTPサーバの設定。不要。
#pass out log on $ext_if proto tcp from ($ext_if) to port smtp  # SMTPサーバの設定。不要。
#pass in on $ext_if inet proto icmp from any to ($ext_if) icmp-type { unreach, redir, timex }  # 外向けインタフェースへの一部のICMPを許可。

上記を元に環境に合わせた /etc/pf.conf を作成します。

#       $FreeBSD$
#       $OpenBSD: pf.conf,v 1.34 2007/02/24 19:30:59 millert Exp $
#
# See pf.conf(5) and /usr/share/examples/pf for syntax and examples.
# Remember to set gateway_enable="YES" and/or ipv6_gateway_enable="YES"
# in /etc/rc.conf if packets are to be forwarded between interfaces.

ext_if="vtnet0"   # 外向けのインタフェース名。さくらのVPSではvtnet0となっている。

tcp_services="{ 22 80 443 }"      # 許可するSSH/HTTP/HTTPSのポート番号を定義。
icmp_types="{ echoreq unreach redir timex }"  # 許可するICMPを定義。

set block-policy drop     # blockしたパケットは応答を返さずにdropする。
set loginterface $ext_if  # 外向けインタフェースのロギングを有効にする。

set skip on lo  # ループバックへの適用をスキップする。

scrub in        # IPパケットのサニタイズを行う。

block in log    # inboundをデフォルトblockしてロギングする。
pass out        # outboundをデフォルトpassする。

antispoof quick for { lo }  # ループバックに対してIP偽装パケットをブロックする。

pass in on $ext_if proto tcp to ($ext_if) port $tcp_services  # 外向けインタフェースの特定のポートを許可。
pass in on $ext_if inet proto icmp from any to ($ext_if) icmp-type $icmp_types  # 外向けインタフェースへの特定のICMPを許可。

pass in on $ext_if inet6 proto udp from any to ff02::66 port 2029  # IPv6のARPを許可。
pass in on $ext_if inet6 proto icmp6 all                           # IPv6のICMPを許可。
pass in on $ext_if inet6 proto udp from fe80::/10 port = dhcpv6-client to ff02::/16 port = dhcpv6-server  # IPv6のDHCPv6を許可。

前提に加えて2つ追加しています。

さくらのVPSではコントロールパネルよりサーバー監視を行うことができます。特定のサービスを監視するのでなければpingを選択するのが無難なので、ICMPの echoreq (いわゆるping)を許可しています。

また、さくらのVPSではIPv6が有効になっていますので、IPv6はルーティングに必要なものを追加で許可しています。SSH/HTTP/HTTPSは inetinet6 を指定していないので、共通で有効になります。

更に詳しく設定したい場合はOpenBSDのPFのドキュメントを参照してください。

pfの設定

最後にpfを有効にします。既に書いている通り、pfは設定を間違えるとログイン不能になることがあります。そのため、記述には最新の注意を払いましょう。

いきなり /etc/rc.conf に反映せずに、まずはコマンドで有効化することをお薦めします。もしも問題があってログインできなくなった場合は、コントロールパネルからVPSを再起動することでpfが有効になっていない状態で起動し直すことができます。

% sudo pfctl -e
% sudo pfctl -vnf /etc/pf.conf
% sudo pfctl -F all -f /etc/pf.conf
% sudo pfctl -s rules

これで問題なくsshでログインしたり、Webサーバに対してアクセスしたりできることを確認できたら、常に有効になるように /etc/rc.conf に以下の記述を追加します。

pf_enable="YES"
pflog_enable="YES"

sysrcコマンドでも設定できます。

% sudo sysrc pf_enable=YES
% sudo sysrc pflog_enable=YES

pfステートの確認

pfのステートフルな通信の状態を確認するにはpfctlコマンドを使います。

% sudo pfctl -s states

また、portsにはtopコマンドのようにリアルタイムでステートを見ることができるpftopというツールがあります。pfctlコマンドよりも直感的に分かりやすくてお薦めです。

% sudo pkg add pftop
% sudo pftop

参考