FreeBSDでは、jailと呼ばれる仕組みを利用することにより、FreeBSD環境上に仮想のFreeBSD環境を複数構築することが出来ます。
jailはUNIXに古くから存在するchrootを発展させたもので、ホスト環境(以下、便宜上「jailer」と呼びます)上に構築したjail環境(以下、便宜上「prisoner」と呼びます)は、ホスト環境とは別に動作するFreeBSDマシンとして振舞います。
prisonerからはjailerおよび並列する別のprisonerに直接アタッチすることが出来ないため、システム管理者がシステムの一部の権利を他者に委譲しなくてはならない場合等に有効な解決策となります。
このページでは、このjailを利用した仮想FreeBSD環境の構築方法について説明します。
用途によって、jailが有効な場合とそうでない場合があります。
ここでは、jailの特徴と向いている用途について説明します。
jailの主な特徴は以下の通りです。
・エミュレーションではないため、高速に動作する
・prisonerごとにjailerとは別のIPアドレスをひとつ振ることができる
・各prisonerごとのリソース配分を予め設定しておくことができない
・jailerとprisonerで同一のファイルシステムが利用されるため、UID/GIDの割り当てに注意する必要がある
jailの一般的な用途としては、
・脆弱性などの懸念があるソフトウェアを安全に(jailerに影響を与えないように)動作させたいとき
・システム管理者がシステムの一部の権利を委譲したいとき
が挙げられます。
応用例として、「portsからのpackage構築を、jailerにpackageをインストールすることなく実行する」ようなことも可能です。
一方、各prisonerごとにIPアドレスがひとつ振られてしまうため、複数のサーバ名を持つWebサーバ(apache等)をネームベースのvirtualホストで構築するような用途には向きません。
それでは、早速jail環境の構築に入ります。
以下では、jailer内に4つのprisoner(prisoner1〜prisoner4)を構築する手順について説明しています。
自分が構築したいprisonerの数にあわせ、適宜読み替えてください。
まずはじめに、各prisonerのルートディレクトリになるディレクトリを作成します。
$ mkdir /home/jail $ mkdir /home/jail/prisoner1 $ mkdir /home/jail/prisoner2 $ mkdir /home/jail/prisoner3 $ mkdir /home/jail/prisoner4 |
上記の例では、各prisonerのルートを「/home/jail」下に用意していますが、必ずしもこの場所に作らなくてはいけないわけではありません。
自分のご趣味に合わせ、お好みの場所に作成してください。
次に、各prisoner環境にインストールするバイナリ一式を構築します。
以下、buildworldと同じ手順で、バイナリ一式を構築してください。(環境にもよりますが、この作業には3時間程度かかります)
$ cd /usr/src $ make buildworld |
バイナリ一式の構築が完了したら、下記の手順で各prisonerにインストールします。
$ cd /usr/src $ make installworld DESTDIR=/home/jail/prisoner1 $ make installworld DESTDIR=/home/jail/prisoner2 $ make installworld DESTDIR=/home/jail/prisoner3 $ make installworld DESTDIR=/home/jail/prisoner4 |
jailerにインストールする場合と違い、シングルユーザモードにする必要はありません。
バイナリ一式のインストールが完了したら、下記の手順で設定ファイル一式を各prisonerにインストールします。
$ cd /usr/src/etc $ make distribution DESTDIR=/home/jail/prisoner1 $ make distribution DESTDIR=/home/jail/prisoner2 $ make distribution DESTDIR=/home/jail/prisoner3 $ make distribution DESTDIR=/home/jail/prisoner4 |
次に、各prisonerで名前解決ができるよう「resolv.conf」を用意します。
ほとんどのケースで「resolv.conf」の内容は、jailerと同じ設定で問題ないため、このファイルをjailer側からコピーします。
$ cp -p /etc/resolv.conf /home/jail/prisoner1/etc/ $ cp -p /etc/resolv.conf /home/jail/prisoner2/etc/ $ cp -p /etc/resolv.conf /home/jail/prisoner3/etc/ $ cp -p /etc/resolv.conf /home/jail/prisoner4/etc/ |
続いて、各prisonerのログに、kernel関係の出力がされないよう、「syslog.conf」を編集して設定します。
$ vi /home/jail/prisoner1/etc/syslog.conf $ vi /home/jail/prisoner2/etc/syslog.conf $ vi /home/jail/prisoner3/etc/syslog.conf $ vi /home/jail/prisoner4/etc/syslog.conf |
それぞれ「syslog.conf」の「*.err;kern.warning;auth.notice;mail.crit /dev/console」の行をコメントアウトしてください。
続いて、各prisonerのcronによってkernelのタイムゾーンが設定されないよう、crontabを編集します。
(kernelのタイムゾーン設定はjailer側のcronで定期的に行われるため、prisonerでのcronによる設定は不要です。)
$ vi /home/jail/prisoner1/etc/crontab $ vi /home/jail/prisoner2/etc/crontab $ vi /home/jail/prisoner3/etc/crontab $ vi /home/jail/prisoner4/etc/crontab |
それぞれ「crontab」の「1,31 0-5 * * * root adjkerntz -a」の行をコメントアウトしてください。
各prisonerで、自身の名前を解決できるように「hosts」を編集します。
$ vi /home/jail/prisoner1/etc/hosts $ vi /home/jail/prisoner2/etc/hosts $ vi /home/jail/prisoner3/etc/hosts $ vi /home/jail/prisoner4/etc/hosts |
それぞれのファイルの一番下に「192.168.16.128 prisoner1.kishiro.local」のように、各prisonerに割り当てるIPアドレスとFQDNのペアを追記してください。
jailでは、jailerと各prisonerでひとつのファイルシステムを共有します。
このため、jailer側で利用しているUID/GIDをそのままprisoner側で利用してしまうと、jailer側のユーザがprisoner側のユーザのファイルを自分のものとして扱えてしまうことになります。
これを回避するためには、jailerと各prisonerで重複しないUID/GIDを振る必要があります。
下記のようにして、UID/GIDの帯域をそれぞれ設定してください。
$ vi /etc/pw.conf $ vi /home/jail/prisoner1/etc/pw.conf $ vi /home/jail/prisoner2/etc/pw.conf $ vi /home/jail/prisoner3/etc/pw.conf $ vi /home/jail/prisoner4/etc/pw.conf |
ファイルの中には、以下のようにしてUID/GIDの範囲を指定します。
jailerおよび各prisonerごとにminuid/maxuid、およびmingid/maxgidを設定し、jailerおよび各prisoner間で同じUID/GIDが割り当てられないようにしてください。
defaultpasswd no minuid 10000 maxuid 19999 mingid 10000 maxgid 19999 |
上記の例では、UIDおよびGIDがそれぞれ10000〜19999の範囲に入るように割り当てられます。
ただし、この作業をしても「root」等の予め定義されているユーザ、および「pgsql」や「postfix」等のports経由で追加されるユーザについては、jailerとprisonerで同じUID/GIDが割り当てられます。
これらのユーザのUID/GIDを変更するには、「vipw」を利用して、直接UID/GIDを変更してください。
(「vipw」でUID/GIDを変更しても、これらのユーザが所有者となっているファイルのオーナーのUID/GIDは自動的には更新されません。自分でchown/chgrpしてやる必要があるので、注意してください。)
上記の手順で、kernel以外のファイル一式が各prisonerにインストールされますが、インストールされたファイルの中には、prisoner側で不要なファイルも含まれています。
本来ならば、これらのファイルをひとつずつ消していかないといけないのですが、なんとprisonerで不要なファイルをリストで提供されている方がいらっしゃいます。
この不要ファイルのリストをありがたく利用させていただくことにします。
以下の手順で不要ファイルのリストを取得し、各prisonerにコピーしてください。
$ fetch http://memberwebs.com/stef/freebsd/jails/docs/jail_remove_5.x.txt $ cp -p jail_remove_5.x.txt /home/jail/prisoner1/remove.txt $ cp -p jail_remove_5.x.txt /home/jail/prisoner2/remove.txt $ cp -p jail_remove_5.x.txt /home/jail/prisoner3/remove.txt $ cp -p jail_remove_5.x.txt /home/jail/prisoner4/remove.txt |
削除リストを取得したら、次に各prisonerでデバイスが利用できるよう、各prisonerのdevをマウントしておきます。
$ mount_devfs devfs /home/jail/prisoner1/dev $ mount_devfs devfs /home/jail/prisoner2/dev $ mount_devfs devfs /home/jail/prisoner3/dev $ mount_devfs devfs /home/jail/prisoner4/dev |
上記までで、jailer側から可能なprisonerの設定は終わりました。
次は、「chroot」を利用して各prisonerのルートディレクトリに遷移し、必要な設定を行います。
「chroot /home/jail/prisoner1」のように、各prisonerごとにルートディレクトリに遷移し、以下の作業を行ってください。
(chrootしたディレクトリからjailer側に戻るときには「exit」を利用してください。)
$ chflags noschg /sbin/init /usr/sbin/sliplogin $ cat remove.txt | xargs rm -rf $ unlink /etc/rmt $ ln /usr/bin/true /sbin/init $ touch /etc/fstab $ newaliases $ touch /etc/wall_cmos_clock $ ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime $ passwd root |
いちいち実行してくのが面倒な方は、上記の内容をシェルスクリプトとして用意しておりますので、ご利用ください。
一番最後の処理では、rootのパスワードを変更しています。
そのprisonerにおけるrootのパスワードを設定してください。(このパスワードはjailerのrootのパスワードとは区別されます。)
上記までで、各prisoner内の設定は完了しました。
最後に、jailer側で各prisoner側に割り当てるIPアドレス、FQDN等を設定します。
「/etc/rc.conf」に以下のように追記してください。
ifconfig_fxp0_alias0="inet 192.168.16.128 netmask 255.255.255.255" ifconfig_fxp0_alias1="inet 192.168.16.129 netmask 255.255.255.255" ifconfig_fxp0_alias2="inet 192.168.16.130 netmask 255.255.255.255" ifconfig_fxp0_alias3="inet 192.168.16.131 netmask 255.255.255.255" jail_enable="YES" jail_list="prisoner1 prisoner2 prisoner3 prisoner4" jail_set_hostname_allow="NO" jail_socket_unixiproute_only="YES" jail_sysvipc_allow="YES" jail_stop_jailer="NO" jail_prisoner1_rootdir="/home/jail/prisoner1" jail_prisoner1_hostname="prisoner1.kishiro.local" jail_prisoner1_ip="192.168.16.128" jail_prisoner1_exec="/bin/sh /etc/rc" jail_prisoner1_devfs_enable="YES" jail_prisoner1_fdescfs_enable="NO" jail_prisoner1_procfs_enable="YES" jail_prisoner2_rootdir="/home/jail/prisoner2" jail_prisoner2_hostname="prisoner2.kishiro.local" jail_prisoner2_ip="192.168.16.129" jail_prisoner2_exec="/bin/sh /etc/rc" jail_prisoner2_devfs_enable="YES" jail_prisoner2_fdescfs_enable="NO" jail_prisoner2_procfs_enable="YES" jail_prisoner3_rootdir="/home/jail/prisoner3" jail_prisoner3_hostname="prisoner3.kishiro.local" jail_prisoner3_ip="192.168.16.130" jail_prisoner3_exec="/bin/sh /etc/rc" jail_prisoner3_devfs_enable="YES" jail_prisoner3_fdescfs_enable="NO" jail_prisoner3_procfs_enable="YES" jail_prisoner4_rootdir="/home/jail/prisoner4" jail_prisoner4_hostname="prisoner4.kishiro.local" jail_prisoner4_ip="192.168.16.131" jail_prisoner4_exec="/bin/sh /etc/rc" jail_prisoner4_devfs_enable="YES" jail_prisoner4_fdescfs_enable="NO" jail_prisoner4_procfs_enable="YES" |
jailでは、jailerで認識しているネットワークデバイス(IPアドレスが割り当て済みのもの)に対し、別のIPアドレスを複数割り当て(alias)、それらのIPアドレスを各prisonerに設定します。
上記の例では、「fxp0」に対し、jailer側で割り当てられているIPアドレスとは別に、「192.168.16.128〜192.168.16.131」のIPアドレスを割り当てています。
aliasされたIPアドレスは「jail_prisoner?_ip=」の部分で各prisonerに割り当てています。
「jail_sysvipc_allow=」は、prisoner上のプロセスからシェアドメモリにアクセスさせるか否かの設定です。
セキュリティの観点からすると「No」にしておくのがベストですが、PostgreSQLなどのシェアドメモリを利用するソフトウェアを動かす際には「Yes」に設定してください。
また、デフォルトの状態では、prisonerではtracerouteやping、bpfを利用したWIDE-DHCP等のraw socketを利用するプログラムが正常に動作しません。
これらのプログラムを利用するために、jailerの「/etc/sysctl.conf」に以下の記述を追記してください。
security.jail.allow_raw_sockets=1 |
以上で設定は完了です、システムを再起動させて各prisonerを有効にしてください。
prisoner内のportsが古くなったり、設定を誤ったりして、prisoner環境を再度一から構築しなおす場合には、以下のようにしてprisonerを削除し、また上記手順を踏んで環境を再構築してください。
$ /etc/rc.d/jail stop prisoner1 $ chflags -R noschg /home/jail/prisoner1/* $ rm -R /home/jail/prisoner1/* |
上記の例では、prisoner1を停止させ、そのルートディレクトリ下のファイルをすべて削除します。
jailerから各prisonerを制御するためのコマンドがいくつか用意されています。
jailer上で「jis」と入力すると、現在稼動中のprisonerの一覧を下記のように表示させることができます。
JID IP Address Hostname Path
4 192.168.16.131 prisoner4.kishiro.local /home/jail/prisoner5
3 192.168.16.130 prisoner3.kishiro.local /home/jail/prisoner4
2 192.168.16.129 prisoner2.kishiro.local /home/jail/prisoner3
1 192.168.16.128 prisoner1.kishiro.local /home/jail/prisoner1
|
一番左側に表示される「JID」はprisonerに割り振られたIDで、後述する「jexec」で遷移するprisonerを特定する際に利用します。
jailer上から「jexec」を利用することによって、任意のprisonerにrootとして遷移することができます。
このコマンドはrootのみ実行できます。
$ jexec 1 /bin/csh |
上記の例では、JIDが「1」のprisonerに「/bin/csh」のシェルを利用して遷移します。
prisonerの開始/停止については、「/etc/rc.d/jail」を利用して実現できます。
prisonerすべてを停止させるには、以下のようにします。
$ /etc/rc.d/jail stop |
逆に、すべてのprisonerを開始するには、以下のようにします。
$ /etc/rc.d/jail start |
すべてのprisonerを開始/停止させるだけでなく、特定のprisonerを指定して開始/停止させることも可能です。
特定のprisonerを指定して開始/停止させるには、以下のようにします。
$ /etc/rc.d/jail start prisoner1 |
$ /etc/rc.d/jail stop prisoner1 |
上記手順で作成したprisoner環境では、「sysinstall」が利用できないため、このユーティリティを利用したユーザ/グループの追加ができません。
prisoner環境でユーザ/グループを追加するには、以下のようにしてください。
$ adduser |
$ pw groupadd user |
上記の例では「user」という名のグループを作成しています。
prisoner上では無論sshdやapache等を動作させることが可能ですが、その際に1つだけ注意する点があります。
それは、prisoner上やjailer上で動作させるsshd/apacheの設定で、明示的にListenするIPアドレスおよびPort番号を明示しなくてはいけない点です。
たとえば、jailer側でsshdを動作させている場合、設定ファイルの中でListenするIPアドレスを明示的に指定していないことが多いと思います。
ListenするIPアドレスを明示していない場合、sshdはそのマシンに割り当てられているIPアドレスのすべてでListenします。
aliasしたIPアドレスについても「マシンに割り当てられているIPアドアドレス」にあたるため、prisonerに割り当てられているIPアドレスでjailer上のsshdがListenしてしまうことになり、結果的にprisoner上でsshdが起動できなくなってしまいます。
これを回避するために、jailerの「/etc/ssh/sshd_config」に下記の一行を追加してください。
ListenAddress 192.168.16.1 |
「192.168.16.1」にはjailerのIPアドレスを指定してください。
また、prisoner上でsshdを動作させる際にも、prisonerの「/etc/ssh/sshd_config」内の「ListenAddress」に、そのprisonerのIPアドレスを指定するようにしてください。
上記はsshdの例でしたが、apacheでも同様にjailer上のapacheがListenするIPアドレス、およびprisoner上のapacheがListenするIPアドレスを明示的に指定する必要があります。
apacheではjailer上およびprisoner上の「/usr/local/etc/apache/httpd.conf」に下記の一行を追加してください。
Listen 192.168.16.1:80 Listen 192.168.16.1:443 |
「192.168.16.1」の部分には、それぞれjailerのIPアドレス、prisonerのIPアドレスを指定してください。
また、80番ポートはHTTP、443ポートはHTTPSになりますので、必要なもののみ適宜してください。
次に、prisoner上でPostgreSQLを動作させる際の注意点ですが、PostgreSQLはOSのシェアドメモリを利用するため、設定が多少煩雑です。
まずはじめに、jailerの「/etc/rc.conf」に以下の記述があるか確認し、なければ追記してください。
jail_sysvipc_allow="YES" |
上記の設定は前述した通り、prisoner内のプロセスからjailerのシェアドメモリにアクセスさせるための設定です。
次に、複数のprisoner上およびjailer上で同時にPostgreSQLを動作させるには、このシェアドメモリ内の利用が重複しないよう、各prisoner上およびjailer上のpgsqlユーザのUID/GIDを重複がないように修正してやる必要があります。PostgreSQLを動作させるprisonerごとに、prisoner内で「vipw」を利用し、「pgsql」ユーザのUID/GIDを重複しないよう変更してください。
pgsql:*:10070:10070:PostgreSQL Daemon:/usr/local/pgsql:/bin/sh |
繰り返しになりますが、「vipw」でUID/GIDを変更しても、これらのユーザが所有者となっているファイルのオーナーのUID/GIDは自動的には更新されません。
PostgreSQLをportsからインストールした直後の状態では、「pgsql」がオーナーのファイルは「/usr/local/pgsql」フォルダのみですので、下記のようにしてオーナーをpgsqlに変更しなおしてください。
$ cd /usr/local $ chown -R pgsql pgsql $ chgrp -R pgsql pgsql |
あとは、「PostgreSQL環境を構築する(PostgreSQL 8.3.x)」の内容を参考にして、DB等の設定を行ってください。
あと、prisoner上のPHP環境等から「localhost」上のDBに接続を試みても、同じprisoner上で動作するPostgreSQLに接続できない場合があります。
(その際、該当するprisonerの「/var/log/message」に以下のメッセージが表示されます)
FATAL: no pg_hba.conf entry for host "192.168.16.128", user "pgsql", database "xxxx" |
この問題を回避するには、「/usr/local/pgsql/data/pg_hba.conf」の最後に以下の一行を追加してください。
host all all 192.168.16.128/32 trust |
「192.168.16.128」の部分には、PostgreSQLを動作させているprisonerのIPアドレスを指定してください。
