ラベル alix の投稿を表示しています。 すべての投稿を表示
ラベル alix の投稿を表示しています。 すべての投稿を表示

2011/01/09

IPSetを使ったFirewallルールの設定について

以前家のブロードバンドルータにしているAlixにIPSetを導入するところまでを、「ブロードバンドルータにしているalixをDebian squeezeにして、ipsetを導入する」に投稿していました。

しばらく前にいわゆるボットネットの一部といわれているIPとの接続を弾くためにiptablesルールを設定仕直したので、そのログをまとめておきます。

現状のiptablesの設定状況

IPSetの導入と平行してiptablesルールの見直しを行なって、起動時にはiptables-restoreを使ってルールを設定しています。

設定のメインは/etc/network/if-pre-up.d/iptablesスクリプトで、if-pre-up.dディレクトリにあるスクリプトは起動時などifupによってネットワークデバイスが設定される直前に実行されます。

本当は重複して実行される可能性のある/etc/network/if-*.dに配置するのはスマートじゃないけど、iptables/ipsetの設定はNICが認識されているかどうかに無関係に実行できるので、重複起動は気にしないことにしました。

他には/etc/rc2.d/なんかにスクリプトを配置することもできると思います。 ここら辺はdebianで標準的な場所がないので、ホストの使い方によって変化するかもしれません。

もしWorkstationならネットワークデバイスの初期化云々はそれほど重要ではないので、/etc/rc.local辺りで設定をするかもしれません。

常時接続のルータは意図せずiptables設定なしにネットワークに接続するのは嫌なので、if-pre-up.dを使いました。

iptables設定スクリプトと参照するファイル群

このスクリプトの配置場所はいくつかの候補の中から選択するしかありませんが、ファイル名と内容は自由に書けるので、内部でiptables-restoreコマンド等を実行するようにしました。

スクリプトの中身は次のようになっています。

/etc/network/if-pre-up.d/iptablesファイル全体

#!/bin/bash

PATH=/sbin:/usr/sbin
BASEDIR="$(dirname $0)"

ipset --restore < "${BASEDIR}/../ipset.restored"
iptables-restore < "${BASEDIR}/../iptables.restored"
ip6tables-restore < "${BASEDIR}/../ip6tables.restored"

スクリプトでは/etc/network/直下に3つのファイルがある事を前提にしています。

  • /etc/network/ipset.restored
  • /etc/network/iptables.restored
  • /etc/network/ip6tables.restored

今回はip6tablesは範囲外です。ipsetとiptablesの連携について書いていきます。

iptablesとの連携

直接"iptables.restored"ファイルを書く事もできますが、いろいろ危険なのでiptablesコマンドを使って設定した後に"iptables.restored"ファイルを保存しています。

ipsetとの連携は、まずiptables側で空のipsetルールを作成して、それを参照するiptablesルールを加えます。

ipset,iptablesコマンドを使った空のipsetルールを使ったルール作り

#!/bin/bash
PATH=/sbin:/usr/sbin:/bin:/usr/bin

## delete all rules
iptables -F
iptables -t nat -F
iptables -X
iptables -t nat -X
iptables -Z
ipset -F
ipset -X
...
ipset -N denyip iphash --hashsize 36864
ipset -N denynet nethash --hashsize 36864
iptables -A INPUT -m set --match-set denyip src -j DROP
iptables -A INPUT -m set --match-set denynet src -j DROP
iptables -A FORWARD -m set --match-set denyip src -j DROP
iptables -A FORWARD -m set --match-set denynet src -j DROP
iptables -A OUTPUT -m set --match-set denyip dst -j DROP
iptables -A OUTPUT -m set --match-set denynet dst -j DROP
iptables -A FORWARD -m set --match-set denyip dst -j DROP
iptables -A FORWARD -m set --match-set denynet dst -j DROP
...

このスクリプトを実行して、動きに問題がない事を確認してからiptables.restoredファイルを作成しておきます。

$ sudo /sbin/iptables-save > /etc/network/iptables.restored

次にipsetを使い、定義だけされている空のipsetルールに具体的な設定を加えていきます。

まず用意するのはボットネットに組み込まれていると思われるIPアドレスのリスト。

iptables.deny.outboundファイル抜粋

##
## comment string, this line should begin with the '#' char.
109.10x.23x.1xx
...
109.23x.22x.0/24
...

このファイルを処理する

#!/bin/bash
PATH=/sbin:/usr/sbin:/bin:/usr/bin
BASEDIR="$(dirname $0)"
OUTBOUND_BLOCK_FILE="${BASEDIR}/iptables.deny.outbound"

