2009/12/30

alixをブロードバンドルーターにする

いままで使っていたCoregaのブロードバンドルーターに問題はなかったのですが、 時々動作が不安定になる事があって自分でBフレッツと自宅LANとのゲートウェイを作ってみる事にしました。

目的は、とりあえずalixをブロードバンドルーター代りにしてみることと、ポートスキャンの様子を観察する事です。

参考資料

今回は次の資料を主に参考にしました。 特に作成したiptablesの設定については、参考資料1,2の2つを合わせてベースとして使わせて頂いています。

最終的なiptablesのリファレンスとしては参考資料3を参考にしました。

全体の構成

使ったalixはalix2c3で、3つのethernet portを持っています。実験ネットワーク(192.168.10.0/24)は無線LANのgatewayを置く事を考えていて、内部からInternetへのアクセスを許可して、192.168.1.0/24には許可しない構成を考えています。

  • eth0(ppp0): global IP address (BフレッツとのPPoE接続用)
  • eth1: 192.168.1.1 (192.168.1.0/24 主ネットワーク用)
  • eth2: 192.168.10.1 (192.168.10.0/24 実験ネットワーク用)

将来的には別のalixを使って新たにネットワークを作りOSPFの実験なども行ないたいと思っています。

フィルターポリシー

eth1,eth2の間は特にルーティングを制限する事はいまのところ考えていませんが、将来的には制限を加える可能性があります。 またeth0については参考にさせて頂いた設定例を参考に、ppp0を通るパケットについて制限を加える事にしました。

具体的な制限の内容は、Internetからの入力については基本的に不許可として、弾力的にssh, bittorrentなどの通信を許可する事ができる仕組みを準備する事としました。 その他に通常使っているpingやtracerouteといったコマンドは外部から拒否しつつも、内部からは使えるようにする事としています。

iptablesの使い方

