インターネットはとことんオープンなネットワークなので、相手のアドレスさえ分かれば誰にでも不正アクセスを試みることができる。また、「特に恨みを買っていないし、大企業のように重要なデータを持っていることもない」という人でも、ランダムなアドレスに対して、セキュリティホールがないか(悪意を持って)調べている人もいるし、万一そういった人の攻撃によってPCの制御を奪われることがあれば、自分のPCが共犯となって他人に迷惑をかけてしまうこともありうるのである。
もちろん、それでは困るので、セキュリティ確保のための方法のひとつに、ファイアウォールというものがある。これは、外部から届いたパケットを調べ、必要なものだけを通すようにして、不正パケットを遮断することによってセキュリティを向上させる技術である。Linuxカーネルの2.4には、そのためのNetFilterという機能が組み込まれているので、ここではそれを用いてファイアウォールを構築することにする。
なお、インターネットのセキュリティというものは、ファイアウォールを設置しただけで完璧になるものでは決してない。不要なサービスを起動しない(いつも要らないものなら、そもそもインストールしない)こと、使用しているサービスのセキュリティホール情報をこまめにチェックしてパッチを当てること、など、正しい運用があってこそセキュリティが確保されるのだということを念のために追加しておく。
NetFilterを使うには、カーネルがそれをサポートするように設定されていなければならない。次に、NetFilterの機能を使用するには、iptablesというコマンドが必要である。したがって、次のような手順で作業を行った。
NetFilterの動作についてもっと詳しく知りたければ、JFの「Linux 2.4 Packet Filtering HOWTO」に目を通すべきである。
カーネルのコンパイルや入れ替えなどについては「カーネルの再構築」を参照して欲しい。
ちなみに、私はカーネルの「make menuconfig」メニューで、「Networking options」の、「Network packet filtering」をONにして、さらに「IP: Netfilter Configuration」内の項目のうち「EXPERIMENTAL」がついていないものをすべてカーネルに組み込んだ。必要に応じてモジュール化してもいいだろうと思う。
Debianならこれは簡単。
root@host# apt-get install iptables
これで、準備は完了である。あとはルールを追加していけばよい。
iptablesでNetfilterの設定を行うには、Linuxカーネル内のテーブル、チェイン、ルールを操作する必要がある。「テーブル」には、通過するパケットとと破棄するパケットを指定するfilterテーブル、Linuxを一般的なNATルータとして使うための設定を行うnatテーブル、それと、パケットに追加的な操作を行うmangleテーブルがある。ここでは、パケットのフィルタリングを行うのでfilterテーブルが操作対象である。
各テーブルには、パケットに対して適用される「ルール」の集合体である「チェイン」があり、たとえば今回のfilterテーブルには最初から3つのチェインが存在する。外から自分宛に届いたパケットに適用されるINPUTチェイン、自分が外に向けて送ろうとしているパケットに適用されるOUTPUTチェイン、そして自分がルータとなってパケットを中継する場合に適用されるFORWARDチェインである。
ここでは、自分宛に届くパケットのフィルタルールを紹介するので、主にINPUTチェインが操作対象である。FORWARDチェインについては、ルータ化の際に紹介することとする。
チェイン内で審査されるパケットは、いずれかのルールに合致して、「ACCEPT(通過)」「DROP(遮断)」「REJECT(返送)」などの指示がされると即座にその動作をし、その後のルールはチェックされない。しかし、どのルールにも合致しなかった場合の動作はどうなるのか?これを指定するのが、「ポリシー」である。
セキュリティを高める基本は、「デフォルトオフ」である。つまり、原則として遮断し、通過してよいパケットを列挙する。
INPUTチェインに入ったパケットは原則として遮断される
root@host# iptables -P INPUT DROP
注意すべき点として、もしsshなどの手段でリモート管理をしているときに、いきなりこのポリシーを設定してはいけない。sshを許可する設定を追加する前にポリシーをDROPにしてしまうと、その瞬間からリモート管理が出来なくなってしまうのだ(当たり前)。
ことさらに外向きと内向きにルールを分離したのは、管理のしやすさを考えてのことである。ここでは、外(インターネット)に繋がっているインターフェースをppp0、内(LAN)に繋がっているインターフェースをeth0として話を進める。
外から自分宛に届いたパケットをチェックするチェインを作成
root@host# iptables -N i_to_h
ppp0から入ってきたパケットを先ほど作ったチェインに送るルールを追加
root@host# iptables -A INPUT -i ppp0 -j i_to_h
このように外向きと内向きでチェインを分けておけば、たとえば緊急の事態が発生して外部との通信のみを遮断する、などということが比較的容易なのだ。ちなみに、チェイン名は「Internet to Host」の略のつもりであるが、別に何でもかまわない。
確立されたコネクションと、それに関連する接続に属するTCPパケットを許可
root@host# iptables -A i_to_h -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
このルールは、TCPパケットのうち、すでに接続が確立されているものと、それに関連する通信のパケットは通過を許可する設定である。たとえば、外から何かをダウンロードする場合に、このような設定がなければ自分が接続を要求した通信であっても返答パケットを遮断してしまい都合が悪い。また、FTPのように、コマンドとデータ転送に使うコネクションが異なるような通信を行うとき、RELATEDを許可しないとデータ転送が失敗する。
外部からのsshへの接続要求を許可
root@host# iptables -A i_to_h -p tcp --dport 22 -m state --state NEW -j ACCEPT
このルールは、dport、すなわちあて先ポート番号が22である、TCP接続要求パケットの通過を許可する設定である。これで、外部からsshで接続可能になる。もちろん、ユーザー名とパスワードなり、秘密鍵なり、認証を通過しないとログインは出来ない。
外部のDNSサーバー(xxx.xxx.xxx.xxxとyyy.yyy.yyy.yyy)からの返答パケットを許可
root@host# iptables -A i_to_h -s xxx.xxx.xxx.xxx -p udp --sport 53 -j ACCEPT
root@host# iptables -A i_to_h -s yyy.yyy.yyy.yyy -p udp --sport 53 -j ACCEPT
これは、自分が使用するプロバイダのDNSサーバの、53番ポートからのUDPパケットの通過を許可するルールで、これがないと外部の名前解決サービスが受けられない。
このくらいが最低限必要なパケット通過ルールかと思われる。そのうち、外部からメールを読めるようにしたい、とか、外部にwebサーバを公開したい、という要求が発生したら適宜ルールを追加していく。
次に内側向けのルールを定義しよう。
同様に、LANから自分宛に届いたパケットをチェックするチェインを作成
root@host# iptables -N l_to_h
eth0から入ってきたパケットを先ほど作ったチェインに送るルールを追加
root@host# iptables -A INPUT -i eth0 -j l_to_h
確立されたコネクションと、それに関連する接続に属するTCPパケットを許可
root@host# iptables -A l_to_h -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
sshサーバへの接続要求を許可
root@host# iptables -A l_to_h -p tcp --dport 22 -m state --state NEW -j ACCEPT
多くの場合、外から通過を許可するパケットは、内側からも通過を許可する。もっとも、DNSサーバーは内側に存在しないのでそのためのルールは要らない。
ICMPパケットを許可
root@host# iptables -A l_to_h -p icmp -j ACCEPT
それだけじゃ外向きと内向きを分けた意味があんまりないので(サービスの増加とともに今後出てくるわけだが)、内側からのみICMPパケットを許可してみる。ICMPとは、pingなど、ネットワークの動作状態を監視するためのツールが使用するプロトコルである。外からのpingに応答してもあんまりいいことはないので、通常外からのICMPパケットは遮断することが多い。
これでパケットフィルタは万全、と言いたいところだが、パケットフィルタルールはLinuxカーネルのメモリ内に保持されているだけなので、再起動すると設定が消えてしまう。そのため、起動するたびにルールを適用する仕組みが必要である。
現在のルールを保存
root@host# /etc/init.d/iptables save active
この点については、Debianでパッケージからiptablesをインストールしたならば簡単。上のコマンド一発で現在の状態をファイルに保存し、次回起動時に自動適用してくれるようになる。