ipset -N tmpip iphash --hashsize 36864
ipset -N tmpnet nethash --hashsize 36864
if test -f "${OUTBOUND_BLOCK_FILE}"; then
  while read ipaddr
  do
    ( echo ${ipaddr} | egrep ^# > /dev/null ) && continue
    ip="$(echo ${ipaddr}|awk -F/ '{print $1}')"
    mask="$(echo ${ipaddr}|awk -F/ '{print $2}')"
    if test "${ip}" != "" -a "${mask}" = "" ; then
      ipset -A tmpip "${ipaddr}"
    else
      ipset -A tmpnet "${ipaddr}"
    fi
  done < "${OUTBOUND_BLOCK_FILE}"
fi
ipset -W denyip tmpip
ipset -W denynet tmpnet
ipset -X tmpip
ipset -X tmpnet

設定したipsetルールは--saveオプションを使って保存します。

$ sudo ipset --save > etc/network/iptables.restored

こういう形でスクリプトを分けたのにはいくつか理由がありますが、ipset/iptablesコマンドを利用して大量のIPアドレスを処理するルールを追加するのにはかなり時間がかかるので処理を分けたかったのが大きな理由です。

次はipsetを使ってみたかったということ。

iptablesを使う事も可能ですが、空のipsetルールを設定したように、空のchainを作成しておいて、後からそのchainに個別のIPアドレスをDROPするルールを追加していくのが良いでしょう。

そうしないと一連のiptablesコマンドの実行が完了するまでの間は、ネットワークに対して脆弱なまま接続する事になるかもしれません。

もちろん直接iptables-restoreコマンドが解釈するようなファイルを生成して時間を短縮することは可能です。

いずれの方法でも、テストを十分にして、いきなり起動時に読み込まれるファイルとして保存しない事が重要です。

ここでリストの挙げられているIPアドレスからの接続を拒否することよりも、そういったサイトへの接続を拒否する事の方が重要です。 もっとも接続してはいけないマスター系ノードのIPアドレスは変化するでしょうし、知られていないものがあるでしょうから、本当にこういうリストが有用なのかは少し疑問が残ります。

何もしないよりはましかな。

2010/12/22

ブロードバンドルータにしているalixをDebian squeezeにして、ipsetを導入す る

マルウェアに感染していたりボットネットのものといわれているIPアドレス群が公開されていたりします。 そういったノードとの通信を拒否するためには、数千のIPアドレス/ネットワークを制御する必要があります。

普通にiptablesを使ってルールを追加するとルールを設定するタイミングでハングアップしそうになるため、ipsetを導入することにしました。

いままでDebian lenny(5.0.x)を使ってきましたが、ipset-sourceパッケージを利用するために Debian squeeze(6.0.x)に移行しようというのが今回の作業です。

Debian squeezeへのアップグレード

Gatewayとなるalixへはgccなどの開発環境をインストールしていないため、別のalixでまずsqueezeに移行し、ipset用のdebモジュールを開発する事にしました。

開発機から本番機にipset debパッケージを転送する様子

基本的にはetchからlennyに移行した時と方法は同じですが、googleでも他の事例を検索してみました。 だいたいはapt-get dist-upgradeではなく、aptitude full-upgradeを使っているようでした。

もしalixのような最小構成で使っているのでなければ、間違いなくaptitudeがお勧めですが、今回はいつもどおりapt-getを使いました。

書き換えたsources.listは以下のとおりです。

/etc/apt/sources.lstファイル全体

deb http://ftp.jp.debian.org/debian squeeze main contrib non-free
deb-src http://ftp.jp.debian.org/debian squeeze main contrib non-free

deb http://security.debian.org/debian-security squeeze/updates main contrib non-free
deb-src http://security.debian.org/debian-security squeeze/updates main contrib non-free
$ sudo apt-get update
$ sudo apt-get install apt
$ sudo apt-get dist-upgrade

途中で新しい設定ファイルと置き換えるかどうかは、 /etc/init.d/openvpn以外は全て差分(Diff)を確認して、デフォルトの'N'を選択しました。

grub2への移行

今回の方法でsqueezeへ移行するとgrub2が導入され、とりあえず従来のgrub1からgrub2を経由してカーネルが起動する状態になります。

grub2の設定は/boot/grub/grub.cfgファイルで行ないますが、serial行は次のようにword,parityを設定する必要がありました。

serial --speed=38400 --word=8 --parity=no
terminal_input serial
terminal_output serial
set timeout=5

/boot/grub/grub.cfgファイルを直接編集するのではなく、/etc/default/grubを編集し、$ sudo grub-mkconfig を実行します。

/etc/default/grubは次のようになっています。

# This file is sourced by update-grub, and its variables are propagated
# to its children in /etc/grub.d/
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX="console=ttyS0,38400n8"
GRUB_SERIAL_COMMAND="serial --speed=38400 --word=8 --parity=no"

# Uncomment to disable graphical terminal (grub-pc only)
GRUB_TERMINAL=serial

# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true

grub1を経由して無事に起動する事を確認した後は、コマンド、upgrade-from-grub-legacyによって直接grub2のブートローダがMaster Boot Record(MBR)あるいはパーティションの先頭セクタに導入することができ、再起動すればgrub1に代わりgrub2が起動することになります。

grub2には変更なく移行できたものの…

手元の環境では grub1 が /boot/grub/menu.lst から起動する時に、 default 0の設定から /boot/vmlinuz-2.6.26-2-686 が起動していました。

"title Chainload into GRUB 2"と書かれている2番目のtitle行を指すために、 default 1に変更して再起動し、無事に grub2 から起動する事を確認しました。

アップグレードに伴なうディスクの増加

もろもろデータをいれて8GBのCompactFlashのうち、約半分の4GBほどを使用していましたが、導入によって一時的に800MBほど増加しました。

Gatewayにするalixは2GBのCompact Flashを使っていて、こちらも約半分の900MBほどを使っていましたが、導入時には400MBほど増加しました。

$ sudo apt-get dist-upgrade
...
アップグレード: 212 個、新規インストール: 53 個、削除: 4 個、保留: 0 個。
167 MB のアーカイブを取得する必要があります。
この操作後に追加で 230 MB のディスク容量が消費されます。

導入後のdf出力は次のとおりです。

Filesystem           1K-ブロック    使用   使用可 使用% マウント位置
/dev/sda1              1942036   1468316    375848  80% /
tmpfs                   127432         0    127432   0% /lib/init/rw
udev                    123088        60    123028   1% /dev
tmpfs                   127432         4    127428   1% /dev/shm

IPSetモジュールの作成と導入

Squeezeにしたのはipsetのモジュールをm-a(module-assistantコマンド)を使って導入するためでした。

m-aはdebパッケージを作成してくれるため、本番機に開発環境を入れることなく、他の開発機でdebパッケージを作成します。

$ sudo apt-get install ipset-source module-assistant

これで/usr/src/ipset.tar.bz2ファイルが導入されるはずです。 次にコンパイルに必要なファイル群を導入します。

$ sudo m-a prepare

prepareを実行すると、kernel moduleをコンパイルするために必要なパッケージが一式導入されます。 既に開発環境は整っているため、現在使っているsqueezeの標準kernel 2.6.32-5-common に対応するヘッダファイル群だけが導入される事になりました。

以下の特別パッケージがインストールされます:
  linux-headers-2.6.32-5-common linux-kbuild-2.6.32
以下のパッケージが新たにインストールされます:
  linux-headers-2.6.32-5-686 linux-headers-2.6.32-5-common linux-kbuild-2.6.32

次にipsetのmoduleを作成します。

$ sudo m-a build ipset

終ると /usr/src/ipset-modules-2.6.32-5-686_2.5.0-1+2.6.32-29_i386.deb が作成されるので、ブロードバンドルータにしているalixに転送して導入しました。

2010/10/01

無線LANからのアクセスをインターネットとの接続に制限する

FONの無線LANルータに限らず、最近のブロードバンドルータは無線LANクライアントを内部LANには接続させずにインターネットとの接続だけに限定する事ができるようになっています。

ブロードバンドルータを流用して無線LANのAccess Point(AP)にしていますが、どうやらルータ機能をONにしないと内部LANへの接続制限は有効にできないようだったので、iptablesを使ってeth2とeth1との接続を制限することにしました。

現状

ネットワークのおおまかな構成は次のようになっています。

おおまかなネットワークの構成図

加える制限の内容

現在はeth1,eth2間の通信は双方向で制限がなくなっているので、無線LANクライアントが接続する192.168.10.0/24ネットワークとの通信は双方向で遮断します。

ここまではiptablesのFORWARDチェインの話で、eth2内部の機器とalix自身の通信についてはもう少し検討が必要になります。

まずメンテナンスのためにALIXからeth2への通信は原則として自由に行なえるようにしました。 eth2からALIXへの接続についてはUDP経由でのDNS(port:53)とNTP(port:123)に限定しています。

また別の図にすると、次のような感じでしょうか。

ネットワークインタフェース間のフィルタールール

事前準備

ALIXはゲートウェイなので、NTPやらDNSやらの機能は持たせていませんでしたが、機器の数も限られているので無線LANクライアント向けにNTPとDNSの機能を提供することにしました。

ntpについてはlisten行を追加しています。

/etc/openntpd/ntpd.confの追加内容

listen on 192.168.10.1

DNSはforwarderに192.168.1/24にあるDNSサーバを追加したキャッシングサーバなので、 linsten-on, listen-on-ipv6行にeth1のインタフェースを指定して、allow-query行に192.168.10/24を追加したぐらいでしょうか。

iptablesの設定方法

以前から使っていた設定スクリプトにルールを追加しました。

iptables設定スクリプトに追加したルール

iptables -A FORWARD -i eth+ -o eth2 -j DROP
iptables -A FORWARD -i eth2 -o eth+ -j DROP
iptables -A FORWARD -i tun+ -o eth2 -j DROP
iptables -A FORWARD -i eth2 -o tun+ -j DROP
iptables -A INPUT -i eth2 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i eth2 -p udp --dport 53 -j ACCEPT
iptables -A INPUT -i eth2 -p udp --dport 123 -j ACCEPT
iptables -A INPUT -i eth2 -j DROP
iptables -A OUTPUT -o eth2 -m state --state NEW -j ACCEPT
iptables -A OUTPUT -o eth2 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -o eth2 -j DROP

とりあえず動いていますが…

直接INPUTチェインを変更していてiptablesらしくないので、これは少し構成を変更しようと思っています。

2010/09/30

自前ブロードバンドルータからフレッツスクウェアに接続してみる

alixで作成したdebianベースのブロードバンドルータを使っていますが、いままでマルチセッションの設定は行なっていませんでした。

フレッツ光メンバーズクラブのダイレクトメールが届いた機会に、マルチセッションでフレッツスクウェアにアクセスするように設定をしました。

平行してNintendoDS用にWEPで構成している無線LANのアクセスポイントをLANから切り離してインターネットとの通信だけを行なうようにしたので、それは改めて別の記事にまとめます。

2010/10/12追記: この記事でのppp周りのスクリプトには十分でない処理があり正しく動作しません。 その点をフォローするように書き直す予定ですが、規模が膨らむため時間がかかりそうです。 そのためこの記事を修正していて、修正個所はこの囲み(Note)でフォローしています。

作業の流れ

今回行なった作業は次のような流れになりました。

  • /etc/ppp以下のファイルを編集して、パスワードなどのflets用PPPoE設定を加える
  • /etc/network/interfacesを編集して、flets用ネットワークデバイス(ppp1)の設定を加える
  • ルーティング情報の追加
  • DNSを変更して、fletsドメインの問合せ先を追加

DNSについてはeucalyptusのテストのために、ドメイン毎にforwarderのIPアドレスを指定できるようにしていたので、そこに"flets"ドメインの検索だけをNTTのDNSサーバに投げるようにしています。

一から作るのも難しくはないですが、/etc/resolv.confを取り敢えず修正すれば簡単なデバッグはすぐに出きる事も覚えておくと良いかもしれません。

PPPoEの設定の追加

alixで使っているPPPoE用のソフトウェアは、debian lennyなので ppp-2.4.4rel-10.1 + pppoe-3.8-3 です。

現在契約しているプロバイダとの接続はPPPoEで既に行なっているので、それを真似て設定ファイルを準備していきます。

接続用のパスワードを設定する

/etc/ppp/pap-secretsに追加した1行

"guest@flets" * "guest"
/etc/ppp/peers以下に設定ファイルを作成する

今回は flets という名前のファイルを作成することにしました。 この名前は/etc/network/interfacesファイルの中で使います。

dsl-providerはpppoeパッケージが配置するデフォルトの接続用ファイルで、既にISPに接続するために安定して動いている設定が入っています。

$ sudo ls /etc/ppp/peers/dsl-provider /etc/ppp/peers/flets

作成したfletsファイルの最下行に設定しておいた user行 をflets網へのアクセス用IDに変更します。

/etc/ppp/peers/fletsの変更した個所

user "guest@flets"

短い設定ファイルなのでコメントと空行を除いた内容を書いておきます。

sudo egrep -v '^#|^$' /etc/ppp/peers/fletsの実行結果

noipdefault
nodefaultroute
hide-password
lcp-echo-interval 20
lcp-echo-failure 3
connect /bin/true
noauth
persist
mtu 1454
noaccomp
default-asyncmap
plugin rp-pppoe.so eth0
user "guest@flets"

2010/10/12追記 "defaultroute"オプションを指定していると、ifup/ifdown時にppp0を巻き込んでデフォルトゲートウェイが変更されてしまいます。 今回は手動でルーティングを変更しますから、明示的に"nodefaultroute"に変更しました。

ここまででパスワードの登録と、それを呼び出す設定を追加しました。 この設定を有効にする設定をinterfacesファイルに設定します。

ネットワーク構成の追加

flets用のPPPoE接続をするように設定を追加します。

/etc/network/interfacesファイルの編集

次のような設定を追加します。

/etc/network/interfacesファイルへの追加内容


auto flets
iface flets inet ppp
  provider flets
  post-up /usr/local/sbin/ppp_flets_up.sh

ルーティングの設定

interfacesファイルに追加した ppp_flets_up.shスクリプト で、 ルーティングの削除や追加を行ないます。

ルーティング情報は、公式サイトの ルーティング情報で確認することができます。

これとは別に検索すると、 http://routing.flets/routing.htmlについての情報がみつかります。

routing.fletsは接続できない事にはアクセスできない情報ですし、サイトの役割も明確ではなかったので、公式サイトに載っている3つのルーティング情報を加えておく事にしました。

routing用スクリプト - Type 1

2010/10/12追記: "sleep 5"を入れているのは、ppp1デバイスが認識されておらず、routeコマンドの処理に失敗する場合があったために追加しました。

2010/10/12追記: 環境変数IFACEには物理デバイス名、LOGICALには論理デバイス名が入るはずでしたが、実際には両方ともが登録されていました。 動的に値を取得する方法は、とりあえずなさそうなので固定的に"ppp1"と決め打ちしています。

スクリプトの役割は閉じたネットワークであるfletsを向いたデフォルトルートの削除と、その閉じたネットワークに向かう限られたネットワークの追加です。

routing設定用スクリプト: /usr/local/sbin/ppp_flets_up.sh

#!/bin/bash
DEVICE="ppp1"
sleep 5
/sbin/route add -net 220.210.194.0 netmask 255.255.255.128 dev ${DEVICE}
/sbin/route add -net 220.210.198.0 netmask 255.255.255.192 dev ${DEVICE}
/sbin/route add -net 220.210.199.144 netmask 255.255.255.240 dev ${DEVICE}
routing用スクリプト - Type 2

この部分は上記の設定で十分なので参考程度に留めてください。

後からwgetでコピーしたrouting.htmlを処理するためのスクリプトも組んでみました。 routing.htmlファイルはスクリプトが配置されている場所と同じディレクトリにあるとします。

ifupから呼び出された場合にはIFACE環境変数にデバイス名が入っているはずなので、ppp1ではない場合を想定して、これを利用しています。

routing設定用スクリプト using routing.html: /usr/local/sbin/ppp_flets_up.sh

#!/bin/bash
BASEDIR="$(dirname $0)"
DEVICE="ppp1"
sleep 5
while read line
do
  eval $(echo $line | awk -F, '/Route.=Add/ {print "/sbin/route add -net ",$2,"netmask",$3,"dev ${DEVICE}"}')
done < ${BASEDIR}/routing.html

環境変数IFACEが設定されていない場合にはexitするべきでしたね…。

DNSへのfletsドメインの追加

閉じたネットワークにあるwww.fletsやrouting.fletsサイトにアクセスするためにはIPアドレスが必要です。

今回は flets をドメイン名とするホストのIPアドレスの解決をBIND9任せることにしました。

公式サイトでターゲットにする DNSサーバのIPアドレスがわかります。 やったことは/etc/bind/named.conf.localファイルに設定を追加してDNSをリスタートしただけです。

/etc/bind/named.conf.localに追加した設定内容

zone "flets" {
  type forward;
  forward only;
  forwarders { 220.210.194.67; 220.210.194.68; };
};

全体のforwardersセクションにはISPのDNSサーバを書いています。 全部をfletsのDNSサーバに投げても良いのかもしれませんが、閉じた場所にあるDNSがどこまで信用できるものかわからないので、fletsドメインだけの名前解決に利用しました。

起動方法

設定が終ればinterfacesファイルに書いた設定名をifupコマンドに指定するだけです。

$ sudo ifup flets

まだ時々挙動がおかしい気がします。しばらく様子見です。

まとめ

今回は既に動いている alix上にあるブロードバンドルータを変更したので、少し前提事項がありました。

基本はdebianが動いているx86マシンなので、特に変な事はないと思いますが、fletsにアクセスする時にルーティングとDNS周りを解決するのがちょっと面倒かもしれません。

マシンが一台だけなら仕組みを作るよりも、その時だけ/etc/resolv.confを手で変更するとか、そもそもブロードバンドルータを手作りなんてしない、っていうのが良さそうです。

2010/04/13

またまたフラッシュメモリが使えなくなる

概要

しばらく前に 突然USBフラッシュメモリが使えなくなることがありましたが、 またalixのバックアップに使っているフラッシュメモリが書き込めない状態になりました。

今回はコジマで買ってきたKodakブランドのLexar製なUSBフラッシュメモリで、980円ぐらいの特価品だったと思います。

用途と症状

フラッシュメモリの領域にディレクトリやファイルを作成しようとすると、次のメッセージが表示されます。

mkdir: ディレクトリ `/misc/bk/test' を作成できません: デバイスに空き領域がありません

ext3フォーマットにして、pdumpfsでバックアップを取っていましたが、2/17〜4/03のデータを取得していました。 だいたい全体の半分まで使いきらないうちに使えなくなった事になります。

$ df -k /misc/bk

dfコマンドの出力

Filesystem           1K-ブロック    使用   使用可 使用% マウント位置
/dev/sdb1              3853472   1587748   2069976  44% /misc/bk

このフラッシュメモリは前回の記事の時に壊れたメモリの代りではなく、新しいalixに付けたものでした。

前回の記事の対応として、SONY製の4GBフラッシュメモリを買って、DNS/SVN/Apacheサーバとして使っているalixに接続していて、そちらは同様の使い方で、2/16〜4/13までのバックアップがちゃんと取れています。

こちらはまだ全体の30%(1GB前後)を使っているだけなので、このまま様子をみようと思います。

2010/03/07

hostapdをdaemontoolsで管理する

いままでプロトタイプ状態で動かしていたhostapdですが、ちゃんと安定して動かすためのサーバにmini-PCIカード(XG-601)の引っ越しをしました。 IPv6やらNIC周りの設定は/etc/network/interfacesのpost-up辺りに書けば良いんですけどね。 稼働し続けるサーバプロセスを動かすための仕組みは、一度起動して終りというわけにはいかないのが難しいところです。

アクセスのたびに起動すれば良い場合にはinetdやxinetdが使えるのですが、今回はずっと起動し続けるdaemonなのでrc.local辺りで一回起動して終りになってしまいそうです。 ALIXに入れたdebian lennyには、いずれも準備されていなかったのでdjbさん作成のdaemontoolsを入れてみました。

「DAEMON Tools」というとWindows用のISOイメージのマウントツールが検索でかなりヒットしますが、qmailで有名なdjbのツールの方が思い浮ぶのは歳を取り過ぎたのでしょうか。

daemontoolsを選んだ理由

hostapdは常に起動し続けるタイプのサーバプロセスなので、inetdやxinetdでは対応する事ができません。 inetdやxinetdはアクセスの都度プロセスを起動して処理が終ったら消えていくプロセスだけで、あまり一般的ではないですがsendmailやhttpdに対応できる応用範囲の広い仕組みです。

他にinetd, xinetで対応できないサーバプロセスは、OSとして必須のものを除くとfreeradius、ntpdぐらいでしょうか。 freeradiusやntpdはパッケージが起動スクリプトを/etc/rc.d/に配置してくれるので便利ですが、監視はされていないので異常停止した場合には手動で起動するまではサービスが提供できない時間帯(outage)が発生することになります。

気になるのであとで、freeradiusもdaemontoolsの

daemontoolsの情報源

本家のdaemontoolsページがまとまっていて、日本語だと本家の翻訳などはありますが、実用的な解説サイトはざっとみたところ見つかりませんでした。 かといってここで包括的な説明をしていくのは難しいので、hostapd + debian lennyを対象にしていきます。

hostapdをdaemontoolsで動かしていく

daemontoolsの導入

とりあえずdaemontoolsを導入します。 daemontools-runは/etc/inittabなども編集して、導入直後から管理プロセスを起動してくれます。

$ sudo apt-get install daemontools daemontools-run
別ディレクトリへの設定ファイルの配置

daemontoolsは導入した直後から/etc/serviceディレクトリをトップディレクトリとして既に稼働しています。 まずは/usr/local/serviceディレクトリを作成して、その中にhostapdディレクトリとrunという名前の起動用スクリプトを配置します。

$ sudo mkdir -p /usr/local/service/hostapd
$ sudo vi /usr/local/service/hostapd/run
$ sudo chmod +x /usr/local/service/hostapd/run

runファイル内容

#!/bin/bash

exec /usr/local/sbin/hostapd /usr/local/etc/hostapd.conf
/etc/serviceディレクトリへの配置

準備が終ったら、daemontoolsが監視している/etc/servicesディレクトリにシンボリックリンクを貼る事でhostapdを起動させます。 もし事前にhostapdが起動しているようであれば、killコマンドなどで停止させてください。

$ sudo ln -s /usr/local/service/hostapd /etc/service

ファイルを作成すると、5秒以内にhostapdプロセスが起動するはずです。 ひと呼吸置いてからプロセスが起動しているか確認しましょう。

$ ps auxww| egrep 'supervise|hostapd'

コマンド実行後の出力結果

root      1798  0.0  0.1   1492   276 ?        S    12:48   0:00 readproctitle service errors: ...ed to set Short Slot Time option in kernel driver?Could not set preamble for kernel driver?Failed to remove interface (ifidx=6).??starting?Configuration file: /usr/local/etc/hostapd.conf?Using interface wlan0 with hwaddr 00:60:b3:xx:xx:xx and ssid 'XXXXXXXX'?starting?Configuration file: /usr/local/etc/hostapd.conf?Using interface wlan0 with hwaddr 00:60:b3:xx:xx:xx and ssid 'XXXXXXXX'?starting?
root      2317  0.0  0.1   1504   328 ?        S    16:17   0:00 supervise hostapd
root      2895  0.0  0.6   4200  1764 ?        S    16:18   0:00 /usr/local/sbin/hostapd /usr/local/etc/hostapd.conf
...

/usr/local/serviceディレクトリを作成した理由は、次のセクションにまとめました。

稼働中のsuperviseプロセスを停止する際の注意点

最初は/etc/serviceディレクトリの直下にサブディレクトリを作成していたのですが、プロセスを停止する段階になって困ってしまいました。 本家のFAQには次のような記述があります。

http://cr.yp.to/daemontools/faq/create.html#remove

How do I remove a service? I want to eliminate /service/telnetd.
Answer:

     cd /service/telnetd
     rm /service/telnetd
     svc -dx . log

debian lennyでは"/service"は"/etc/service"に、"telnetd"は今回"hostapd"になるわけですが、直接"telnetd"(あるいは"hostapd")ディレクトリを作成しているとこの手順を実施する事はできません。 そしてどうやら代替手段もなく、手動でsuperviseプロセスをkillするしかなさそうです。

そんなわけで"/etc/service"直下はシンボリックリンクだけを集めるようにしましょう。

さいごに

djbさんが作成したツール群は非常に素晴しいのですが、作り手の個性が強烈に伝わってくる仕上りになっています。 良くも悪くも従来の概念の延長線上の斜め上ぐらいにあるので、使い手が、自身が対応できないために、使いづらく感じるのは事実だと思います。 まぁ機能が限定されすぎていて現状に合っていないとか、いろいろ全うな不満はあるとは思いますけどね。

それでも、しばらく前にコードの改変を許すようになったので、不満があるなら改造して使い易くするのは使い手の責任になっていますね。 うーんちょっとコードを変更するぐらいじゃだめだし、これと同等に安定した成果物を作れる自信はないです。

ちなみにqmailを題材にdjbさんが書かれた論文「Some thoughts on security after ten years of qmail 1.0」ではどのようにセキュアな(サーバ)アプリを作成するかが解説されています。

閑話休題:djbがパブリックドメインを宣言したというけれど

原文は本家のサイト「Placing documents into the public domain」にあります。

まぁ日本でパブリックドメインという概念は著作権上の財産権の放棄ぐらいでしか実現できなさそうだから、勝手にコードを取り込んだとしても名前を明記するなどの著作者人格権の尊重はしないといけないんだろうなぁ。

2010/03/06

セキュリティを考慮したOpenVPNの設定変更

ALIXのブロードバンドルータで動かしているOpenVPNについては、本家のHOWTOの中にセキュリティについての記述があります。

この中により強固なセキュリティを確保するために、暗号化アルゴリズムとしてAES-256-CBCも選択できるという記述がありました。 そこまでは必要ないとしても、よりセキュアな環境を作るために少し変更を加えていこうと思います。

テスト環境

今回は次のような環境で、図左の端末からPHS通信カード(AX420S)を使いOpenVPN経由で家のサーバに接続します。

OpenVPN Network Graph

tls-authを併用する

HOWTOには通常の認証に加えて追加の共通鍵を追加する事で,DoS攻撃への耐性を高めるなどいくつかの利点があると説明されています。 予防的なものですから、これを無神経に配ってもそれほど脅威ではないでしょうね。 低コストでそれなりの効果は期待できそうなので、導入することにします。

まずは有効にするためにサーバとクライアントで共有する鍵ファイルta.keyを生成します。

$ cd /etc/openvpn
$ sudo openvpn --genkey --secret ta.key

このファイルと同じものをクライアントとなるWindows XPにも転送します。 server.confやclient.confの先頭の方に次のように設定を加えていきます。

/etc/openvpn/server.confファイル

...
tls-server
tls-auth ta.key 0
...

C:\Program Files\OpenVPN\config\client.confファイル

...
tls-client
tls-auth ta.key 1
...

サーバ側をリスタート(/etc/init.d/openvpn restart)してから、クライアントから接続します。 Windows XP側の"View Log"でログを確認すると無事に認識されたようです。

client.logファイルから抜粋

...
Sat Mar 06 16:00:05 2010 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Sat Mar 06 16:00:05 2010 Control Channel Authentication: using 'ta.key' as a OpenVPN static key file
Sat Mar 06 16:00:05 2010 Outgoing Control Channel Authentication: Using 160 bit message hash 'SHA1' for HMAC authentication
...

udpの使用

TCPはパケットの再送などを上位レイヤで実現できるので、使う事ができる環境なら「UDPよりもTCP」という認識があるかもしれません。ただUDPはオーバヘッドが少ないですし、VPNの用途では望ましいのかもしれません。

HOWTOではproto udpを使う事でDoS攻撃に対してはUDPの方がより望ましい耐性を備えていると説明されています。

プロセス実行ユーザの変更

root以外のユーザでプロセスを実行する方法として、実行後に"nobody"などroot以外のユーザに権限を降格させる方法と、そもそもプロセスの起動自体をroot以外で行なう方法、最後にchroot環境が説明されています。

お勧め:実行後に権限を降格させる方法

よりお手軽な方法はapacheなどと同様にuser, group行でroot以外のユーザを指定する方法です。 当初から採用している方法はこれで、最低限これぐらいは必要でしょう。

Linux限定:最初からroot以外のユーザでプロセスを起動する方法

もう一方はプロセスの起動自体をroot以外のユーザにする方法です。 OpenVPNが使用するポートは特権ポートではないですから原理的には問題ないですが、 Linux環境のみで有効なこの方法は少しばかり複雑です。

セキュリティを高めるためにあまり複雑な方法を採用するのは、よくよく考慮する事が必要です。 この方法は悪くはないですし潜在的な脅威が高まるとは思えませんが、設定ファイルやTLS鍵ファイルの管理など、考慮するべき点は他にもあり、あまり報われない気がします。

プロセスがハイジャックされた場合を考えれば、より汎用的な次の方法がお勧めです。

chroot環境の利用

HOWTOで説明しているchroot環境は、プロセスが起動後に初期設定を終えてから特定のディレクトリに引きこもる方法です。 初期設定は終っているので、下記に挙げた例外を除けばserver.confファイルにchroot行を追加するだけです。

もしプロセスがハイジャックされても、そこから起動されたプログラムがアクセスできる範囲は、chrootで指定したディレクトリ以下に限定されるため、より安全になるはずです。

これもあまりにも簡単なのでuser, group行と合せてchrootも使うことにしました。

$ sudo mkdir /var/jail/openvpn

/etc/openvpn/server.confの最後に追加

...
## chroot after initialization
chroot /var/jail/openvpn

HOWTOでは動的に読み込まれる、crl-verify行に指定しているファイルとclient-config-dir行に指定しているディレクトリはchrootしたディレクトリをトップディレクトリとした場所に配置するよう書いていますが、これを使用していなければディレクトリを作成する以外の準備作業は不要です。

chrootに指定するディレクトリは任意なので/etc/openvpn以下にしたい場合もあると思いますが、openvpnプロセスがcoredumpした場合を考えると十分な余裕のあるファイルシステムにディレクトリに配置する事をお勧めします。

共通鍵は256bit程度、公開鍵は2048bitを使う

より長いサイズの鍵を使いましょうという話しがHOWTOにありますが特に説明は不要と思われます。

CAのプライベート鍵ファイルの管理

親CA、中間CAのプライベート鍵ファイル(demoCA/private/cakey.pem)の管理はサーバに配置しないようにして分離しておきましょう。 HOWTOにはフロッピーディスクなどに格納してネットワークアクセスを難しくするようアドバイスされています。 日本で無難な対応はMOやUSBメモリなどにdemoCAディレクトリ一式を配置して必要なタイミングでマウントするのが最善でしょう。

少し脱線して鍵の管理について…

まぁ個人レベルでそこまでする必要はないと思いますけどね。 少なくとも鍵ファイルを使うサーバ(Consumer)とdemoCAディレクトリ(Provider)を共存させるのはやめましょう。 企業でプライベートなCAを運用するのであれば、こういったメディアの管理に加えて部屋への物理的な入室制限も考えなければいけないところです。

まぁそれでも抜け道はいろいろ発生するんですよね。 より良い対応は手順を守らせるフレームワークの策定と、その手順を現実的に対応可能な範囲で四半期毎といった間隔で改訂していくことでしょう。

二人以上が共謀しないと漏洩しない仕組みは、割と低コストで実現可能な範囲に収まります。 物理的な鍵の管理と、論理的なパスワードを別々の人間が管理するだけで実現できますからね。 一般的な日本の会社だと責任の範囲だけ決めて、そういう仕組みが機能するようにはならないのかなぁ…。

さいごに

OpenVPNはお手軽にインターネット経由でVPNを実現可能にする仕組みで、RSA鍵の有効期限を短く設定するぐらいの考慮があれば比較的安全なインフラが構築できるでしょう。 問題はパフォーマンスですが

2010/03/05

MTU設定時の注意点

以前、 IPsecを試していた時にIPのフラグメントが原因と思われる通信障害が発生しました。 どうやらAlixで作成したブロードバンドルータのMTU設定がまずかったようです。

現状でも参考にした資料を全部読めていないのですが、いまの理解でまとめておこうと思います。

問題となった現象について

エラーメッセージ

VMWare上でTeamを組んで2つのサブネットをlinuxをルータにして接続してみました。 MTUをいろいろ変更している最中にsyslog経由で記録されたログは次のようになっています。

linuxでのメッセージ

...
Mar  3 10:05:40 lipsec01 kernel: [ 1778.358003] pmtu discovery on SA AH/09dd8188/0a000001
Mar  3 10:07:40 lipsec01 kernel: [ 1898.356663] pmtu discovery on SA AH/09dd8188/0a000001
...

netbsdでのメッセージ

...
wm0: discarding oversize frame (len=1502)
wm0: discarding oversize frame (len=1502)
...

どちらもMTUが適切に設定されていない事を示唆していますが、この時点ではまだその点にちゃんと気がついていませんでした。

設定を確認

実際に問題が発生したAlixブロードバンドルータを中心にしたネットワークは次のようになっていました。

Alixを中心にしたHomeLANの概要

192.168.1.0/24と192.168.10.0/24のサブネットのゲートウェイになるeth1, eth2のMTU値が1454になっていました。 MTU値1454はBフレッツで接続する際にNTTから指定されている値で、PPPデバイスに対しては正しいのですが、家の中で各サブネットに接続しているPCはMTU値1500が設定されているため、サブネット単位で不整合が発生しています。

対応策

サブネット単位でちゃんとMTUを揃えて対応は完了。

通常はこれで気になる事もなかったのですが、どういうわけかIPsecでAH+ESPヘッダを付与したところで顕著に影響が出てしまったようです。 IPsecはヘッダが付与される分パケットが長くなりがちですが、どうしてこれでIPsec以外の通信では問題にならなかったんだろう。

ああ、外部に出ていく時にはPPPのMTU値1454に合わせるから問題がなくて、家の中ではそもそも大きなファイルの転送をしなかったとかかな。いや、そんなことはない。

Path MTU Discoveryについて

今回の原因はMTUの不整合でした。 それとは別にMTU関連についていろいろ調べていく中で、alixを通した時にPath MTU(PMTU) Discovery(PMTUD)がちゃんと動いているのか確認する事にしまいた。

サブネット単位でMTU値を同じ値に設定した後に、ICMPパケットをtcpdumpを使って眺めてみました。 外部へ接続する時にはppp0に設定されているMTU値1454に合わせるよう、PMTUDが動いている様子がわかります。

$ sudo tcpdump -p -i eth0 icmp -vv -n

tcpdump出力ログ

...
21:26:29.868484 IP (tos 0xc0, ttl 64, id 18704, offset 0, flags [none], proto ICMP (1), length 576) 192.168.1.1 > 192.168.1.xxx: ICMP 210.xxx.yyy.zzz unreachable - need to frag (mtu 1454), length 556
	IP (tos 0x0, ttl 64, id 19526, offset 0, flags [DF], proto TCP (6), length 1499) 192.168.1.xxx.59612 > 210.xxx.yyy.zzz.80: P 3195263263:3195264710(1447) ack 810452849 win 90 <nop,nop,timestamp 89692924 674447780>[|icmp]
...

今回の反省

サブネット単位でMTUが揃っていなかったのは痛いミスでした。

今回の調査の中でPMTUDについても勉強しなおしてAlixの設定に反映させました。 まぁAlixでルータを作った時にはPMTUの事を考えずにICMPパケットを全て落していましたからね。

現在のiptables設定

現在ではいろいろな経緯で変更を加えて state モジュールをUDPとICMPにも適用した状態になっています。 またネットワーク内部を探査されないようにecho-requestとredirectについては明示的にDROPする設定にしています。

現在のiptables設定スクリプト

...
## [inppp] default rules
## - disallow some icmp inbound messages
iptables -A inppp -p icmp --icmp-type echo-request -j logdrop
iptables -A inppp -p icmp --icmp-type redirect -j logdrop
## - allow already used connections
iptables -A inppp -m state --state ESTABLISHED,RELATED -p tcp -j ACCEPT
iptables -A inppp -m state --state ESTABLISHED,RELATED -p udp -j logaccept
iptables -A inppp -m state --state ESTABLISHED,RELATED -p icmp -j logaccept

## [outppp] default rules
iptables -A outppp -m state --state ESTABLISHED,RELATED -p tcp -j ACCEPT
iptables -A outppp -m state --state ESTABLISHED,RELATED -p udp -j logaccept
iptables -A outppp -m state --state ESTABLISHED,RELATED -p icmp -j logaccept
iptables -A outppp -m state --state NEW -p tcp -j ACCEPT
iptables -A outppp -m state --state NEW -p udp -j logaccept
iptables -A outppp -m state --state NEW -p icmp -j logaccept
...

意図せず侵入を許したパケットは通過してしまうので、outboundに"ESTABLISHED,RELATED"を設定するのは少し問題なのかもしれません。 外部からのSSH接続などinboundを追加するタイミングで個別にoutboundを設定するよう変更するかな。

icmpは到達不可をちゃんと伝えるには必要そうだけど利便性の問題でたぶん必須ではないのだと思います。

2010/02/28

racoonによるIPsec環境の構築

freeradiusを触った時に経路の暗号化をしないと潜在的なリスクがあると思ったので、IPsecをtransport modeで試してみる事にしました。

今回の鍵作成の作業はApache2用にSSLキーを作成した時と同じで、Ubuntu 8.04 LTSで行なっています。作成する鍵ファイルは次の3種類です。

  • newreq.pem - リクエストファイル。秘密鍵ファイルを作成するために使用
  • newkey.pem - 秘密鍵ファイル。newreq.pemを元にCAが署名し、生成される
  • newcert.pem - 公開鍵ファイル。newkey.pemと対になる

opensslやCA.plを使った鍵作成の手順を説明したサイトはいくつかありますが、独自のファイル名を使うため鍵ファイルの用途がわかりずらいと感じました。 この記事ではCA.plが作成するファイル名を踏襲しました。

今回の作業全体の流れは7.3.3.2 公開鍵を使った方法にある流れに従っています。 ただ、これだけでは不十分だったので、より正確な情報をIPsec HowtoThe PKIX certificate with racoon、それにipsec-tools.sourceforge.netからのリンク先にあるAutomatic keyingから補完しました。

なおIPsecが稼働するサーバはAlixとWRAPで稼働するDebian lennyです。

鍵ファイルの作成

CAとしてはApacheサーバ用にSSL用鍵ファイルを作成した時に使ったdemoCAをずっと使っていますが、手を広げ過ぎたので鍵ファイルの管理が煩雑になってきました。

そこで今回はIPsec用に中間CAを作成するところから始めました。 必要がなければ飛して既にあるdemoCAディレクトリを使えば良いと思います。

中間CA用demoCAの作成

まずは/usr/lib/ssl/misc/CA.plを使って、土台となるdemoCAディレクトリを準備します。 いま親CAのdemoCAディレクトリがみえるディレクトリにいるとして、中間CA用にサブディレクトリを作成する事にします。

$ mkdir demoCA.ipseq_ca
$ cd demoCA.ipseq_ca
$ /usr/lib/ssl/misc/CA.pl -newca

入力項目は任意なのでパスワードだけ忘れないようにしておきます。 この作業が終るとカレントディレクトリにdemoCAディレクトリが作成されています。

親CAによる中間CA用reqファイルの署名

いまはdemoCA.ipseq_caディレクトリ直下にいて、そこには"demoCA"ディレクトリだけが存在している状態を仮定します。

このディレクトリの中で単独のCAとして必要なものは揃っていますが、中間CAとなるために親CAに署名をしてもらう必要があります。 通常CAを作成するときに使うCA.pl -newcaコマンドでは、リクエストファイルを作成して、リクエストファイルへの署名をdemoCA内にある自身の秘密鍵で行なっています。

今回はその署名作業を親CAに行なってもらう必要があるので、いちど作成したcacert.pemを捨てて、改めてcareq.pemファイルを親CAに署名してもらいます。

$ cp demoCA/careq.pem ../newreq.pem
$ cd ..
$ /usr/lib/ssl/misc/CA.pl -signCA

ここまでの作業で"newreq.pem"ファイルが新たに作成されました。 これを中間CAのdemoCAディレクトリにコピーします。

$ cd demoCA.ipseq_ca
$ rm demoCA/cacert.pem 
$ mv ../newcert.pem demoCA/cacert.pem

ここでcacert.pemを削除せずに上書きしても良いのですが、作業内容をはっきりとさせるために削除するようにしています。

作業が終ったらnewreq.pemファイルは削除しておきます。

$ rm ../newreq.pem

これで中間CAを作成する作業は完了です。

IPsec用の鍵ファイルの作成

中間CAはできたので、IPsecで2点間の通信を暗号化するための鍵ファイルを作成します。 ここでは最終的に"newkey.pem", "newcert.pem"の2つのファイルを1セットとして作成します。 サーバ2台でテストをするために、合計で4ファイル、2セットを作成する事になります。

$ /usr/lib/ssl/misc/CA.pl -newreq
$ /usr/lib/ssl/misc/CA.pl -sign
$ mkdir server1
$ mv new* server1/

作業ではCommon Name(CN)をサーバのFQDNにしておきます。 作業の結果作成された"newkey.pem", "newcert.pem"ファイルをサーバ毎のディレクトリに分けておき、必要に応じて使う事にします。

続けて"server1"を"server2"と読み替えて、もう一度、この手順を繰り返します。

最後にserver1ディレクトリにserver2のnewcert.pemファイルをコピー(server2.newcert.pem)し、server2ディレクトリにも同様にserver1のnewcert.pemファイルをコピー(server1.newcert.pem)します。

ここまででserver1, server2ディレクトリにそれぞれ"newkey.pem"と"newcert.pem"ファイルと相手のnewcert.pemファイルが存在しているはずです。 treeコマンドを使うと、次のようなディレクトリ構造になっているはずです。

treeコマンド出力

|-- demoCA ## 親CA
|   |-- cacert.pem
.   .
.   .
.   `-- careq.pem
.   
`-- demoCA.ipsec_ca ## 中間CA
    |-- demoCA
    .
    .
    |-- server1/
    |   |-- newcert.pem
    |   |-- server2.newcert.pem
    |   `-- newkey.pem
    `-- server2/
       |-- newcert.pem
       |-- server1.newcert.pem
       `-- newkey.pem

実際のところはnewcert.pemとnewkey.pemが2組作れていれば良いので、この構造は参考に留めておいてください。 いろいろな鍵ファイルを作る手順をみていると、どこまでがMUSTで、どこからがSHOULDなのか判らないですよね…。

公開鍵方式を使ったIPsec接続

今回はracoonを使った鍵交換を行なうため、双方のシステムにracoonをインストールしておきます。

$ sudo apt-get install racoon
鍵ファイルの配置

サーバにserver1, server2ディレクトリをそれぞれコピーし、作成した鍵ファイルを/etc/racoon/certsディレクトリにコピーしておきます。 なお本当の環境では、サーバの秘密鍵"newkey.pem"はネットワークを経由する事はありません。相手側に渡さないように注意しましょう。

$ sudo mkdir /etc/racoon/certs
$ sudo cd server1
$ sudo cp newkey.pem newcert.pem server2.newcert.pem /etc/racoon/certs/

他方のserver2サーバにログインして、同様にファイルを配置します。

racoon.confファイルの作成

ファイルの設定方法は本家サイトのドキュメントが信頼性が高そうです。 この他のドキュメントはKAMEの流れを組む*BSD用だったり、少し古いとそのまま使えないものもありました。

exchange_mode main;を使う場合には、IPアドレスでの指定だけが使えるため、IPv6とIPv4の設定を分けていますが、内容は同じです。

server2側の/etc/racoon/racoon.confファイル

path certificate "/etc/racoon/certs";
remote 192.168.1.xx {
        exchange_mode main;
        my_identifier asn1dn;
        peers_identifier asn1dn;

        certificate_type x509 "newcert.pem" "newkey.pem";
	peers_certfile x509 "server1.newcert.pem";
	verify_cert on;
        proposal {
                encryption_algorithm aes;
                hash_algorithm sha1;
                authentication_method rsasig;
                dh_group modp1024;
        }
        generate_policy on;
	passive off;
}
remote 2001:03xx:xxxx:1::xxxx {
        exchange_mode main;
        my_identifier asn1dn;
        peers_identifier asn1dn;

        certificate_type x509 "newcert.pem" "newkey.pem";
        peers_certfile x509 "server1.newcert.pem";
	verify_cert on;
        proposal {
                encryption_algorithm aes;
                hash_algorithm sha1;
                authentication_method rsasig;
                dh_group modp1024;
        }
        generate_policy on;
        passive off;
}

sainfo anonymous {
        pfs_group modp768;
        encryption_algorithm rijndael, 3des;
        authentication_algorithm hmac_sha1, hmac_md5;
        compression_algorithm deflate;
}
CRLファイルの取り扱いについて

いまのところは、CRLやcacert.pemファイルの取り扱いについて、ちゃんとしたドキュメントをみつけられていません。 racoon.confのオプションには関連しそうなca_typeパラメータがありますが、使い方について明確な指示はみつけられませんでした。verify_certは相手方から送られてくるDNと手元のcertファイルのDNを比較するもののようで関係なさそうでした。

コードを少し追った限りではCRLのチェックはしているようにみえました。 しかしrevokeしたnewcert.pemファイルはpeers_certfileに指定した状態で、CRLファイルを配置してもhashファイルへのリンクを作成しても通信が拒否される事ありませんでした。

ハッシュファイルへのシンボリックリンクはc_rehashコマンドで行なうことができます。

$ sudo c_rehash /etc/racoon/certs

ここら辺の動きについてはもう少し確認が必要そうです。

/etc/racconディレクトリの内容

treeコマンドを使うと、現在の/etc/racoonディレクトリの構造は次のようになっています。

tree /etc/racoonの実行結果

/etc/racoon/
|-- certs
|   |-- server2.newcert.pem
|   |-- newcert.pem
|   `-- newkey.pem
`-- racoon.conf
ipsec-tools.confファイルの編集

少し古いドキュメントには/etc/setkey.confを編集する手順が載っていますが、Debian Lennyでは/etc/ipsec-tools.confファイルを編集します。 起動時に/etc/init.d/setkeyスクリプトが、/etc/rc2.d/S??setkey経由で実行されて、ipsec-tools.confの内容が反映されることになります。

/etc/ipsec-tools.confファイル

#!/usr/sbin/setkey -f
#
flush;
spdflush;

## out rule (local -> remote)
## IPv4
spdadd 192.168.10.xx 192.168.1.yy any -P out ipsec
           esp/transport//require;
## IPv6
spdadd 2001:03xx:xxxx:10::xxxx 2001:03xx:xxxx:1::yyyy any -P out ipsec
           esp/transport//require;

## in rule (remote -> local)
## IPv4
spdadd 192.168.1.yy 192.168.10.xx any -P in ipsec
           esp/transport//require;
## IPv6
spdadd 2001:03xx:xxxx:1::yyyy 2001:03xx:xxxx:10::xxxx any -P in ipsec
           esp/transport//require;
稼働確認

両方のサーバで次のようにプロセスを再起動する事で設定が完了します。

$ /etc/init.d/setkey restart
$ /etc/init.d/racoon restart

さいごに

IPsecを使ってみて気になったことをまとめておきます。

煩雑な設定

相手方を指定するところでは個別にpeers_certfileなどを指定する必要があるため、サイト全体に拡張しようとするとかなり煩雑になりそうです。 ファイルを互いに交換する仕組みだと鍵交換がn*(n-1)回必要になるので公開鍵方式の利点を殺したような状態になっています。

まぁネットワーク層レベルの仕組みですから、何を信頼するかという議論もあると思うんですけどね。

とはいえracoon.confファイルはincludeで、外部ファイルを読み込む事ができるので、他で検証した設定ファイルとpemファイル一式を展開してあげれば、だいたい安全に自動化ができそうな気配です。

しかしpeers_certfileはdnssecを引数に取るので、これが使えるかもしれないと思っています。 時間をみつけて検証していこうと思います。

おかしな挙動

試したdebian lenny同士の接続では、ipsec-tools.confでAHを有効(ah/transport//require;付き)にした状態で、しばらく使っていると反応がなくなりハングアップしてしまいました。

うまく動かなかったipsec-tools.conf設定

...
## IPv6
spdadd 2001:03xx:xxxx:10::xxxx 2001:03xx:xxxx:1::yyyy any -P out ipsec
    esp/transport//require
    ah/transport//require;
...

これはpmtu関連のメッセージが大量にでていて、どうやら途中の経路にあるルータのMTUが1500よりも低いことが原因のようでした。 このルータをはさまないようにスイッチングハブのみを経由して互いを接続したところ、この問題は発生しませんでした。

ただIPv4は無事に接続できたのですが、IPv6だけはエラーになってしまいました。

racoonのエラーメッセージ

...
Mar  1 08:59:26 localhost racoon: ERROR: phase1 negotiation failed due to time up. ac92e25b5eb92084:0000000000000000
Mar  1 08:59:37 localhost racoon: ERROR: phase2 negotiation failed due to time up waiting for phase1. ESP 2001:3e0:a31:10:20d:b9ff:fe12:f284[500]->2001:3e0:a31:10:20d:b9ff:fe03:4154[500]
...

これはracoonが正常に動いていないようにみえるのですが、ともかく設定を再び行ないます。

$ sudo /etc/init.d/racoon stop
$ sudo /usr/sbin/setkey -F
$ sudo /usr/sbin/setkey -FP

2,3分ほどしたら再び起動します。

$ sudo /etc/init.d/racoon start

両方のサーバでracoonが起動した事を確認します。 次にsetkeyでSPDを更新します。

$ sudo /etc/init.d/setkey restart

これで無事にIPv4, IPv6ともにtransport modeでIPsecを動かす事ができました。 たぶん/etc/ipsec-tools.confでAHを有効したり、無効にしたり編集している間におかしくなったんだと思います。

それにしても元々の問題はpmtuが正常に動けば問題ないはずなのですが、この直結に近い状態でもmtuの高い側から低い側へ接続すると比較的データ量の多そうな通信の応答がなくなってしまいます。 これはVMWare上でも同様の動きをしています。 tcpdumpでみるとARPやらICMPv6 Neighbor Discoveryには正常に反応しているので、接続は切れずにこのまま画面が固まり続けるんでしょうね。

試しにこの状態で$ sudo setkey -F$ sudo setkey -FPを実行すると、反応は戻ってきました。 /etc/init.d/setkeyをリスタートしてみると、やはりまた元に戻ってしまいます…。 う〜ん、困ったなぁ…。

racoonプロセスの稼働状況

sshで相手方に接続している最中にどちらかのracoonプロセスを再起動すると、次のログのようにSADが再構成されるため、その間は通信ができなくなります。 切断はしませんでしたが、入力した文字はバッファに入ったまま応答がなくなってしまうので、使い方によってはクリティカルかもしれません。

/var/log/daemon.logから抜粋

...
Feb 28 13:38:16 localhost racoon: INFO: respond new phase 2 negotiation: 192.168.10.yy[500]<=>192.168.1.xx[500]
Feb 28 13:38:16 localhost racoon: INFO: Update the generated policy : 192.168.1.xx/32[500] 192.168.10.yy/32[500] proto=any dir=in
Feb 28 13:38:16 localhost racoon: INFO: IPsec-SA established: ESP/Transport 192.168.1.xx[0]->192.168.10.yy[0] spi=250681865(0xef11a09)
Feb 28 13:38:16 localhost racoon: INFO: IPsec-SA established: ESP/Transport 192.168.10.yy[500]->192.168.1.xx[500] spi=221824184(0xd38c4b8)
Feb 28 13:38:16 localhost racoon: ERROR: such policy does not already exist: "192.168.1.xx/32[500] 192.168.10.yy/32[500] proto=any dir=in"
Feb 28 13:38:16 localhost racoon: ERROR: such policy does not already exist: "192.168.10.yy/32[500] 192.168.1.xx/32[500] proto=any dir=out"
...

2010/02/18

OpenVPN on alixによる外出先から自宅へのアクセス

alixで稼働しているブロードバンドルータでOpenVPNサーバを稼働させてみました。 外出先からWindows XPが動くThinkpad x22とWillcom(PHS)のAX420Sを使って自宅マシンにアクセスできています。

実際のところはVNC+sshのポートフォワードでも家PCのデスクトップを操作する事ができます。 パスワード認証を止めてしまえば、SSL/TLS-VPNと同程度のセキュリティは確保できるでしょう。 ただ家の中に複数のサーバがあってSVNリポジトリへのコミットやら、直接ノートPCからアクセスできた方が便利な状況になったのでVPNを使う事にしました。

最近はIPv6も使っていますが、まだOpenVPNでのIPv6サポートは限定的なようです。 試しにIPv6アドレスで接続してみましたが、認証がちゃんと通りませんでした。 そこで今回は固定IPv4アドレスを使っています。

作業の概要

いまのところサンプルの設定ファイルを使って、共有鍵(static.key)を使った接続ができています。 「共有鍵方式は簡単!」とドキュメントにありますが、慣れの問題でデメリットを上回るほどのものではないと思いました。

このままではクライアントは1台だけしかサポートできないので、最近繰り返している手順を使って公開鍵方式を使った接続に変更します。

サーバ側の設定変更

サーバはalixで動くdebian lennyです。 パッケージからopenvpnを入れて、/etc/openvpn/server.confを編集しました。

secretで指定した共有鍵を指定している1行を削除して、tls-server, ca, cert, keyの4行を追加しました。

/etc/openvpn/server.confファイル

tls-server
dev tun
ifconfig 192.168.20.5 192.168.20.6
dh dh2048.pem
ca cacert.pem
cert newcert.pem
key newkey.pem
port 1194
user nobody
group nogroup
comp-lzo
ping 15
ping-restart 45
ping-timer-rem
persist-tun
persist-key
verb 3

あとは*.pemファイルを/etc/openvpnに配置して、パーミッションを適切にする事ぐらいでしょうか。とはいえrootだけが閲覧できるようにするのはnewkey.pemぐらいでしょう。

server.confにtls-serverを含めずに、/etc/default/openvpnを使う事もできます。

OPTARGS="--tls-server"

ただ設定個所が増えるのは良くないので、server.confにまとめるのが良いと思います。

クライアント側の設定

クライアントはThinkpad x22に入っているWindows XP SP3です。

OpenVPNが導入されているフォルダの中にcacert.pem, newkey.pem, newcert.pemをコピーします。

C:Program Files/OpenVPN/configフォルダ以下

cacert.pem
client.ovpn
newcert.pem
newkey.pem
opensvn_static.key

続いて、client.ovpnファイルを編集します。secretの1行を削除して、先頭にtls-clientを加え、他にca, cert, keyの合計4行を追加しました。

client.ovpnファイル全体

tls-client
dev tun
proto udp
nobind
persist-key
persist-tun
comp-lzo
verb 3
port 1194
remote 219.xxx.xxx.xxx
ifconfig 192.168.20.6 192.168.20.5
ca cacert.pem
cert newcert.pem
key newkey.pem
keepalive 10 60
ping-timer-rem
route 192.168.20.0 255.255.255.0
route 192.168.10.0 255.255.255.0
route 192.168.1.0 255.255.255.0

remote行に指定するのは、ブロードバンドルータのalixにつながる固定IPv4アドレスです。 そこから繋がっているサブネットに対してルーティングするため、複数のroute行を最後に書いています。

クライアントのセキュリティ

正しく設定を終えれば、Windows上ではOpenVPN Clientを起動するだけでVPNが確立します。 ノートPCが盗まれたりした場合には、少しばかり心配になってきます。

今回はnewkey.pemにパスフレーズをつけておくのがお勧めです。 newkey.pemを作成する時に"-nodes"オプションを指定していてパスフレーズを付けていなかった場合には、次のように対応できます。

$ openssl rsa -in newkey.pem -out newkey_with_pass.pem -des3

ここで"newkey.pem"が既に存在しているパスフレーズのついていない秘密鍵ファイル、"newkey_with_pass.pem"が新たに作成されるパスフレーズのついた秘密鍵ファイルで、秘密鍵としての機能自体は同じです。

2010/02/17

freeradiusでのCRLのテスト

せっかく導入したEAP-TLSですが、鍵ファイルが一人歩きしないように再発行の仕組みが必要です。

新しくクライアント用に鍵ファイルを作成する事は手順の繰り返しなので簡単ですが、古い鍵ファイルが使われないようにするためにCertificate Revocation List(CRL)ファイルをfreeradiusで使うようにしました。

CA局側での操作

作業自体は他のCA局でも可能ですが、ここでは鍵ファイルを発行したCA局で作業を行なっています。

現在無線LANのクライアント認証に使っている鍵ファイルを"demoCA/newcerts"ディレクトリから探してきて失効処理を行ないます。

公開鍵ファイルはどこにあっても良いはずですが、作成した鍵一式は既にPCに移動してしまったので、このファイルを使いました。

$ openssl ca -revoke demoCA/newcerts/D5F1DF1134CF2B80.pem

revoke時の画面出力

Using configuration from /usr/lib/ssl/openssl.cnf
Enter pass phrase for ./demoCA/private/cakey.pem:
Revoking Certificate D5F1DF1134CF2B80.
Data Base Updated
CRLファイルの作成

失効した鍵情報を含むCRLファイルを作成します。

$ openssl ca -gencrl -out demoCA/crl/crl.20100217.pem
作成したファイルの転送

CRLファイルをfreeradiusが稼働するサーバに転送します。

$ scp demoCA/crl/crl.20100217.pem 192.168.10.10:

freeradiusサーバ側での作業

鍵ファイルの配置

freeradiusサーバの/etc/freeradius/certs/crlディレクトリを作成します。 ここでcacert.pemとcrl.20100217.pemファイルの2つをコピーしてきます。

$ sudo mkdir /etc/freeradius/certs/crl
$ sudo cp ~/crl.20100217.pem /etc/freeradius/certs/crl/
$ sudo cp /etc/freeradius/certs/cacert.pem /etc/freeradius/certs/crl/

ファイルを配置したらhashを作り直します。

$ sudo c_rehash /etc/freeradius/certs/crl/

c_rehashコマンド実行時の画面出力

Doing /etc/freeradius/certs/crl/
cacert.pem => 22f12cbd.0
crl.20100217.pem => 22f12cbd.r0

c_rehashコマンド実行後のディレクトリの様子

$ sudo ls /etc/freeradius/certs/crl/
22f12cbd.0  22f12cbd.r0  cacert.pem  crl.20100217.pem
eap.conf設定ファイルの編集

eap.confを編集して、CRLを有効にします。

--- /etc/freeradius/eap.conf.20100217	2010-02-16 15:55:18.000000000 +0900
+++ /etc/freeradius/eap.conf	2010-02-17 11:37:41.000000000 +0900
@@ -17,6 +17,8 @@
                 include_length = yes
                 check_cert_cn = %{User-Name}
                 cipher_list = "DEFAULT"
+		check_crl = yes
+		CA_path = ${certdir}/crl
         }
 } 