iptablesコマンドをそのまま使うというケースはほとんどないと思います。 CentOSなどRHEL系列では/etc/sysconfig/iptablesファイルにその内容を記録しますし、Ubuntuではufwパッケージを使い、/etc/ufw/*.rulesファイルを使う事ができます。

今回はdebianを使っていますが、パッケージを追加する他に適当な方法がありません。 そこで自前のスクリプトを/usr/local/sbin/iptables.shに準備する事にしました。

このスクリプトは起動時ではなく、PPPoEが実行された後で実行する必要があります。 そこで/etc/network/interfacesファイルを使う事にしました。 PPPoEを使うための設定は次のようになっています。

...
auto dsl-provider
iface dsl-provider inet ppp
  pre-up /sbin/ifconfig eth0 up # line maintained by pppoeconf
  provider dsl-provider
  post-up /usr/local/sbin/config_iptables.sh

auto eth0
iface eth0 inet manual
  mtu 1454

このpost-upを使い、ppp0インタフェースが起動した後に必ずiptablesの設定が行なわれるようにしています。 interfacesファイルを使った設定については、$ man 5 interfacesを参考にしてください。

Debian Etchのインストール

以前投稿した記事(Ubuntu 7.10からALIXでブート可能なCFカードを作成する)を参考にしてください。 ここではalixでdebianとsshdが起動している事を前提に進めています。

PPPoEの設定

参考にしたサイトでは手動で設定を入れていますが、pppoeconfパッケージが面倒をみてくれます。

$ sudo apt-get install pppoeconf
$ sudo /usr/sbin/pppoeconf

質問への答えで困るところは特にないはずです。 プロバイダから貰った接続用のID, パスワードを設定すれば基本的には終りです。

PPPoE設定の変更

設定ファイル/etc/ppp/peers/dsl-providerを変更しました。

$ sudo diff -u /etc/ppp/peers/dsl-provider.20091223 /etc/ppp/peers/dsl-provider
38c38
< usepeerdns
---
> #usepeerdns
66c66
< mtu 1492
---
> mtu 1454

usepeerdnsをコメントアウトしたのは、自前のDNSサーバーを持っているからです。 そうでなければ変更する理由はないと思います。

mtuを1454に変更したのは、Bフレッツの設定例に載っているからです。 これに併せて家のネットワークにあるLAN内の機器についても上記eth0と同様にmtu 1454を設定しています。

接続テスト

ponコマンドを使って、ppp0インタフェースが構成されるか確認しましょう。

$ sudo pon dsl-provider
$ /sbin/ifconfig ppp0

テストが終ったら接続を切ります。これを忘れるとppp0, ppp1, ...とインタフェースが増えてしまいます。

$ sudo poff -a
起動時にPPPoEを立ち上げる

電源ONと同時にBフレッツに接続するために、/etc/network/interfacesiptablesの使い方にあるように変更します。

不具合があった場合の対応方法

もしプロバイダから送られてきたIDやパスワードに変更があった場合には、$ sudo pppoeconfを再度実行して必要な変更を加えた方が早いと思います。

そういった場合も含めて、自分で設定を書き換えたい場合には/etc/ppp以下にあるファイルにgrepをかけて必要な情報全体を修正してください。

ID, パスワードの情報はpap-secrets, chap-secretsファイルの他にdsl-providerファイルにも含まれています。

ブロードバンドルーターとしての設定

いまのところalixからInternetへは繋っていますが、LANで繋っている他のPCからはInternetへの通信ができないままです。

つまりeth1のネットワーク(192.168.1.0/24)にあるPCからの通信は、 eth0->ppp0を通ってInternetに出ていかないように設定されています。 そのためppp0,eth1,eth2の各インタフェース間を行き来できるように設定を加えていきます。

sysctl設定

Ubuntuを含むDebianの系統では、systcl関連の設定を/etc/sysctl.confで行ないます。 最低限必要な設定はnet.ipv4.ip_forward=1だけですが、sysctl.confファイル全体の設定は次のようにしました。

$ egrep -v ^# /etc/sysctl.conf | egrep -v ^$
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.tcp_syncookies=1
net.ipv4.ip_forward=1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
最低限のiptables設定

通常はiptablesのFORWARDポリシーはACCEPTになっているため、一つしかないグローバルIPアドレスを共有するためのNAT(IP MASQUERADE)設定を追加するだけで動くようになります。 この段階での/usr/local/sbin/iptables.shスクリプトの内容は次のようになります。

#!/bin/bash
umask 066
PATH=/sbin:/usr/sbin:/bin:/usr/bin
export PATH
iptables -F
iptables -t nat -F
iptables -Z
iptables -X
iptables -t nat -X
iptables -t nat -A POSTROUTING -o ppp+ -j MASQUERADE

2010/01/04追記:
このままではスクリプトを複数回実行した時に、ターゲット(-t)に指定したNAT関連の設定が初期化されずに残ってしまいます。 そのため次の2行を追記しました。 iptables -t nat -Fiptables -t nat -X

もちろん、このままでは危なくてInternetに常時接続する事はできません。 最低限確認しておく事はまだあります。

セキュリティ其の一:LISTENポートの確認

Internetのどこかにいる人が、このalixにアクセスする事が可能か確認する事にします。 今回はSheldsUp!ポートスキャンサービスを利用してみます。

リンク先に進むとShieldsUp!が検出したグローバルIPアドレスが表示されます。 Proceedボタンを押すと、サービスの選択画面が表示されます。 All Service Portsを選択して1056ポートをチェックします。

何も設定していないとOpenSSHの22番ポートが赤いOpenにチェックされているのではないでしょうか。 またpingに反応する設定にしていても問題だと認識されます。 外部からICMPパケットを送り込んで内部の状況を観察する方法もあるので、 Firewallの設定は適切に行なう必要があります。

セキュリティ其の二:LISTENポートのクローズ

今やInternetと家のLANとのゲートウェイになったalixが、どのようなネットワークサービスを提供しているのか確認しておく事にします。

$ netstat -na --protocol=inet
稼働中のインターネット接続 (サーバと確立)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態       
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:6010          0.0.0.0:*               LISTEN     
tcp        0      0 192.168.1.1:22          192.168.1.3:50845       ESTABLISHED
udp        0      0 192.168.1.1:34761       192.168.1.2:123         ESTABLISHED
udp        0      0 192.168.1.1:111         0.0.0.0:*

ここで問題になるのは、状態がLISTENであるもののうち、内部アドレスが0.0.0.0:22になっているSSHのサービスです。 :::22あるいは0.0.0.0:22となっている場合、eth0, eth1, eth2, ppp0のいずれのインタフェースからの接続も許可してしまいます。 そのためポートスキャンサービスでは、Openと検出され警告の対象となります。

Internetからのssh接続は必要ないため、アクセスを制限する事にします。

SSHのポート制限

SSHデーモン(sshd)の制限は、Debianを始め主要なDistributionでは/etc/ssh/sshd_configを通して行なわれます。 変更個所は以下の通りです。

** 変更前 **

ListenAddress 0.0.0.0

** 変更後 **

ListenAddress 192.168.1.1
ListenAddress 192.168.1.1

この変更により、192.168.1.1, 192.168.10.1を宛先とする接続を許可する事ができます。 つまり、クライアントはIPアドレスが192.168.1.0/24, 192.168.10.0/24のいずれかに限定できる事になります。

portmap関連のセキュリティ

"0.0.0.0:111"のように表示されれば、それはportmapが稼働している事を意味しています。 今回はautofsパッケージを導入したため、portmapも稼働する事になっています。

しかしalix以外からの接続を許可する必要はないため、"127.0.0.1:111"と表示されるように変更をします。 まずportmap自身は-i, -lの2つのオプションで、SSHのListenAddressと似たような設定をする事が可能です。 これはportmapの起動時の引数で設定するため、/etc/default/portmapファイルを編集します。

OPTIONS="-l"

しかしportmapをSSHのように192.168.1.1, 192.168.10.1の2つのアドレスで待ち受けるようにする事はできないようです。 また、Linuxに付属するportmap関連のdaemonの中には、上記のようにListenAddressを変更する事ができないものもあります。

そこで、rpc.statdなどの対策のために今回は/etc/hosts.allow, /etc/hosts.denyファイルも編集する事にします。

hosts.allow, hosts.denyの編集

tcpwrapperと呼ばれるライブラリを使うように作成されたプログラムでは、一般的に /etc/hosts.allow, /etc/hosts.denyという2つのファイルを使用してネットワーク接続を制限する事ができるようになります。

さて、どのプログラムがtcpwrapperを使う事ができるのかはリンクされているライブラリから伺い知る事ができます。

$ ldd /usr/sbin/sshd
	linux-gate.so.1 =>  (0xb7f75000)
	 => /lib/libwrap.so.0 (0xb7f68000)
	libpam.so.0 => /lib/libpam.so.0 (0xb7f5d000)

libwrap.so.0が表示されれば、tcpwrapperの機能を含んでいると考えられます。 実際に/sbin/portmapにlddコマンドで確認しても、libwrap.so.0が含まれています。

今回はとにかく家のLANからのアクセスだけを許可したいので、 細かい制御は止めて一括でルールを設定します。

/etc/hosts.allow

ALL: 192.168.1.0/24 192.168.10.0/24

/etc/hosts.deny

ALL: ALL
閑話休題:Linuxでのネットワークアクセス制御

Linuxでは各プログラム独自のアドレス、ポート管理の他に、tcpwarpperというライブラリによるプログラムの制御、iptablesによるカーネルレベルでの管理を行なう事ができます。 低レベルなところで動くiptablesで十分かもしれませんが、iptablesはとびらにつける鍵のようなものです。 LISTENするアドレスを制限する事で、外に向けてとびら自体を作らないという考え方がより堅牢とはいえます。

セキュリティ其の三:iptables

これまでの方法で外部から直接的にシステムに侵入する事は難しくなっています。 しかしまだpingに反応しますし、まだ攻撃対象となる可能性があります。

/etc/network/interfacesファイルから呼ばれる/usr/local/sbin/iptables.shスクリプトの構成について説明します。

この文書の最低限のiptables設定で作成したスクリプトでは、 ざっとみて下記に挙げたようないくつかの観点が抜けています。 いきなり完璧なルールを作る事はできませんが、将来的に拡張可能な小さいルールセットを作成して、 徐々に拡張して行くのが良いと思います。

  • デフォルトポリシーの設定
  • ログの取得による稼働状況の確認
  • インタフェース毎の制御
  • 外部設定ファイルによるinbound接続の許可
  • 不正なIP送信元、送信先パケットの拒否
  • 不正なTCPフラグを持つパケットの拒否

まずは全体的な構成に関わるログの取得のところまでを先に説明します。 各論はまた別のテーマにしたいと思います。

デフォルトポリシーの設定

前にも書きましたが、何のiptablesの設定を行なわないとACCEPTがデフォルトのポリシーになっています。

$ sudo /sbin/iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination  

このままではフィルタールールを設定しても、そこから漏れたものがalixを通過していってしまいます。 そこでiptables -Fなどで初期化した後に、デフォルトルールを設定します。

iptables.sh

...
iptables -F
iptables -Z
iptables -X

iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

iptables -t nat -A POSTROUTING -o ppp+ -j MASQUERADE
...

ポリシーが適用できるのは、この3つのINPUT, OUTPUT, FORWARDだけです。

ログの取得による稼働状況の確認

とりあえずログをとりつつ、許可を与えるルール(logaccept)と、不許可にするルール(logdrop)を準備する事にします。

iptables.sh: ログ取得用コード

...
iptables -P FORWARD DROP

iptables -N logdrop
iptables -A logdrop -m limit --limit 5/minute -j LOG --log-level warn --log-prefix "myfilter drop "
iptables -A logdrop -j DROP

iptables -N logaccept
iptables -A logaccept -m limit --limit 5/minute -j LOG --log-level debug --log-prefix "myfilter accept "
iptables -A logaccept -j ACCEPT
...
インタフェース毎の制御

ここから先はiptablesがどのようにルールを適用させていくか理解しておかないと面倒になってきます。 参考資料3の中にある図で確認するのが良いでしょう。 この図は家からInternetに出ていく方向(outbound)か、Internetから家のLANに入ってくる方向(inbound)かによって、少し頭を切り替えて上部の"Packet in"のところから流れを追い掛ける必要があります。

既に設定されているMASQUERADEによるIPアドレスの変換は、outboundに対するPOSTROUTINGのタイミングで行なわれています。その手前で行なうfilter TableでのFORWARD Chainでの制御は家のLAN内部で通用するIPアドレスを使って制御する事ができます。

逆にinboundでの外部からLAN内部の機器に対するIPアドレス変換を行なう場合は、PREROUTINGのタイミングで設定すると、指定するIPアドレスがLAN内部のアドレスで良くなります。

またこの図からalixでのINPUT, OUTPUT Chainの設定は、ailx自身からの接続に対してだけ適用される事もわかります。単純にalixを通過するだけの通信は、inbound, outbound共にFORWARD Chainが適用される事になります。

alixを通過する通信もalix自身の通信も、個別に制御する必要性は低いと思います。 そのため3つを制御するのではなく、inboundとoutboundで制御する方法を選択する事にしました。

ここではppp0, ppp1などのインタフェース(ppp+)を通過するパケットを、inbound, outboundそれぞれinppp, outpppという名前で制御する事にします。

iptables.sh 続き2

...
iptables -A logaccept -j ACCEPT

iptables -N inppp
iptables -N outppp

iptables -A INPUT   -i ppp+ -j inppp
iptables -A FORWARD -i ppp+ -j inppp
iptables -A OUTPUT  -o ppp+ -j outppp
iptables -A FORWARD -o ppp+ -j outppp
...

eth1, eth2を通過するパケットも同じような名前をつける事で制御が可能ですが、 とりあえず現状では制御はしない事にします。

iptables.sh: 最後でeth1, eth2を許可するルール

...
iptables -A inppp -j logdrop
iptables -A outppp -j logdrop

iptables -A INPUT   -i eth1 -j ACCEPT
iptables -A OUTPUT  -o eth1 -j ACCEPT
iptables -A FORWARD -i eth1 -j ACCEPT

iptables -A INPUT   -i eth2 -j ACCEPT
iptables -A OUTPUT  -o eth2 -j ACCEPT
iptables -A FORWARD -i eth2 -j ACCEPT

FORWARD Chainに-o eth1-o eth2を追加していないのは、Internetを含めた外部からの通信がeth1やeth2を通る可能性があるからで、そのようなパケットはinpppoutpppで許可し、漏れたものは拒否するようにしたかったからです。

外部設定ファイルによるinbound接続の許可

さて少し順序が逆になりますが、外部へwebサーバーを公開するといった手段のために、外部の設定ファイルを使ってinboundの接続を許可しようと思います。

とりあえず外部からデスクトップPC(192.168.1.10)の22番(ssh)ポートへのアクセスを許可するための設定は次のようになります。

参考:外部からのinbound ssh接続を許可するコード

iptables -t nat -A PREROUTING -p tcp -i ppp+ --dport 22 -j DNAT --to-destination 192.168.1.10
iptables -A inppp -p tcp --dport 22 -d 192.168.1.10 -j ACCEPT

PREROUTINGでとりあえず192.168.1.1から192.168.1.10への接続のように変換し、その上で改めて192.168.1.10への接続の許可を与えています。

これを踏まえて、/usr/local/etc/iptables.inboundに次のようなルールを記述しました。

/usr/local/etc/iptables.inbound

tcp,22,192.168.1.210

iptables.sh 設定ファイルを読み込む処理

...
iptables -A FORWARD -o ppp+ -j outppp

OIFS=${IFS}
INBOUND_FILE="/usr/local/etc/iptables.inbound"
if test -f "${INBOUND_FILE}"; then
  IFS=$'\n'
  for line in $(cat "${INBOUND_FILE}")
  do
    ( echo $line | egrep ^# ) > /dev/null && continue
    proto="$(echo $line | awk -F, '{print $1}')"
    dport="$(echo $line | awk -F, '{print $2}')"
    dipaddr="$(echo $line | awk -F, '{print $3}')"
    if test "${proto}" = "tcp" -o "${proto}" = "udp"; then
      iptables -t nat -A PREROUTING -p "${proto}" -i ppp+ --dport "${dport}" -j DNAT --to-destination "${dipaddr}"
      iptables -A into -p "${proto}" --dport "${dport}" -d "${dipaddr}" -j ACCEPT
    fi
  done
fi
IFS=${OIFS}

iptables -A inppp -j logdrop
...

まとめ

とりあえずここまでのコードの断片を繋げると、それなりに動くものができていると思います。

0 件のコメント: