軽井沢の別荘に温度・湿度を測定するマイコンを設置し、そのデータを東京本宅まで転送して365日24時間に渡りデータを収集し、それをグラフ化しようと考えている。そのためには東京本宅入り口のルーターに、ファイル転送の為の「穴」をあけなければならない。
アクセス元が固定IPであるならば、ルーターまたはサーバーでアクセス元IPによるアクセス制御を設定してしまえば、まず安全な環境となる。しかし、軽井沢別邸のインターネット環境は、固定IPに掛かる余計な出費を嫌い、動的IPとなっている。また、本事案はゆくゆくは商用サービスとして昇華することが出来る可能性があるので、任意のクライアントIPからのアクセスを許可したい。なので、任意のアクセス元IPからのファイル転送を「セキュアに」実現する必要がある。
インターネットに任意のIPからアクセス可能な「穴」をあけるのは、実は非常に危険なことである。インターネットはもともと、米国防総省(DOD)において、核戦争にも耐えうるリライアブルな(信頼性の高い)ネットワークを実現するプロジェクトとしてスタートした。その後民間に払い下げられ、大学における学術ネットワークとして普及した。大学間であるならば、それ程「悪意のある」アクセスはないので、インターネットの原初のプロトコルは性善説に基づく「隙だらけ」なものとして始まり、その後商用利用が始まった段階で様々なセキュリティ問題をさらけ出し、泥縄式の対策によって今日に至っている。まぁ、とにかく世界の縮図みたいな感じの悪意が存在するのよ、インターネットにわ。
本題に入ると、sshを使ってファイル転送するのだが、任意のコマンドを実行出来てしまうシェルは渡したくないし、気前良くサーバーの全ファイルシステムをベロォ~~ンと開示する気は毛頭ない。要件としては:
- sshプロトコルを使ってファイル転送をする。転送ログを残せるのでsftpが良い。
- 特定ユーザー(この設定例では"goldcup"とする)のみアクセスを許可する。また、アクセスには公開鍵認証のみを許可する。
- サーバーのファイルシステムを隠蔽する。つまり、chroot jail環境とする。
- 通常管理業務用のOS標準添付のOpenSSHとは別のOpenSSHバージョンを利用したデーモン(sshd)を、ファイル転送専用に、通常のsshd(TCP/22)とは別のポート(この設定例ではTCP/12345とする)で稼動させる。
この目的のために調べてみると、sshと組み合わせてファイル転送のみを許可する仕組みを作ることができることが分かった。rsshというツールなのだが。
設定にあたって参考としたサイトは以下の通り。
- Linux Configure rssh Chroot Jail To Lock Users To Their Home Directories Only
- log of sftp in a chroot (with rssh)
他にもrsshソースに添付のCHROOTファイルにヒントが隠されている。
それでは私が実施した手順を紹介したい。まずはOS標準添付以外のもう一つのOpenSSHをソースからコンパイルする。OpenSSHのtarballはOpenSSHよりダウンロードできる。バージョン(実施例では、バージョンX.YパッチレベルZ)は最新のものがよいであろう。
root# cd openssh-X.YpZ
root# ./configure --prefix=/usr/local/openssh-X.YpZ --with-default-path=/usr/local/openssh-X.YpZ/bin:/usr/bin:/bin:/usr/sbin:/sbin --with-privsep-path=/usr/local/openssh-X.YpZ/var/empty --with-pid-dir=/usr/local/openssh-X.YpZ/var/run
root# make
root# make install
root# cd /usr/local
root# ln -s openssh-X.YpZ openssh
OS標準のOpenSSHとバッティングしてしまっては困るので、色々とオプションを付けておいた。
次に、設定ファイル(/usr/local/openssh/etc/sshd_config)を編集しておく。
root# cat sshd_config
...
Port 12345
...
PermitRootLogin no
...
PasswordAuthentication no
...
ChallengeResponseAuthentication no
...
# http://kb.monitorware.com/log-sftp-chroot-with-rssh-t10497.html
Subsystem sftp /usr/local/openssh-X.YpZ/libexec/sftp-server -f LOCAL6 -l VERBOSE
...
# http://www14.plala.or.jp/campus-note/vine_linux/server_ssh/ssh_filter.html
#DenyUsers goldcup
AllowUsers goldcup
...
# Mitigate floody crack access.
# http://www14.plala.or.jp/campus-note/vine_linux/server_ssh/ssh_filter.html
MaxStartups 2:70:5
root#
見たところ自明であろう。TCP/12345でssh接続要求を受付け、パスワード認証、チャレンジレスポンス認証を禁止し、rootユーザーでのログオンを禁止し、sftpサーバーは今回コンパイルしたものを使用する。
sftpサーバーのsyslog出力に関しては、log of sftp in a chroot (with rssh)に記述がある。私の設定例では、sftp-serverのログはsyslogファシリティLOCAL6(後にsftp専用として設定する)に出力される。VERBOSEとしてあるのは、sftpセッション中のユーザー操作を細かく記録したいから。sftpコマンドとしては記録されないが、セキュリティに関係するインシデントが逐一記録されるので、それからユーザーの実行しているsftpコマンドを推定することが出来る。
アクセス許可ユーザーについては、AllowUsers行(接続制限の設定)を指定してやればよい。逆に、DenyUsers行を指定する方法もある。ホワイトリスト方式なのかブラックリスト方式なのかということだ。便利な方を使えばよい。
更に、任意のIPからの不正アクセスが頻発すると予想されるので、接続制限の設定の「SSH の接続要求を制限する」の項にて紹介されているいわゆる「スロットル設定」を行っておく。短い時間にバコバコアクセスしてくる輩には接続を受け付けなくする設定だ。良くあるパスワードクラック攻撃を想定した対策だね。
最後に、Linux標準添付のinitスクリプト(/etc/init.d/sshd)をsshd2としてコピーし編集して、Linuxに登録する。そして、sftp転送専用のsshdインスタンス(/etc/init.d/sshd2)を起動する。
root# cp sshd sshd2
root# vi sshd2
...
root# cat sshd2
#!/bin/bash
#
# Init file for Another OpenSSH server daemon
#
# chkconfig: 2345 55 25
# description: OpenSSH server daemon
#
# processname: sshd
# config: /usr/local/openssh/etc/ssh_host_key
# config: /usr/local/openssh/etc/ssh_host_key.pub
# config: /usr/local/openssh/etc/ssh_random_seed
# config: /usr/local/openssh/etc/sshd_config
# pidfile: /usr/local/openssh/var/run/sshd.pid
# source function library
. /etc/rc.d/init.d/functions
# pull in sysconfig settings
[ -f /etc/sysconfig/sshd2 ] && . /etc/sysconfig/sshd2
RETVAL=0
prog="sshd"
# Some functions to make the below more readable
SSHD=/usr/local/openssh/sbin/sshd
PIDFILE=/usr/local/openssh/var/run/sshd.pid
runlevel=$(set -- $(runlevel); eval "echo \$$#" )
start()
{
echo -n $"Starting $prog: "
$SSHD $OPTIONS && success || failure
RETVAL=$?
[ "$RETVAL" = 0 ] && touch /var/lock/subsys/sshd2
echo
}
stop()
{
echo -n $"Stopping $prog: "
if [ -f $PIDFILE ]; then
pid=`cat $PIDFILE`
kill $pid
else
failure $"Stopping $prog"
fi
RETVAL=$?
# if we are in halt or reboot runlevel kill all running sessions
# so the TCP connections are closed cleanly
if [ "x$runlevel" = x0 -o "x$runlevel" = x6 ] ; then
trap '' TERM
killall $prog 2>/dev/null
trap TERM
fi
[ "$RETVAL" = 0 ] && rm -f /var/lock/subsys/sshd2
echo
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo $"Usage: $0 {start|stop|restart}"
RETVAL=1
esac
exit $RETVAL
root#
root# chkconfig --add sshd2
root# /etc/init.d/sshd2 start
起動したかどうかは、ポート(TCP/12345)を見るなり、プロセスを見るなりすれば確認できる。
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 1910/sshd
tcp 0 0 192.168.101.254:12345 192.168.101.100:51949 ESTABLISHED 32011/sshd: goldcup
root#
root# ps -ef|grep openssh
root 758 28758 0 15:34 pts/0 00:00:00 grep openssh
root 1910 1 0 05:04 ? 00:00:00 /usr/local/openssh/sbin/sshd
root#
sftp専用のsshdは起動した?よかったね。
次に、rsshを入れてしまおう。rsshは、rssh homepageよりダウンロードする。
root# make
root# make install
root# cd /usr/local
root# ln -s rssh-x.y.z rssh
一応念のため、scpとsftpを使用可能とし、先ほど入れたOpenSSHバージョンX.YpZのバイナリを指定する。
忘れないうちに、sftpユーザー(goldcup)を作成し、そのログインシェルをrsshに変更しておこう。
root# passwd goldcup
...
root# cat /etc/passwd
...
goldcup:x:505:505::/home/goldcup:/usr/local/rssh/bin/rssh
...
root#
rsshの設定ファイル(/usr/local/rssh/etc/rssh.conf)も編集しておく。
root# vi rssh.conf
...
allowsftp
#allowcvs
#allowrdist
#allowrsync
...
umask = 002
...
chrootpath = /home/jail
...
root#
sftpのみを許可し、chroot jailは/home/jailとしている。
まだまだ設定は続くよ。次はchroot jail設定をやるんだから!!
実は、chroot jailは万能ではない。Best Practices for UNIX chroot() Operations に色々と注意点が書いてある。jail内に使わない余計なファイルを置いたり、外部ファイル・ディレクトリへのハードリンクを置いたり、setuid-rootプログラムを置いたりしない事は常識かな。jailに閉じ込めるプロセスをroot権限で走らせるのももちろんご法度。root権限を盗られてしまうと、jail環境外へ通じる道が開けてしまう可能性が高くなるらしいからね。
まぁ、そういうことだ。今回はrsshでchrootするので、「親」のsshdはroot権限が必要だね。しかし、sftp-serverが起動すると、これはsftpユーザーの権限で稼動するので安心だ。試しに私のところでsftp-serverが稼働しているサーバー上で、sftpクライアントで接続中のプロセスのステータスを覗いてみると:
root# cat status
Name: sftp-server
State: S (sleeping)
Tgid: 32017
Pid: 32017
PPid: 32016
TracerPid: 0
Uid: 505 505 505 505
Gid: 505 505 505 505
...
nonvoluntary_ctxt_switches: 31
root#
興味があるのは、Uid/Gidの行だ。これだけでは良く分からないが、uid and gid fileds from /proc/pid/status によれば、左から順番に、実(real)UID、実効(effective)UID、保存(saved-set)UID、ファイルシステム(file system)UIDという順番らしい。GIDも同様。これらはすべてsftpでアクセスしているユーザー(goldcup)のものでありroot(UID=0,GID=0)ではないので、ひとまず安心であろう。
ではchroot jail設定を始めよう。参考とした記事は、Linux Configure rssh Chroot Jail To Lock Users To Their Home Directories Only。実は、rsshにLinux用のchroot jail作成用のスクリプト(mkchroot.sh )があるらしいのだけれども、僕は試していない。まぁ、手作業で汗をかけば、原理原則も見えてくるだろうから、今回は手作業でjail環境を構築した。
chrootのベースディレクトリは/home/jail。ここに必要なディレクトリを作成し、ファイルをコピーしていく。(私の例では64bit版Fedora 10なので、libディレクトリではなく、lib64ディレクトリが必要となる。)また、sftpで転送してきたファイルはバッチ処理するはずなので、バッチが稼動するユーザーとグループ(それぞれbatchユーザー、batchグループとする)を用意しておき、goldcupのchroot jail内ホームディレクトリのオーナーグループをbatchグループとし、かつ同ディレクトリのsetgidビットを立てておくことにより、同ディレクトリ内で作成されるファイルのグループオーナーが自動的にbatchグループによる所有となるようにしておく。rssh.conf中のumask設定を"002"としておいたのは、転送してきたファイルに対するグループ書き込み許可をデフォルトで付与したいから。(効いていないようなので、sftpバッチ処理中で明示的にchmodしているが。)
僕のサーバーもそうだけど、Fedoraを使用していると、ディレクトりのsetuid/setgid/stikcy等のファイルアクセスモードビットを立てると、二度と解除できなくなる事がある旨を指摘しておこう。おそらくCentOS等ではこのようなイレギュラーな問題が起こることはないはずなのだが。
root# mkdir jail
root# cd jail
root# mkdir bin
root# mkdir dev
root# chmod 111 dev
root# cd dev
root# mknod -m 666 null c 1 3
root# cd ..
root# mkdir etc
root# mkdir -p home/goldcup
root# mkdir lib64
root# mkdir -p usr/local/openssh-X.YpZ
root# mkdir -p usr/local/rssh-x.y.z
root# cd home
root# chown goldcup:batch goldcup
root# chomod 2770 goldcup
root# cd /bin
root# cp bash /home/jail/bin/.
root# cd /etc
root# cp group /home/jail/etc/.
root# cp hosts /home/jail/etc/.
root# cp ld.so.cache /home/jail/etc/.
root# cp -Rp ld.so.cache.d /home/jail/etc/. # If any.
root# cp -Rp ld.so.conf.d /home/jail/etc/.
root# cp nsswitch.conf /home/jail/etc/.
root# cp passwd /home/jail/etc/.
root# cp resolv.conf /home/jail/etc/.
root# cd /usr/local/openssh-X.YpZ/bin
root# cp scp /home/jail/usr/local/openssh-X.YpZ/bin/.
root# cp sftp /home/jail/usr/local/openssh-X.YpZ/bin/.
root# cd /usr/local/openssh-X.YpZ/libexec
root# cp sftp-server /home/jail/usr/local/openssh-X.YpZ/libexec/.
root# cd /usr/local/rssh-x.y.z/bin
root# cp rssh /home/jail/usr/local/rssh-x.y.z/bin/.
root# cd /usr/local/rssh-x.y.z/libexec
root# cp rssh_chroot_helper /home/jail/usr/local/rssh-x.y.z/libexec/.
root# chmod 0555 /home/jail/usr/local/rssh-x.y.z/libexec/rssh_chroot_helper
特に、rssh_chroot_helperはもともとsetuid-rootプログラムだ。chrootするために必要な権限なんだろうね。しかし、chroot jail内では、もはや必要ない権限なので、setuidビットは明示的に外してある。chroot jail内のrssh_chroot_helper実行イメージから未ロードのページキャッシュをメモリ上にロード出来さえすればいいのだから。chroot jail内でroot権限を取得させるような設定は絶対にダメ!ダメ!
/home/jail/etc/passwdファイル中のエントリーは、sftpログインするユーザーと、batchユーザーのみにしておこうね。
root# cat passwd
batch:x:504:504::/home/batch:/sbin/nologin
goldcup:x:505:505::/home/goldcup:/sbin/nologin
root#
batchユーザーは不要かもしれない。また、シェルの部分を編集して、/sbin/nologinとしている。chroot jail内で必要な情報は、sftpユーザーのUID/GIDのみなのよ。もっとも、rssh実行イメージがchroot jail内に存在するので、ちょっと良く調べればバレバレなのであるが。/home/jail/etc/groupファイルも同様にね。chroot jail内に侵入する攻撃者に余計な情報を提供する必要はないんだから。
さて、次のステップとして、各実行イメージが必要とする共有オブジェクトをchroot jail内にコピーする必要がある。各実行イメージに対してlddコマンドを当てて調べることが出来るけれども、いちいちこれを手でやっているのは大層なので、Linux Configure rssh Chroot Jail To Lock Users To Their Home Directories Onlyにて紹介されているl2chrootスクリプトを利用する。chroot jailのベースディレクトリを/home/jailに変更したものはこちら。
# Use this script to copy shared (libs) files to Apache/Lighttpd chrooted
# jail server.
# ------------------------------------------- ------------------------
# Written by nixCraft <http://www.cyberciti.biz/tips/>
# (c) 2006 nixCraft under GNU GPL v2.0+
# + Added ld-linux support
# + Added error checking support
# --------------------------------------------------------------------
# See url for usage:
# http://www.cyberciti.biz/tips/howto-setup-lighttpd-php-mysql-chrooted-jail.html
# --------------------------------------------------------------------
# Script source :
# http://www.cyberciti.biz/files/lighttpd/l2chroot.txt
# Set CHROOT directory name
#BASE="/webroot"
BASE="/home/jail"
if [ $# -eq 0 ]; then
echo "Syntax : $0 /path/to/executable"
echo "Example: $0 /usr/bin/php5-cgi"
exit 1
fi
[ ! -d $BASE ] && mkdir -p $BASE || :
# iggy ld-linux* file as it is not shared one
FILES="$(ldd $1 | awk '{ print $3 }' |egrep -v ^'\(')"
echo "Copying shared files/libs to $BASE..."
for i in $FILES
do
d="$(dirname $i)"
[ ! -d $BASE$d ] && mkdir -p $BASE$d || :
/bin/cp $i $BASE$d
done
# copy /lib/ld-linux* or /lib64/ld-linux* to $BASE/$sldlsubdir
# get ld-linux full file location
sldl="$(ldd $1 | grep 'ld-linux' | awk '{ print $1}')"
# now get sub-dir
sldlsubdir="$(dirname $sldl)"
if [ ! -f $BASE$sldl ];
then
echo "Copying $sldl $BASE$sldlsubdir..."
/bin/cp $sldl $BASE$sldlsubdir
else
:
fi
# EOF
このスクリプトを使って、chroot jail内の各実行イメージが使用する共有ライブラリをchroot jail内にコピーする。
root# l2chroot /usr/local/openssh-X.YpZ/bin/scp
root# l2chroot /usr/local/openssh-X.YpZ/bin/sftp
root# l2chroot /usr/local/opnessh-X.YpZ/libexsec/sftp-server
root# l2chroot /usr/local/rssh-x.y.z/bin/rssh
root# l2chroot /usr/local/rssh-x.y.z/libexec/rssh_chroot_helper
原理的にはこれだけでOKなはずなのだけれども、現実にはそうは立ち行かない。rsshのtarball中、CHROOT文書に書いてある通り、認証関係のライブラリをchroot jail内にコピーしておかなければならない。通常のセットアップであるならば、/lib64/libnss_files.so.?だけれども、LDAP認証でログインする人ならば、/lib64/libnss_ldap.so.?ファイルが必要となるらしい。僕の場合は前者なので、当該共有オブジェクトをコピーすると共に、/lib64中と同様にシンボリックリンクを張っておく。
root# cp libnss_files-X.Y.so /home/jail/lib64/.
root# cd /home/jail/lib64
root# ln -s libnss_files-X.Y.so libnss_files.so.2
よし、これでchroot jailの準備は終わったね。
まだまだ!!何だ、もう疲れちゃったの?sftpの出力を記録するsyslogの設定が残っているんだけど。しかも今回はchroot jailとなるので、その点も考慮する必要がある。
古き良きsyslogであるならば、Linux Configure rssh Chroot Jail To Lock Users To Their Home Directories OnlyのModify syslogd configurationの項が参考となる。 /etc/sysconfig/syslogファイルを編集して、chroot jail内に追加のログ用Unixドメインソケットを作成するようにして、syslogを再起動する。この設定例ではLOCAL6ファシリティをsftpサーバー用に割り当てる設定を追加している。sftpのログは/var/log/sftp.logに保存される。
root# cat syslog
SYSLOGD_OPTIONS="-m 0 -a /home/jail/dev/log"
root# cd /etc
root# cat syslog.conf
...
local6.* /var/log/sftp.log
...
root# /etc/init.d/syslog restart
さて、僕のサーバーの場合は、より新しいrsyslogとなっている。いくつかやり方はあるんだろうけど、rsyslog.confの編集だけで、syslogと同様にchroot jail内Unixドメインソケット追加とLOCAL6ファシリティの追加ができる。もちろん、設定ファイル編集後、rsyslogを再起動する。
root# cat rsyslog.conf
...
# http://kb.monitorware.com/log-sftp-chroot-with-rssh-t10497.html
$AddUnixListenSocket /home/jail/dev/log
...
#
# sftp logging
# 2012/6/21
local6.* /var/log/sftp.log
...
root#
root# /etc/init.d/rsyslog restart
これで一式設定が済んだね。やっと動作確認に入れる。
sftpクライアントは、私のThinkPad X121e Windoes 7 64bit。これになぜかOpenSSHが入っているのが、とってもマジカル!Installing OpenSSH on Windows 7の手順に従ってインストールしてある。(確か、動かすために、ちょっと手順を追加したか、ひねった記憶があるが・・・)外部からThinkPadにアクセスするようなsshdは入れていない。あくまでもクライアントのみ。空パスで鍵ペアを作成し、公開鍵認証が出来るようにセットアップしておく。公開鍵はchroot jailしたホームディレクトリ(/home/jail/home/goldcup/.ssh)ではなく、chrootする前のホームディレクトリ(/home/goldcup/.ssh)に仕込む必要がある。公開鍵認証設定手順については、例えばSSH公開鍵認証手続きをみてやっといて。Windowsの場合、HOME環境変数に自分のホームディレクトリ(C:\Users\【ログイン名】)を設定しておき、その配下に".ssh"ディレクトリを作成して鍵を保管するようにすると良い。
さて、sftpによる対話的セッションの確立とファイル転送を早速実施してみた。
転送先サーバーは、yggdrasil(ユグドラシル=世界樹)。このWEBサーバーのプライベートな名称なの。テストアカウントは、goldcup。待ちうけポートはTCP/12345。転送ファイルは、test.txt。sftpセッションを確立後ホームディレクトリへ移動して、ファイルを転送後、パーミッションを660に設定している。chroot jail内ホームディレクトリをsetgidしているためか、ファイル転送しただけだとパーミッションが000となってしまうんだよね。
転送先サーバー上で、ファイルが出来ていることを確認する。
/home/jail/home/goldcup
root# ls -l
合計 4
-rw-rw---- 1 goldcup batch 5 2012-06-30 12:22 test.txt
root# cat test.txt
bbb
root#
これならば、batchグループのユーザーによるバッチ処理でもゴリゴリと転送ファイルを処理・加工できるよね。
同じ処理を対話的セッションではなく、バッチファイル(batch.txt)にまとめて処理することができる。
こりゃ、楽チン。
一方、これら一連の操作をすると、サーバー上のログにきちんと記録が残っているのが分かる。
root# cat sftp.log
...
Jun 30 12:53:17 localhost sftp-server[9539]: session opened for local user goldcup from [10.1.1.1]
Jun 30 12:53:17 localhost sftp-server[9539]: received client version 3
Jun 30 12:53:17 localhost sftp-server[9539]: realpath "."
Jun 30 12:53:17 localhost sftp-server[9539]: realpath "/home/goldcup"
Jun 30 12:53:17 localhost sftp-server[9539]: stat name "/home/goldcup"
Jun 30 12:53:17 localhost sftp-server[9539]: open "/home/goldcup/test.txt" flags WRITE,CREATE,TRUNCATE mode 00
Jun 30 12:53:17 localhost sftp-server[9539]: close "/home/goldcup/test.txt" bytes read 0 written 5
Jun 30 12:53:17 localhost sftp-server[9539]: lstat name "/home/goldcup/test.txt"
Jun 30 12:53:17 localhost sftp-server[9539]: set "/home/goldcup/test.txt" mode 0660
Jun 30 12:53:17 localhost sftp-server[9539]: session closed for local user goldcup from [10.1.1.1]
root#
chroot後もきちんとログが残っていることに注意して欲しい。これなら運用だってばっちりでしょう?
このセットアップ方式だと、転送先サーバーのTCP/22とTCP/12345のsshd側の鍵が異なっているので、同一クライアントマシンから両ポートにアクセスするたびに%HOME%/.ssh/known_hostsに書かれているサーバー側の認証トークンのようなものが違っているよと警告されて、サーバーのエントリーをknown_hostsから削除しないとサーバーへアクセスできなくなる。だから、バッチ用クライアントからOpenSSHでアクセスするのは、TCP/12345のみにするとよい。TCP/22のシェルが欲しければ、PuTTY等を使えば問題なくサーバーへアクセスできる。
最後に一言。今回設定したサーバーがルーターやファイヤーウオールの背後にあるのであるならば、NAT設定をきちんとしておくことだ。TCP/12345へ転送するグローバルIPとポートを設定しないと、パブリックなインターネットからアクセスできないよ。個別サイトの問題だし、本稿の範囲を逸脱するので、詳述は致しませんがね。