この後で、freeradiusを再起動すると設定が有効になり、いままで使っていた鍵ファイルが使えなくなりました。

さいごに

crlの運用について

今回は日付を付けてCRLファイルを出力しましたが、Revocation DateがCRLファイルに記録されているので、必要なら配布先のradiusなどのサーバ側で保存すれば良いのかなと感じました。

ただCRLファイルの管理や配布をどのようにするべきなのか、まとまった文書がみつからないので、まだ迷っているところでもあります。 商用のCA局の鍵には、"CRL Distribution Points"(CDP)なるExtensionが入っていてURLでDER形式のCLRファイルを配布していたりします。

Apache2やFreeradiusなどではCDPには対応していないようです。 そもそもサーバが直接インターネットに接続している状況がありえないので、サーバ系ではCRLファイルの管理、配布について考えておく必要がありそうです。

クライアント側ではCDPに対応するのは簡単でしょうし、実装していくべきだと思います。 cacertsにLAN内部からアクセスできるURLをCDP情報に含めて配布する事で、おもしろい実験になりそうです。

demoCA内部でのcrl.pemの配置場所

openssl.cnfファイルではデフォルトとして、demoCA/crl.pemファイルが指定されています。 しかしソースコードを"ENV_CRL"をキーにgrepしてみた限りでは、特に使われている形跡はありません。

"demoCA/crlnumber"ファイルはしっかりと、扱われているんですけどねぇ…。 crlファイルは、そもそもの配置場所からして、どう扱うべきなんでしょうか。

Debian lennyでのhostapdとfreeradiusによるEAP-TLS環境の構築

AlixではXG-601を使って無線LAN APを構築していますが、stable系はドライバ未対応が原因で使えないため、開発版のhostapd-0.7.1を使っています。 いままではWPA-PSKを使った共有鍵方式で運用してきましたが、今回はEAP-TLSを試す事にします。

EAP-TLSはユーザ毎に認証用ファイルを配布する事ができるようになります。 誰がネットワークに接続したのか特定する必要がある環境ではとても便利な仕組みです。 共通鍵を使った環境では、鍵情報の流出+MACアドレス詐称が行なわれた場合、どの端末がネットワークに接続しているのか、どの経路で情報が漏洩したのか把握する事ができません。

公開鍵暗号化方式は、概念はとてもシンプルですが、使うまでの手順が面倒そうなので、あまり使われていないのではないでしょうか。 最近は市販されている家庭用無線LANルータにもRADIUS設定ができるものが多いので、ローカルなCA局とfreeradiusの運用から始めても良いかもしれません。

試行錯誤したところhostapd内蔵のRADIUS機能がどうしても使えなかったため、freeradiusを稼働させました。 debパッケージから構成できれば楽だったのですが、freeradiusのdebian lennyのパッケージでは、libsslライブラリとリンクされていないため、パッケージのビルドも行なっています。 詳細な手順はWPA2 + FreeRADIUS + EAP-TLSを確認してください。

参考にした文書、サイト

目次

作業全体の流れ

EAP-TLSを稼働させるまでの流れは、参考にした上記リンク先に従って進めています。 リンク先に書かれている事は省略していますが、気になったところや判りずらいところを補足する形で書きたいと思います。

  • hostapd-0.7.1のコンパイル
  • freeradiusパッケージの作成 & インストール
  • hostapd及びfreeradiusの設定
  • Windows Vistaからの接続確認

hostapd-0.7.1のコンパイル

本家からtarアーカイブを取得して、適当な場所にファイルを展開します。 configureスクリプトは附属していないので、次の手順で.configファイルを準備してコンパイルしましょう。

いまどきconfigureを使わずに.configファイルなんてlinux kernelのコンパイルみたいですね。

$ cd hostapd-0.7.1/hostapd/
$ cp defconfig .config
$ make
.configファイル

現状使っているhostapdをコンパイルした時の設定ファイルから、コメントと空行を除いたものは次のようになります。

.configファイルからコメントと空行を除いた中身

CONFIG_DRIVER_NL80211=y
CONFIG_EAP=y
CONFIG_EAP_TLS=y
CONFIG_IPV6=y

freeradiusパッケージの作成 & インストール

標準的にapt-getで導入できるfreeradiusではEAP-TLSが使えないので、設定を少し変更してパッケージを独自に作成する必要があります。

手順は参考にしたサイトにある通りですが、EAP-TLS以外は必要ないので、"--with-rlm_eap_tls"と"--with-openssl"の2個所だけを変更しました。 debian/ruleファイルの差分は次のようになっています。

debian/ruleファイルの差分

--- debian/rules.old	2010-02-15 20:17:09.000000000 +0900
+++ debian/rules	2010-02-15 20:16:36.000000000 +0900
@@ -80,14 +80,14 @@
 		--with-large-files --with-udpfromto --with-edir \
 		--enable-developer \
 		--config-cache \
-		--without-rlm_eap_tls \
+		--with-rlm_eap_tls \
 		--without-rlm_eap_ttls \
 		--without-rlm_eap_peap \
 		--without-rlm_eap_tnc \
 		--without-rlm_otp \
 		--with-rlm_sql_postgresql_lib_dir=`pg_config --libdir` \
 		--with-rlm_sql_postgresql_include_dir=`pg_config --includedir` \
-		--without-openssl \
+		--with-openssl \
 		--without-rlm_eap_ikev2 \
 		--without-rlm_sql_oracle \
 		--without-rlm_sql_unixodbc \
@@ -176,12 +176,12 @@
 	dh_strip -a --dbg-package=freeradius-dbg
 
 	dh_makeshlibs -a -n
-	for pkg in ${pkgs} ; do \
-	  if dh_shlibdeps -p $$pkg -- -O 2>/dev/null | grep -q libssl; then \
-	    echo "$$pkg links to openssl" ;\
-	    exit 1 ;\
-	  fi ;\
-	done
+#	for pkg in ${pkgs} ; do \
+#	  if dh_shlibdeps -p $$pkg -- -O 2>/dev/null | grep -q libssl; then \
+#	    echo "$$pkg links to openssl" ;\
+#	    exit 1 ;\
+#	  fi ;\
+#	done
 	dh_shlibdeps
 
 binary-common:

あとは一般ユーザでパッケージ作成のコマンドを実行して、待つだけです。

$ dpkg-buildpackage -rfakeroot
freeradiusパッケージの導入

ひとつ上(..)のディレクトリにパッケージが作成されているので、これを導入します。

$ cd ..
$ sudo dpkg -i freeradius_2.0.4+dfsg-6_i386.deb
$ sudo dpkg -i libfreeradius2_2.0.4+dfsg-6_i386.deb
$ sudo dpkg -i freeradius-common_2.0.4+dfsg-6_all.deb

ここで事前にパッケージのfreeradiusを導入していなかったり、状況によっては/etc/freeradiusに正しく設定ファイルが配置されないようです。

dpkgを実行した直後の/etc/freeradiusの様子


acct_users.dpkg-new		    otp.conf.dpkg-new
attrs.access_reject.dpkg-new	    policy.conf.dpkg-new
...

どうも依存関係が十分ではなかったようで、他のパッケージを導入しようとしたタイミングで気がつきました。

他のパッケージを導入する際に表示されたメッセージ

The following packages have unmet dependencies:
  freeradius: Depends: libperl5.10 (>= 5.10.0) but it is not going to be installed
              Depends: libsnmp15 (>= 5.4.1~dfsg) but it is not going to be installed
              Recommends: freeradius-utils but it is not going to be installed
E: Unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

ここに書かれているようにapt-getを実行すれば、問題は解決します。

$ sudo apt-get -f install

予防策としては事前にopensslに対応していないリポジトリのfreeradiusを導入してから、コンパイルしたfreeradiusを導入するのが良いのかもしれません。

2010/03/06追記:
どうやら設定ファイルが正しく配置されない状況があるようなので、このセクションを追加しました。

dpkg --set-selectionsオプションについて

参考にしたサイトでは、自動的にapt-getでfreeradiusのパッケージが更新されてしまう事のないように各パッケージをholdにする手順が書かれています。

しかしこの中の一部の文字コードがASCIIではないために、コマンドの実行に失敗してしまいます。

エラー時の画面出力

$ echo “freeradius hold” | sudo dpkg --set-selections
dpkg: illegal package name at line 1: must start with an alphanumeric

バイトの並びをみると"0xe2 0x80 0x9c"と"..0x9d"になるので、HTMLでいうところの&ldquo;(left double quotation mark)と&rdquo;(right double quotation mark)のようです。

ユーロ圏では母国語用キーボードで、ASCIIではない事を意識せずにleft/right double quotation markを簡単に使えるのかもしれません。 実際のところechoコマンドでのquotationは、複数の空白を含めたい場合を除けば必要ありません。

$ echo freeradius hold | sudo dpkg --set-selections
$ echo libfreeradius2 hold | sudo dpkg --set-selections
$ echo freeradius-common hold | sudo dpkg --set-selections
間違ってmakeコマンドを実行してしまったら…

この手順通りに進めれば問題ないのですが、どこかのタイミングでmakeコマンドを打ってしまうとdpkg-buildpackage -rfakerootを実行してもパッケージが作成できなくなってしまいます。 やり直そうとしてmake distcleanとかは実行してはいけないルールのようです。

設定を少し変更してパッケージの作り直しをする時には、そのまま繰り返しdpkg-buildpackageコマンドを実行すればOKです。 どうしようもなくなったら、apt-get sourceからやり直しましょう。

freeradiusの起動

2010/02/17追記:
たまたまfreeradiusが起動しなかったと思ったら、どうもバグだと気がついたのでセクシュンを追加。

正しく設定ファイルを作成しても、現在のstableパッケージ(freeradius_2.0.4+dfsg-6)に含まれる/etc/init.d/freeradiusスクリプトからはfreeradiusは起動しません。

これはバグで/var/run/freeradiusディレクトリにfreeradユーザで書き込みができないからです。 起動スクリプトはこのディレクトリがない場合には作成して、適切な権限に設定してくれますが、このディレクトリはfreeradiusパッケージの管理下にあるため最初からroot権限で作成されてしまいます。

既にrootでのみ書き込みができるディレクトリが存在するため、ディレクトリの作成が行なわれず、後続の権限修正もまた行なわれないままになるという仕組みです。

このバグはfreeradius (2.1.7+dfsg-1)で修正されている模様です。 将来のstableパッケージでは問題は起きないでしょう。

$ sudo chown freerad:freerad /var/run/freeradius

hostapd及びfreeradiusの設定

今回の設定のポイントはTLS用の鍵ファイルを作成するところで、clientAuthserverAuthを指定します。 参考にしたサイトにはchallengeパスワードを設定するよう書かれていますが、EAPTLS.pdfでは言及されていません。

デフォルトのopenssl.cnfファイルにはreq_attributesセクションが定義されているので、おそらくこれが読み込まれているようにみえます。残念ながらドキュメントを追い掛けても、ここら辺の動きについての資料をみつける事ができませんでした。

作業の概要

今回は前回のApache2のSSL化で作成したCA局の鍵をそのまま使います。

ただ後続の作業で"--extfile"オプションを使うため設定ファイルを準備しました。 これはopenssl.cnfに含める事はできないため、別ファイルである事が必要です。ファイル名や配置場所は任意で構いません。

demoCA/xpextensionsファイル

[xpclient_ext]
extendedKeyUsage=1.3.6.1.5.5.7.3.2

[xpserver_ext]
extendedKeyUsage=1.3.6.1.5.5.7.3.1

今回は同様の内容を持つ、次のファイルを使用しました。

demoCA/xpextensionsファイル ver.2

[xpclient_ext]
extendedKeyUsage=clientAuth

[xpserver_ext]
extendedKeyUsage=serverAuth

どちらのファイルを使っても、問題なく動きました。

server用鍵の生成

このステップは基本的にapache2のSSL化の時と同じですが、前回使用したコマンドラインの後ろにextensions関連のオプションを追加したところエラーになってしまいました。

エラーになる実行例

$ openssl ca -policy policy_anything -out newcert.pem -infiles newreq.pem -extensions xpserver_ext -extfile demoCA/xpextensions

今回は次の手順で鍵ファイルを作成しました。

$ openssl req -new -nodes -keyout newkey.pem -out newreq.pem -days 365
$ openssl ca -policy policy_anything -out newcert.pem -extensions xpserver_ext -extfile demoCA/xpextensions -infiles newreq.pem

Extended Key Usageの追加を確認する

        X509v3 extensions:
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication
Certificate is to be certified until Feb 16 23:14:16 2011 GMT (365 days)
Sign the certificate? [y/n]:y
...

これで、サーバ用のnewkey.pem(秘密鍵)とnewcert.pem(公開鍵)のペアが作成できているはずです。 別のディレクトリを作成して鍵ファイルを保存します。

$ mkdir wireless_server_keys
$ mv newcert.pem newkey.pem wireless_server_keys/
$ cp demoCA/cacert.pem wireless_server_keys/

2010/03/06追記:
中間CAを使う時にはfreeradiusに指定するcacert.pemファイルは1つなので、親CAと中間CAのpemファイルを連結します。
$ cat ../demoCA/cacert.pem demoCA/cacert.pem > wireless_server_keys/cacert.pem

CN(CommonName)にはローカルのDNSサーバに登録されているFQDNを指定しました。

ここまでの作業が無事に終ったら、秘密鍵(newkey.pem)に繋がるnewreq.pemファイルは削除しておきます。

$ rm newreq.pem
client用の鍵生成

手順自体は同じですが、Windowsでクライアント用の鍵ファイルを読み込むためにpkcs12形式のファイルも作成しておきます。 参考にした手順では、この時に"-clcerts"オプションを指定してCA局の公開鍵を別経路で取り込むようにしています。

セキュリティ的にはより良い方法ですし、もし社内や閉じられたコミュニティ向けのローカルなCA局を運用するのであれば、別経路でのCA局公開鍵の配布は必須とは重います。 ここではpkcs12にCA局の情報も入れています。

$ openssl req -new -nodes -keyout newkey.pem -out newreq.pem -days 365
$ openssl ca -policy policy_anything -out newcert.pem -extensions xpclient_ext -extfile demoCA/xpextensions -infiles newreq.pem 
$ openssl pkcs12 -in newcert.pem -inkey newkey.pem -certfile ./demoCA/cacert.pem -out newcert.p12 -export -name "My Certificate"

ここでのCNにはメールアドレスを入力しました。 社員番号のようなユニークなキーがあれば、それでも良いと思います。

2番目のコマンドで署名をする時には、次のようなExtended Key Usageが付与されているか確認します。 また最後のコマンドでnewcert.p12ファイルを作成する時には、任意のパスワードを付けます。 CA局のパスワードとは違うものにするのが良いでしょう。

Extended Key Usageの追加を確認する

        X509v3 extensions:
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication
Certificate is to be certified until Feb 16 23:26:17 2011 GMT (365 days)
Sign the certificate? [y/n]:y
...

作成した鍵ファイルを別ディレクトリに保存しておきます。

$ mkdir wireless_client_keys
$ mv newcert.pem newkey.pem newcert.p12 wireless_client_keys/
$ rm newreq.pem

freeradiusサーバへの鍵ファイルの配布

TLS-EAPに対応したfreeradiusが既に導入されている事が必要です。 このfreeradiusが稼働するサーバにscpなどで鍵ファイルのディレクトリ全体をコピーしておきます。

$ scp -r wireless_server_keys 192.168.10.10:

freeradiusが稼働するサーバの/etc/freeradius/certs以下にファイルを保存していきます。

$ sudo cp wireless_server_keys/cacert.pem /etc/freeradius/certs
$ sudo cp wireless_server_keys/newkey.pem /etc/freeradius/certs
$ sudo cp wireless_server_keys/newcert.pem /etc/freeradius/certs

さらに参考にしたサイトの例に従って"dh"ファイルや"random"ファイルを作成します。 dhファイルの作成はalixには重い作業なので、より高速なCPUを積んだマシンで作成するのがよいでしょう。

$ openssl dhparam -5 -out dh1024.pem 1024
$ sudo cp dh1024.pem /etc/freeradius/certs

randomファイルはalix上で作成します。

$ sudo dd if=/dev/urandom of=/etc/freeradius/certs/random count=2

2010/03/06追記:
参考にした手順で作成した"dh"ファイルは512bitだったので、1024bitで作成しました。 2048bitでも良いのですが、ファイル作成に時間がかかるのと、そこまでは またrandomファイルの作成手順についても載せています。
この作業の結果、eap.confの中でのdhファイル名を変更(dh->dh1024.pem)しました。

またeap.confは次のようになりました。

/etc/freeradius/eap.confファイル

eap {
        default_eap_type = tls
        timer_expire     = 60
        ignore_unknown_eap_types = no
        cisco_accounting_username_bug = no         

        tls {
                certdir = ${confdir}/certs
                cadir = ${confdir}/certs
                private_key_password = 
                private_key_file = ${certdir}/newkey.pem
                certificate_file = ${certdir}/newcert.pem
                CA_file = ${cadir}/cacert.pem
                dh_file = ${certdir}/dh1024.pem
                random_file = ${certdir}/random
                fragment_size = 1024
                include_length = yes
                check_cert_cn = %{User-Name}
                cipher_list = "DEFAULT"
        }
}

次にclient.confファイルを編集します。 テストのためにclient localhostの定義がありますが、secretは適当に変更しておきます。 そして最後にhostapdが走るホストのIPアドレスかFQDNを定義します。

client 192.168.1.x {
        secret = 6a_abp1z75_5e318
        shortname = wireless-ap
}

パスワードは次のようなコマンドで適当な文字列を表示させて使っています。 このままだと0-9,a-fの範囲しか使わないので数字の'0'は適当な記号に、'f'はa-fの範囲外の適当なアルファベットに手で置き換えるといったことをしています。

$ dd if=/dev/urandom bs=16 count=1 | od -t x8

2010/03/06追記:
あっちこっち参照するのは面倒なのでclient.confの編集とパスワードの適当な選び方について追加しました。

サーバ用のnewreq.pemファイルを生成する段階で、"-nodes"オプションを指定してパスワードをつけていません。 このためprivate_key_passwordには何も指定していないところが、参考にした手順と違うところです。

hostapdの設定について

hostapd内蔵のradius機能を使わないためにeap_server=0を指定するか、コメントアウトする事が必要だろうと思います。 ただ"WPA/IEEE 802.11i configuration"セクションにあるオプションを有効にしたところ、PTK Rekeyingのタイミングで接続が切れるようになってしまいました。

できるだけデフォルト設定のように使って、wpa_ptk_rekeyなどのオプションは指定しないのが良さそうです。 いまのところhostapd.confは次のようになっています。

コメント、空行を除いたhostapd.conf

interface=wlan0
bridge=br0
driver=nl80211
logger_syslog=-1
logger_syslog_level=2
logger_stdout=-1
logger_stdout_level=0
dump_file=/tmp/hostapd.dump
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
ssid=ALIXHOMEAP4TEST
country_code=JP
ieee80211d=1
hw_mode=g
channel=9
beacon_int=100
dtim_period=2
max_num_sta=255
rts_threshold=2347
fragm_threshold=2346
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wmm_enabled=1
wmm_ac_bk_cwmin=4
wmm_ac_bk_cwmax=10
wmm_ac_bk_aifs=7
wmm_ac_bk_txop_limit=0
wmm_ac_bk_acm=0
wmm_ac_be_aifs=3
wmm_ac_be_cwmin=4
wmm_ac_be_cwmax=10
wmm_ac_be_txop_limit=0
wmm_ac_be_acm=0
wmm_ac_vi_aifs=2
wmm_ac_vi_cwmin=3
wmm_ac_vi_cwmax=4
wmm_ac_vi_txop_limit=94
wmm_ac_vi_acm=0
wmm_ac_vo_aifs=2
wmm_ac_vo_cwmin=2
wmm_ac_vo_cwmax=3
wmm_ac_vo_txop_limit=47
wmm_ac_vo_acm=0
ap_max_inactivity=300
max_listen_interval=100
ieee8021x=1
eapol_version=1
eapol_key_index_workaround=1
eap_reauth_period=1800
use_pae_group_addr=0
eap_server=0
own_ip_addr=192.168.10.10
nas_identifier=alix.example.org
auth_server_addr=192.168.10.10
auth_server_port=1812
auth_server_shared_secret=xxxxxxxxxxxxxxxxxx
acct_server_addr=192.168.10.10
acct_server_port=1813
acct_server_shared_secret=xxxxxxxxxxxxxxxxxx
radius_retry_primary_interval=600
radius_acct_interim_interval=1200
dynamic_vlan=0
wpa=1
wpa_key_mgmt=WPA-EAP
wpa_pairwise=TKIP

Windows Vistaからの接続確認

作成したnewcert.p12ファイルをUSBメモリなどでコピーし、このファイルをWindows上でダブルクリックするとインストールが始まります。 気をつけるところはデフォルトではどのCA局も信頼する対象になっていませんから、Access Pointのプロパティから信頼するCA局としてチェックを入れるところでしょうか。

ただ、wpa_ptk_rekeyをコメントアウトして、比較的安定して動いていると思いますが、WPA-PSKの時と比べると、まだ時々切断されている事があります。

さいごに

hostapdでEAP-TLSを実現するための方法をみると、CA局を専用に作らないとなのかなぁ、などいろいろ思いますが、なんとかApache2のSSL化で使ったdemoCA/cacert.pemが使えました。 ただ、ちゃんと動く環境を手に入れるまでは不確定な要素を排除するために、専用のdemoCA/cacert.pemを作る確実な手順を踏襲するのが良いとは思います。

いくつかの手順をみるとそれぞれの工夫がみて取れます。 EAPTLS.pdfではスクリプトが準備されていますが、内部を理解しないとちゃんと動かない、なんてことにもなりそうです。 TLS周りの作業は自分でスクリプト化するのは良い事ですが、人が作成したスクリプトは時間をかけても確実に各ステップを確認して、自分なりのスクリプトに編み直す事が必要そうです。

hostapdの内蔵TLSがうまく動いていないとか、未解決のままにしている事はありますが、ひとまずは目的を達成できたので情報を整理しておこうと思います。

2010/02/15

突然USBフラッシュメモリが使えなくなる

alixのバックアップ用に使ってきたELECOM製のMF-AU2 USBフラッシュメモリが壊れてしまいました。 とはいっても読み出しについてはエラーは出ていません。

ちょっと普通じゃない使い方

このフラッシュメモリはext3フォーマットをした上で、DNSサーバにしているalixのバックアップ領域として使っていました。

バックアップ自体はpdumpfsで行なうためハードリンクが使いたかったので、ext3にしていたわけです。

alix+debianの組み合せだと、起動時にUSBメモリが認識されるタイミングが一定ではなく、使いたい時にマウントしたかったのがautofsにした理由です。

ちなみに設定ファイルは次のようにしていました。

/etc/auto.masterファイル抜粋

/misc	/etc/auto.misc

/etc/auto.miscファイル抜粋

bk		-fstype=ext3,noatime		:/dev/disk/by-uuid/d83cc93b-a40e-4435-8de6-d18a56491f66

今回の症状

たまたまログインしてバックアップの様子を確認した時に発見しました。 家にもちゃんとnagios辺りで監視系を作らないとだめかなぁ。

新しくディレクトリを作ろうとすると次のようなメッセージが表示されます。

エラーメッセージ

$ sudo mkdir test
mkdir: ディレクトリ `test' を作成できません: No space left on device

とはいえ、領域を全部使い切ったわけでもなく、まだ半分程度は余っています。

現時点での容量

/dev/sdh1              3886448   1787812   1901212  49% /media/disk

常に100%を使い切るような使い方をすれば、あっという間に使えなくなる事は想像できたのですが、まだ51%残っている状態で、簡単に使えなくなり少し驚いています。

別の格安フラッシュメモリをチェックしてみる

念のため昨年末に近くの家電屋さんで放出されていたKingston製の格安メモリもチェックしてみると、みごとに不具合が出ていました。 ログの日付をみると2009/12/24のクリスマス前後から使い出したばかりみたいなんだけどなぁ…。

格安フラッシュメモリ領域にlsした時のエラー

$ ls /misc/bk
ls: cannot access /misc/bk/2009: Input/output error
2009  2010  OLD  do_pdumpfs.sh  ignore.list  latest  lost+found  stderr.log  stdout.log

この状態だと2009ディレクトリ以下のファイル、ディレクトリにはアクセスできません。 まぁこっちはまだ書き込めているので、しばらくは使い続けてダメになってから交換しようと思います。

復旧までのステップ

この領域はバックアップ領域なので、簡単に復旧できるのが良いところです。

  • 新しいUSBメモリを装着
  • ext3フォーマット
  • tune2fsでUUIDを調べて、/etc/auto.miscを書き換え
  • do_pdumpfs.shスクリプトを配置する

あとはcrontabから/misc/bk/do_pdumpfs.shが自動的に呼ばれて終りです。 autofsをベースにしたpdumpfsとcrontabの組み合せはとてもお手軽で助かっています。

問題は稼働状況の把握だけですね。

さいごに

まぁバックアップ領域なので、また適当なフラッシュメモリを買ってくるつもりです。NANDメモリの製造元でもあるSANDISK製のものは価格のわりに速度も品質も安定していて好きだったんですけどね。最近はもう売られていません。

SONY製のハイスピードタイプは読み出しは早いですけれど、全体的なパフォーマンスは価格ほどではないですし、どれもこれも信頼性という意味ではイマイチな感じなんですよね。

とはいえ「今のところ」信頼している製品でもNAND MLCである限りは潜在的に問題があるのは間違いありません。 繰り返しになりますが、USBフラッシュメモリをext3フォーマットして使うことに問題があると思います。今回のように純増していく用途であれば、NILFSが最適でしょうね。でもNILFSはハードリンクに対応しているのかなぁ、今度試してみましょう。

ちゃんと使いたいならお金を出して10万回以上の書き込み/消去サイクルを保証しているTranscendのJetFlash 1xxシリーズを使うべきです。 仕事で使うなら、このクラスの製品がお勧めです。

2010/02/12

AlixでのIPv6接続の自動化を試してみる

Alixで作成したブロードバンドルータを再起動した時に、自動でfeel6へ接続しIPv6通信ができるようにしました。 単純にいくつかのスクリプトを作成して、組み合せただけですけどね。

修正 & 作成したスクリプトについて

今回作成したスクリプトは次の通りです。

  • /usr/local/sbin/yadtcpc.pl
  • /usr/local/sbin/ipv6_up.sh
  • /usr/local/sbin/ipv6_down.sh

起動時に呼ばれるスクリプトは"ipv6_up.sh"で、"ipv6_down.sh"は停止時、及び、メンテナンス用のスクリプト。 yadtcpc.plは"ipv6_up.sh"が内部的に呼び出すため、明示的に実行する必要はありません。

/usr/local/sbin/yadtcpc.pl

前回の投稿で紹介したNet::POP3モジュールを使ったDTCPクライアントです。少し変更して、DTCP接続時に渡された情報を標準出力ではなくファイルに出力するようにしました。 接続時には、このファイルから必要な情報を読み取るようにしています。

/var/run/yadtcpc.infoファイル

local=219.xxx.xxx.xxx,remote=43.244.xxx.xx,prefix=2001:03xx:xxxx::,mask=48

また内部では適当なwaitを入れてループしていますが、"/var/run/yadtcpc.control"ファイルから停止の指示があるとquit()により接続を切って全体の処理を中断します。 停止したい場合には$ echo exit | sudo tee /var/run/yadtcpc.controlのようなコマンドを実行します。

前回作成したpackage YADtcpc;はそのまま使い、後ろにあるmain処理全体を次のようなコードで置き換えています。

差し替え個所

...
################
## main class ##
################
package Main;

sub new {
  my ($class) = @_;
  my $self = {
    dtcpc => undef,
    local => undef,
    remote => undef,
    prefix => undef,
    mask => undef,
  
    feel6_remote => "dtcp.feel6.jp",
    feel6_port => 20200,
    feel6_id => undef,
    feel6_password => undef,
  };

  bless $self, $class;
  return $self;
}

sub connect {
  my ($self) = @_;

  if($self->{dtcpc}) {
    $self->{dtcpc}->quit();
    $self->{dtcpc} = undef;
  }
  $self->{dtcpc} = YADtcpc->new($self->{feel6_remote}, Port=>$self->{feel6_port});
  ($self->{local}, $self->{remote}, $self->{prefix},$self->{mask}) = $self->{dtcpc}->dtcp($self->{feel6_id}, $self->{feel6_password});

  $self;
}

sub write_log {
  my ($self, $log_file) = @_;
  if(open(LOG, ">$log_file")) {
    printf LOG "local=%s,remote=%s,prefix=%s,mask=%s\n", $self->{local}, $self->{remote}, $self->{prefix},$self->{mask};
    close(LOG);
  }

  $self;
}

## for debug purpose only
sub print_log {
  my ($self) = @_;
  printf "local=%s,remote=%s,prefix=%s,mask=%s\n", $self->{local}, $self->{remote}, $self->{prefix},$self->{mask};
  $self;
}
sub ping_pong {
  my ($self, $check_file) = @_;
  while ($self->{dtcpc}->ping() =~ /pong/i) {
    if(open(CHECK, $check_file)) {
      my $check = <CHECK>;
      if($check =~ /exit/i) {
        $self->{dtcpc}->quit();
        exit;
      }
      close(CHECK);
    }
    sleep 210; ## essential to send 'ping' within five minutes intervales.
  }

  $self;
}

##########
## main ##
##########
my $main = Main->new();
$main->{feel6_remote} = "dtcp.feel6.jp";
$main->{feel6_port} = 20200;
$main->{feel6_id} = "xxxxxxxx";
$main->{feel6_password} = "xxxxxxxx";

my $info_file = "/var/run/yadtcpc.info";
my $control_file = "/var/run/yadtcpc.control";
do {
  $main->connect();
  $main->write_log($info_file);
  $main->ping_pong($control_file);
  sleep 1;
} while(1);

__END__

あとはPIDを"/var/run/yadtcpc.pid"辺りに書き出して、コマンドの起動時に重複起動のチェックをしようかなと思っています。

/usr/local/sbin/ipv6_up.sh

内部では次のような処理を行なっています。

  • DTCPクライアント(yadtcpc.pl)の起動
  • /var/run/yadtcpc.infoファイルの読み込み
  • デフォルトルートの設定
  • eth1, eth2へのユニキャストアドレスの設定、及びルーティング
  • RAサーバ(radvd)の起動

/usr/local/sbin/ipv6_up.shファイル

#!/bin/bash

umask 022
PATH=/sbin:/usr/sbin:/bin:/usr/bin
LANG=C
export PATH LANG

function ipv4to6addr {
  ## main process
  ipv4_1="$(echo $1 | cut -d'.' -f1)"
  ipv4_2="$(echo $1 | cut -d'.' -f2)"
  ipv4_3="$(echo $1 | cut -d'.' -f3)"
  ipv4_4="$(echo $1 | cut -d'.' -f4)"

  printf "%02x%02x:%02x%02x\n" "${ipv4_1}" "${ipv4_2}" "${ipv4_3}" "${ipv4_4}"
}

## start the my DTCP client
## - first remove the control file
YADTCPC_FILE=/var/run/yadtcpc.control
test -f ${YADTCPC_FILE} && rm ${YADTCPC_FILE}
## - start the client
/usr/local/sbin/yadtcpc.pl &
## - waiting to finish the DTCP configuration
sleep 5

YADTCPC_FILE=/var/run/yadtcpc.info
if test -f ${YADTCPC_FILE}; then
  IPV6_LOCAL="$(cat ${YADTCPC_FILE}|cut -d, -f1|cut -d= -f2)"
  IPV6_REMOTE="$(cat ${YADTCPC_FILE}|cut -d, -f2|cut -d= -f2)"
  IPV6_PREFIX="$(cat ${YADTCPC_FILE}|cut -d, -f3|cut -d= -f2)"
  IPV6_MASK="$(cat ${YADTCPC_FILE}|cut -d, -f4|cut -d= -f2)"
else
  exit 1
fi

## setup sit device
ifconfig sit0 up tunnel ::${IPV6_REMOTE} mtu 1454
route -A inet6 del ::/0 dev eth0
DEV_NAME="$(ifconfig | grep ^sit | grep -v sit0 | head -1 | cut -d' ' -f1)"
test "${DEV_NAME}" && route -A inet6 add ::/0 dev "${DEV_NAME}"

## setup network address
DEV1_IP="$(ip -f inet addr show eth1 | tail -1 | awk '{print $2}' | cut -d/ -f1)"
DEV1_NET="$(echo ${IPV6_PREFIX}|sed -e 's/::/:/')1"
ip -f inet6 addr add ${DEV1_NET}::$(ipv4to6addr ${DEV1_IP})/64 dev eth1
ip -f inet6 route add ${DEV1_NET}::/64 dev eth1

DEV2_IP="$(ip -f inet addr show eth2 | tail -1 | awk '{print $2}' | cut -d/ -f1)"
DEV2_NET="$(echo ${IPV6_PREFIX}|sed -e 's/::/:/')10"
ip -f inet6 addr add ${DEV2_NET}::$(ipv4to6addr ${DEV2_IP})/64 dev eth2
ip -f inet6 route add ${DEV2_NET}::/64 dev eth2

## start radvd
INIT_RADVD=/etc/init.d/radvd
if test -f ${INIT_RADVD}; then
  ${INIT_RADVD} start
fi

/usr/local/sbin/ipv6_down.sh

内部の処理は、ほぼ起動時の処理の逆ですが、既にLAN内部でIPv6を使った接続済みのセッションを切断しないようにデバイスのIPv6アドレス、及び、ルーティング情報は削除していません。

/usr/local/sbin/ipv6_down.shファイル

#!/bin/bash

umask 022
LANG=C
PATH=/sbin:/bin:/usr/bin/:/usr/sbin
export LANG PATH

if test -f /var/run/yadtcpc.info; then
  IPV6_LOCAL="$(cat /var/run/yadtcpc.info|cut -d, -f1|cut -d= -f2)"
  IPV6_REMOTE="$(cat /var/run/yadtcpc.info|cut -d, -f2|cut -d= -f2)"
  IPV6_PREFIX="$(cat /var/run/yadtcpc.info|cut -d, -f3|cut -d= -f2)"
  IPV6_MASK="$(cat /var/run/yadtcpc.info|cut -d, -f4|cut -d= -f2)"
fi

## stop the my DTCP client
echo exit > /var/run/yadtcpc.control

## list up sit+ devices other than sit0
DEV_LIST="$(ifconfig | grep ^sit | grep -v sit0 | cut -d' ' -f1)"

## stop radvd
INIT_RADVD=/etc/init.d/radvd
if test -f ${INIT_RADVD}; then
  ${INIT_RADVD} stop
fi

## down devices
for sit_dev in ${DEV_LIST}
do
  route -A inet6 del ::/0 dev ${sit_dev}
  ip link set ${sit_dev} down
  ip tunnel del ${sit_dev}
done

/etc/network/interfacesファイルの修正

ファイルを配置してから、interfacesファイルを編集します。 再起動の後で$ ping6 www.kame.netが成功する事を確認しています。

interfacesファイル変更後

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
  post-up /usr/local/sbin/config_ip6tables.sh
  post-up /usr/local/sbin/ipv6_up.sh
  pre-down /usr/local/sbin/ipv6_down.sh

2010/02/11

IPv6 + TCP Wrapperでのnetmaskの取り扱いについて

LAN内部での事ですが、DNSにAAAAレコードを追加し、Ipv6を使ってサーバに接続するようになりました。 しかしAlixで作成したゲートウェイにIPv6経由でSSH接続したときだけ、タイムアウトをしているかのような動きになったので原因を追ってみました。

エラーの内容::クライアント編

サーバ側に出力されたログでは、原因が直感的にはわかりませんでした。 一般的にサーバ側は誰かに管理されていて、ログをみたり設定の変更ができる事は少ないですよね。 そんなわけでsshで困った時にはクライアント側で-vオプションを使うのがお勧めです。

$ ssh -v 2001:03xx:xxxx:1::c0a8:0101

出力されたエラーの内容

OpenSSH_4.7p1 Debian-8ubuntu1.2, OpenSSL 0.9.8g 19 Oct 2007
debug1: Reading configuration data /home/yasu/.ssh/config
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: Connecting to 2001:03xx:xxxx:1::c0a8:0a01 [2001:03xx:xxxx:1::c0a8:0a01] port 22.
debug1: Connection established.
debug1: identity file /home/yasu/.ssh/identity type 0
debug1: identity file /home/yasu/.ssh/id_rsa type -1
debug1: identity file /home/yasu/.ssh/id_dsa type 2
ssh_exchange_identification: Connection closed by remote host

エラーの内容::サーバ編

ちなみにサーバ側では次のようなログでした。

サーバ側の/var/log/auth.logファイル抜粋

Feb 11 18:07:56 xxxxxxxx sshd[5424]: refused connect from 2001:03xx:xxxx:1::xxxx (2001:03xx:xxxx:1::xxxx)

サーバ側で接続を拒否しているかのような動きですが、すぐには原因が思いつきませんでした。

解決編

結局のところ原因は、/etc/hosts.allowにIPv4用のTCP Wrapper(libwrap)の設定を書いていたことでした。

原因がわかればなんてこともないのですけどね。 修正内容はhosts.allowにIPv6用の設定を追加しただけなのですが、ネットマスクを使ったルールの書式が少し判りずらいかなと感じました。

追加した/etc/hosts.allowファイル

ALL: 192.168.1.0/24 192.168.10.0/24 [2001:03xx:xxxx::]/48

ブランケットの中に/48を入れてしまうとうまく動きません。

今回はiptablesの保険として設定しておいたTCP Wrapperがちゃんと仕事をしてくれた事でトラブルになってしまいました。 まぁIPv6用の設定方法もわかったし、良しとしましょう。

2010/02/04

AlixのブロードバンドルータでIPv6

以前作成したalixなブロードバンドルータをフリービットのFeel6接続サービスを使ってIPv6に対応させてみました。

この手の作業は同じlinuxでもディストリビューションや環境に左右されるので、まず今回の環境をまとめておきます。

  • インターネット接続:NTT東日本 Bフレッツ + 固定IP
  • ブロードバンドルータHW:Alix 2C3
  • ブロードバンドルータSW:Debian lenny (eth0:Global ipv4 addr, eth1:192.168.1.1, eth2:192.168.10.1)
  • DTCPクライアント:自作Perlスクリプト
  • RAサーバ:debian lenny付属のradvdパッケージ from http://v6web.litech.org/radvd/dist/
  • クライアント1:Ubuntu 8.04 x86_64 (192.168.1.x)
  • クライアント2:Mac OS X 10.6.2 (192.168.1.x)

Feel6接続サービスへの登録

IPv6なネットワークに接続する方法はいくつかありますが、今回はフリービットのサービスを使う事にしました。 固定IPを使っているので静的にトンネルを作ってもらっても良かったのですが、将来プロバイダを変更するかもしれないですしね。

登録はFeel6のトップページから必要事項を送信するだけです。

DTCP接続を試してみる

Feel6のサーバにIPv6ネットワークへのトンネルを作ってもらうわけですが、その情報のやりとりはDTCPというプロトコルを使用するようです。 ここら辺の解説は「Linux 上で DTCP を使って IPv6 over IP トンネルを張る」がとても参考になりました。

検索してみるとDTCPの使い方としては、次のような方法がありました。

  • DTCPクライアントを自作する方法
  • USAGIプロジェクトが配布しているdtcpcコマンドを使用する方法

Linux 上で DTCP を使って IPv6 over IP トンネルを張る」にある"setup-dtcp"スクリプトやUSAGIプロジェクトのdtcpcを使ってみました。しかし内部で呼び出しているコマンドの実行に失敗するなど簡単にはできなかったため"setup-dtcp"の元になったスクリプトをベースに手作業でコマンドを実行していきました。

DTCPクライアントの自作

最終的には作り直したのですが、まずは参考にしたPerlスクリプトでトンネル状態を維持する事にしました。 参考にしたページにあるスクリプトでは変数定義が抜けているのと、Feel6から貰ったIPv6アドレスがわからないので出力ようにして、だいたい次のようなコードを追加しています。

#!/usr/bin/perl

my $host = "dtcp.feel6.jp";
my $port = 20200;
my $user = "xxxxxx";
my $pass = "xxxxxx";
...
    ($local, $remote, $prefix, $nbits) = ($1, $2, $3, $4);
    printf(STDERR "local=%s,remote=%s,prefix=%s,nbits=%s\n", $1, $2, $3, $4);
} else {
...

ここで$user$passには、Feel6登録時に入力したものを記入します。 実行に成功すると4つの文字列が手に入ります。

DTCPクライアントは接続を維持するために毎分PINGメッセージを送信しています。 そのためIPv6を使う間はずっと起動しておく必要があります。

AlixをIpv6ルータとして構成する

ここからはDTCPクライアントが裏側で動いている前提で書いています。

Feel6とは関係なしにBフレッツを使っているとNTTからIPv6アドレスが付与されます。 ただしこれは/64なプリフィックスなので、サブネット用に番号を加える事ができません。 また他の方のブログなどをみるとオプションを申し込まないと、NTTが準備したコンテンツ以外にアクセスする事は難しいようです。

Alixで確認すると、NTTから自動的に割り当てられるこのアドレスは以前Alixをルータにする投稿の中で、Bフレッツとの接続に使ったeth0に割り当てられています。

Bフレッツで自動的に割り当てられるIPv6アドレス

eth0      Link encap:イーサネット  ハードウェアアドレス 00:0d:xx:xx:xx:xx
          inet6アドレス: 2001:c90:489e:xxxx:xxxx:xxxx:xxxx:xxxx/64 範囲:グローバル
          inet6アドレス: fe80::xxxx:xxxx:xxxx:xxxx/64 範囲:リンク
...

Feel6から付与されるプリフィックスは/48ですから、前半64bitの残り16bit(0xffff分)を使ってネットワークを分割する事が可能になります。IPv4ではeth1, eth2でネットワークを分けているので、それにならってIPv6でもeth1, eth2でサブネットを設定する事にしました。

IPv6ルータになるための設定項目

Bフレッツ用のIPv6アドレスが割り当てられていた理由は、lennyのデフォルトとしてRouter Advertisement(RA)を受け付けるようになっている事に起因します。 ルータ自身がRAに反応しては困るらしくsysctlの設定を変更します。

Fee6を使う場合はRAを有効にしても大丈夫なんじゃないかという気はしつつ、先達に習うことにしました。 その他のアイデアとしてはeth0ではRAを受信してNTTのアドレスを構成しつつ、eth1, eth2はRAを無視して自分でアサインする、という構成も考えられましたが今回は試していません。

/etc/sysctl.confファイルの追加内容

...
net.ipv6.conf.all.accept_ra=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.all.forwarding=1
...

また直接は関係ないですが、この他にsysctl.confではipv6関連の設定を2つ行なっています。

net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_source_route = 0
proc経由での設定変更について

sysctl.conf周りの設定は/procファイルシステムを通しても可能で、IPv6を説明した多くのサイトで$ echo 1 > /proc/sys/net/ipv6/conf/all/accept_raのようなコードが書かれています。 しかし恒久的な変更には/etc/sysctl.confファイルを使いましょう。

procに"0"や"1"を書き出したいが、sudoを使わないとという場合にはteeコマンドを使いましょう。

$ echo 1 | sudo tee /proc/sys/net/ipv6/conf/all/accept_ra

DTCPの出力からAlixにIPv6アドレスを付与する

DTCPクライアントの出力から次のような情報が得られています。

  • local - 自分のGlobal IPアドレス:219.117.xxx.xxx
  • remote - Feel6への接続用IPアドレス:43.244.xxx.xx
  • prefix - IPv6接続に使用するプリフィックス:2001:03xx:xxxx::/48
Alixの各インタフェースに割り当てる情報を決めていく

prefixは最後の2byte(16bit)が空いているので、ここを埋めてeth1, eth2にどのようなサブネットを設定するか考えます。 何でも良いのですが、IPv4に合わせて最後は0001,0010としました。

  • eth1 prefix - 2001:03xx:xxxx:1::/64
  • eth2 prefix - 2001:03xx:xxxx:10::/64

ここまでを踏まえてそれぞれのIPv6アドレスを決めました。 アドレスは何でも良いですが、"192.168.1.1", "192.168.10.1"を16進数に変換した"c0a8:0101", "c0a8:a101"を使う事にしました。

  • eth1 ipv6 addr - 2001:03xx:xxxx:1::c0a8:0101/64
  • eth2 ipv6 addr - 2001:03xx:xxxx:10::c0a8:0a01/64

ただしalixから外部に通信する必要がなければサイトローカルユニキャストアドレス(fe80::/64)で十分なはずで、必要だとしても2つも追加する必要はなかったりします。 まぁどちらかのデバイスをダウンさせる事もあるかもしれないので、こういう設定にしてみました。

実際にIPv6環境を構築していく

情報は決まったのでコマンドをパチパチ打っていきます。

トンネルの構築

remoteアドレスをベースにsit0, sit1インタフェースを構成します。

$ sudo /sbin/ifconfig sit0 up tunnel ::43.244.xxx.xx

Alixではiptablesを構成しているので、通常の構成ではカプセル化したパケットが出ていきません。 dropしたパケットのログをみると次のようになっています。

iptablesでdropしたパケットの情報

...
[135368.059979] netfilter drop IN= OUT=ppp0 SRC=219.117.xxx.xxx DST=43.244.xxx.xx LEN=124 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=41
...
[136225.974684] netfilter drop IN=ppp0 OUT= MAC= SRC=43.244.xxx.xx DST=219.117.xxx.xxx LEN=124 TOS=0x00 PREC=0x00 TTL=246 ID=16480 PROTO=41
...

このproto=41を通すためにiptablesに設定を追加します。

iptables -A inppp -s 43.244.xxx.xx --proto 41 -j ACCEPT
iptables -A outppp -d 43.244.xxx.xx --proto 41 -j ACCEPT
ルーティング情報の設定

eth0にはBフレッツから割り当てられたアドレスが付いています。 当然デフォルトゲートウェイはeth0に向いているので、このルーティングを削除しつつ、Feel6に変更する設定を行ないます。

$ sudo /sbin/route -A inet6 del ::/0 dev eth0
$ sudo /sbin/route -A inet6 add ::/0 dev sit1
eth1, eth2にIPv6アドレスを付与する

外部との通信に使用するために、IPv6でいうところのグローバルユニキャストアドレスを追加します。

$ sudo /sbin/ip -f inet6 addr add 2001:03xx:xxxx:1::c0a8:0101/64 dev eth1
$ sudo /sbin/ip -f inet6 addr add 2001:03xx:xxxx:10::c0a8:a101/64 dev eth2
ここまでの稼働確認

おそらくping6は既にインストールされていると思うので、これを使って接続が確立しているか確認します。

$ ping6 www.kame.net
$ ping6 2001:200:0:8002:203:47ff:fea5:3085

ping6 www.kame.netの出力

PING www.kame.net(orange.kame.net) 56 data bytes
64 bytes from orange.kame.net: icmp_seq=1 ttl=54 time=14.2 ms
64 bytes from orange.kame.net: icmp_seq=2 ttl=54 time=13.3 ms

ping6 2001:200:0:8002:203:47ff:fea5:3085の出力

PING 2001:200:0:8002:203:47ff:fea5:3085(2001:200:0:8002:203:47ff:fea5:3085) 56 data bytes
64 bytes from 2001:200:0:8002:203:47ff:fea5:3085: icmp_seq=1 ttl=54 time=14.6 ms
64 bytes from 2001:200:0:8002:203:47ff:fea5:3085: icmp_seq=2 ttl=54 time=13.5 ms

生のIPv6アドレスはwww.kame.netのものです。 将来的に変更されているかもしれないので、nslookupでアドレスを確認してからping6に渡してください。

$ nslookup -type=AAAA www.kame.net

nslookup -type=AAAA出力例

$ nslookup -type=AAAA www.kame.net
Server:		192.168.1.x
Address:	192.168.1.x#53

Non-authoritative answer:
www.kame.net	has AAAA address 2001:200:0:8002:203:47ff:fea5:3085
....

もしping6がインストールされていなければ、iputils-pingパッケージを導入してください。 nslookupはdnsutilsパッケージに含まれています。

$ sudo apt-get install iputils-ping

ここまでうまく行けば、radvdクライアントを動かすか、クライアント側を適切に設定すればIPv6ネットワークに接続する事ができるようになります。

トラブルシュート

もしping6の実行に失敗してデバイスを指定する次のようなコマンドが成功するとしたら、ルーティング情報が間違っているはずです。

$ ping6 -I sit1 2001:200:0:8002:203:47ff:fea5:3085

またping6実行時にping: sendmsg: Operation not permittedメッセージが出力されているとすれば、iptablesの設定に失敗しているか、ip6tablesが設定されているのが原因でしょう。

いまはIPv6用のfirewallは素通しの状態になっているはずで、次のコマンドでデフォルトが"(policy ACCEPT)"になっている事を確認します。

sudo /sbin/ip6tables -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

radvdクライアントの起動

Alixではパッケージで配布されているradvdコマンドを使用します。

$ sudo apt-get install radvd

今回は次のような設定ファイルを準備しました。

/etc/radvd.confファイル

interface eth1
{
        AdvSendAdvert on;

        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 10;

        prefix 2001:03xx:xxxx:1::/64
        {
                AdvOnLink on;
                AdvAutonomous on;
        };
};

interface eth2
{
        AdvSendAdvert on;

        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 10;

        prefix 2001:03xx:xxxx:10::/64
        {
                AdvOnLink on;
                AdvAutonomous on;
        };
};

最後にradvdを起動して終りです。

$ sudo /etc/init.d/radvd start
radvdの初期設定について

パッケージに含まれる通常の構成だとAlixの再起動時に自動的にradvdが起動します。 これが悪いとはいいませんが、トンネルの構成をまだ自動化していないので意図しないタイミングでradvdが起動してしまうかもしれません。

今回はradvdを制御する目的で明示的に/etc/init.d/radvdスクリプトを実行する事にして、自動起動は停止する事にしました。

$ sudo mv /etc/rc2.d/S50radvd /etc/rc2.d/K50radvd

クライアントの接続確認

本当は手動でルーティングをしていたのですが、radvdを使う方法を書く事にしたのでクライアント側でする事はあまりありません。 Ubuntu 8.04 x86_64以外にMac 10.6でもブラウザをいくつか立ち上げて確認をしています。

Ubuntu 8.04での稼働確認

accept_raは有効になっているので、基本的にIPアドレスはすでに割り当てられていました。 しかしufwを有効にしているせいかip6tablesのデフォルトポリシーがDROPになっていたためping6の実行に失敗しました。

ip6tablesの構成は後でする事にして、いまは全部の通信を許可しています。

$ sudo /sbin/ip6tables -P INPUT ACCEPT
$ sudo /sbin/ip6tables -P OUTPUT ACCEPT
$ sudo /sbin/ip6tables -P FORWARD ACCEPT

とりあえずWebブラウザだけを使って、keme.netの亀が踊るかどうか確認します。

http://www.kame.net/

Mozilla Firefox 3.0.17, Google Chromium 5.0.315.0 (37939) Ubuntu, Opera 10.10で確認しました。 結果としてOperaだけがIPv4で接続していました。

OperaはIPv6周りののバグ対策として、とりあえずIPv6を使えなくしたようですね。 一時的とはいえ後ろ向きな感じがしなくてはない対応ですが、フレームワークの問題で抜本的な対策が難しかったのかなぁ…。

Mac 10.6での稼働確認

Ubuntu同様に自動的にグローバルアドレスを受信しているので、Safariなどで何も考えなくても亀が踊っています。Opera 10.10を試してみたところやはりIPv6では接続できませんでした。

さいごに

Bフレッツのアドレスを使えるのであれば、eth0とbound0を組んでradvdだけ起動してしまえば使えそうな気もします。 それにサブネットを分ける必要性があるのか、という気もします。 まぁせっかくalixにはNICが3つも付いているので、これもまた良いでしょう。

しかし、一応動いているとはいえ頭の中が完全にIPv4のネットワークのマナーに染まっているので、IPv6的な考え方がまったくできていないと実感しています。 ここに書いた対応も成功事例ではありますが、本当のベストプラクティスとはいえないところがあるだろうと思います。まぁしばらくIPv6で遊んでみるつもりです。

この後の残作業はFirewall周りの他に、基本的なところでDTCPクライアントからeth1, eth2にサブネットワークを構成して、そのアドレスを反映したradvd.confを作成するところまでを自動化する必要があります。

まっさらなところからの自動化自体は簡単ですが、途中からのやり直しなんかを考えると、各作業の粒度を考えて個別のスクリプトにしないと安定しそうにありません。これも少し時間を考えて試行錯誤したいと思います。

とりあえずは次のステップはfirewallの設定ですかね。