LXCを使った権限分離とテンプレートのカスタマイズ
LXCは単に仮想環境を構築するためだけでなく、権限を分離してセキュリティを高めるという目的にも利用可能だ。前回はLXCの基本について紹介したが、今回はLXCを使用した権限分離の例や、テンプレートをカスタマイズする例を紹介する。
LXCを利用した権限の分離
LXCにはさまざまなLinuxディストリビューション環境を構築するためのテンプレートが用意されているため、仮想環境を構築するためのツールという認識を持っている人も少なくないだろう。しかし、LXCの核となっているのはLinux上のさまざまなリソースを管理する機能であるcgroupsであり、これを利用した権限の分離にLXCを利用することも可能だ。
たとえばLXCに含まれている「lxc-sshd」テンプレートでは、ホストと同一の環境をベースに制限された権限でsshdを実行させる環境を構築できる。
lxc-sshdテンプレートを使う
lxc-sshdテンプレート(以下、sshdテンプレート)は、以下のような環境を持つコンテナを作成するテンプレートだ。
- コンテナ内の/libや/bin、/usr、/sbin、/etc/sysconfig/network-scripts、/etc/rc.dといったディレクトリはホストのものをそのままリードオンリーでマウントして使用する
- /etcや/root、/var、/homeなどは独自に作成したものを使用する
- 初期状態のコンテナ内ではsshとinit、dhclientのみが稼動している
sshdテンプレートで作成されたコンテナのファイルシステムは、/homeや/etcなど一部を除き、そのままホストのファイルシステムが用いられる。つまり、ホストとほぼ同じ環境がコンテナ内で利用可能となる。また、ホストと共有しているディレクトリについてはリードオンリーになっているため、基本的にはコンテナ内部から変更できない。こういった特性から、このコンテナは外部に公開するSSHサーバーを構築する際に、権限を分離するために利用できる。さらに、このコンテナのテンプレートファイル(/usr/share/lxc/templates/lxc-sshd)はシンプルなものになっており、これをベースにカスタマイズを行うことで権限分離を行った独自のサーバー向けテンプレートを作成できる。
sshdテンプレートを使ったコンテナの作成
それでは、実際にsshdテンプレートを使ってコンテナを作成する例を見てみよう。なお、以下ではホスト環境としてCentOS 6.5を使用している。ほかのディストリビューションを利用している場合でも同様の手順で再現できると思われるが、パッケージ名やツール名などが異なる場合があるのでそちらについては適宜読み替えて欲しい。
また、LXCバージョン1.0.0~1.0.3に含まれるsshdテンプレートにはバグがあり、そのままではコンテナを作成できない。LXC 1.0では一部コマンドがリネームされているのだが、それが反映されていないためだ。LXC 1.0.4を利用するか、もしくはsshdテンプレートファイル(/usr/share/lxc/templates/lxc-sshd)内の「/usr/libexec/lxc/lxc-init」という部分をすべて「/usr/sbin/init.lxc」に置き換えることで対処できる。
さて、sshdテンプレートでは、テンプレート固有のオプションとしてコンテナ内でのrootユーザーがSSH経由でログインする際に使用するSSH公開鍵を指定する「-S」オプションが用意されており、コンテナの作成時にこのオプションで使用するSSH公開鍵を指定する。このようなコンテナ固有のオプションは、「--」オプションの後に指定する形となる。
次の例は、「sshd01」という名前のコンテナを作成し、使用する公開鍵として「~hylom/.ssh/id_rsa.pub」を指定する場合の実行例だ。
# lxc-create -n sshd01 -t sshd -- -S ~hylom/.ssh/id_rsa.pub Generating public/private rsa key pair. Your identification has been saved in /var/lib/lxc/sshd01/rootfs/etc/ssh/ssh_host_rsa_key. Your public key has been saved in /var/lib/lxc/sshd01/rootfs/etc/ssh/ssh_host_rsa_key.pub. The key fingerprint is: 70:87:8b:55:a9:09:d2:ce:45:ab:01:be:8d:7b:d3:8c root@fedora20.localdomain The key's randomart image is: +--[ RSA 2048]----+ | .. .. .. | | ...o .+. | | .+oo=o. | | +oBoo | | o + S | | . + | | . E o | | . . | | | +-----------------+ Generating public/private dsa key pair. Your identification has been saved in /var/lib/lxc/sshd01/rootfs/etc/ssh/ssh_host_dsa_key. Your public key has been saved in /var/lib/lxc/sshd01/rootfs/etc/ssh/ssh_host_dsa_key.pub. The key fingerprint is: f2:42:87:b6:ed:1f:0b:28:85:d7:b0:51:1b:c1:8a:86 root@fedora20.localdomain The key's randomart image is: +--[ DSA 1024]----+ | .+. | | ..o | | . .o.. | | E o..* | | .. O S | | = B | | . + + . | | . o . o | | ..o | +-----------------+ Inserted SSH public key from /home/hylom/.ssh/id_rsa.pub into /var/lib/lxc/sshd01/rootfs//root/.ssh
作成したコンテナはlxc-startコマンドで起動できる。以下では「-d」オプションを使用してバックグラウンドでコンテナを起動し、「-L」オプションでコンソールの出力先を「sshd01_log」というファイルに設定している。
# lxc-start -n sshd01 -d -L sshd01_log
コンテナの起動後にこのログファイルを確認すると、コンテナのIPアドレスが表示されているはずだ。この場合、「192.168.122.10」がそのIPアドレスとなる。
# cat sshd01_log Container IP address: inet 192.168.122.10 netmask 255.255.255.0 broadcast 192.168.122.255 inet6 fe80::78dc:2eff:fe25:3f3d prefixlen 64 scopeid 0x20<link>
コンテナにログインするには、コンテナの作成時に登録したSSH鍵を使ってこのIPアドレスに対しSSH接続すれば良い。
$ ssh root@192.168.122.10 The authenticity of host '192.168.122.10 (192.168.122.10)' can't be established. RSA key fingerprint is 70:87:8b:55:a9:09:d2:ce:45:ab:01:be:8d:7b:d3:8c. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.122.10' (RSA) to the list of known hosts. Enter passphrase for key '/home/hylom/.ssh/id_rsa': -bash-4.2#
ログイン後、psコマンドで実行されているプロセスを確認すると、initおよび最小限のプロセスしかコンテナ内では実行されていないことが確認できる。
# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 15116 732 ? S 13:39 0:00 /usr/sbin/init. root 100 0.0 1.2 106604 12816 ? Ss 13:40 0:00 dhclient eth0 - root 104 0.0 0.1 87176 1320 ? Ss 13:40 0:00 /usr/sbin/sshd root 105 0.0 0.4 91808 4328 ? Ss 13:44 0:00 sshd: root@pts/ root 107 0.0 0.1 17928 1704 pts/0 Ss 13:44 0:00 -bash root 108 0.0 0.1 26072 1280 pts/0 R+ 13:44 0:00 ps aux
コンテナを停止させるには、lxc-stopコマンドを利用する。
$ lxc-stop -n sshd01 -k
なお、「-k」オプションはコンテナ内で実行されているプロセスをすべてkillするという意味のオプションだ。ちなみに、コンテナ内でshutdownコマンドを実行すると、ホストOSが停止さされてしまうため注意したい。
コンテナ内にユーザーを作成する
作成されたコンテナは、デフォルトではrootおよびsshdアカウントしか用意されていない。アカウントを追加するにはコンテナ内でuseraddコマンドを実行すれば良いのだが、コンテナ内ではアカウント認証に必要となるPAM(Pluggable Authentication Modules)関連の設定ファイルが用意されていないので、そのままではuseraddコマンドやgroupaddコマンドを実行できない。そのため、まずホストで以下のように実行し、必要な設定ファイルをコンテナ内にコピーしておく必要がある。
# cp -r /etc/pam.d /var/lib/lxc/sshd01/rootfs/etc/ # cp -r /etc/login.defs /var/lib/lxc/sshd01/rootfs/etc/
続いてコンテナ内で以下のように/etc/shadowファイル(パスワードが記録されるファイル)を作成することで、useraddコマンドやgroupaddコマンドが利用できるようになる。
# touch /etc/shadow # chmod 000 /etc/shadow
たとえば、「taro」というグループおよびユーザーを作成するには、以下のようにする。
# groupadd -g 10000 taro # useradd -d /home/taro -g taro -m -u 10000 taro # chsh -s /bin/bash taro # passwd taro Changing password for user taro. New password: ←taroユーザーに設定するパスワードを入力 Retype new password: ←同じパスワードを再入力 passwd: all authentication tokens updated successfully.
以上で、コンテナ外からsshを使ってtaroユーザーとしてログインが可能となる。
コンテナ内でサービスを実行させる
続いて、コンテナ内でsshd以外のサービスを実行させてみよう。sshdテンプレートを使って作成されたコンテナでは、/usrや/lib、/bin以下などのディレクトリはホストのものを共有するため、理論上はホスト上にインストールされているすべてのソフトウェアがコンテナ内で実行可能だ。ただし、/etcや/varといったディレクトリは共有されていないため、これらディレクトリ内に格納されている設定ファイルなどはユーザー側で用意しなければならない。
今回は例として、Apache HTTP Server(httpd)をコンテナ内で稼動させてみよう。この場合、前提条件として、ホストにhttpdがインストールされていることが必要だ。あらかじめインストールを行っておこう。
# yum install httpd
httpdがどのような設定ファイルを必要とするかは、「rpm -ql」コマンドでhttpdパッケージに含まれるファイル一覧を調べることで確認できる。
# rpm -ql httpd | less /etc/httpd /etc/httpd/conf /etc/httpd/conf.d /etc/httpd/conf.d/README /etc/httpd/conf.d/welcome.conf /etc/httpd/conf/httpd.conf /etc/httpd/conf/magic /etc/httpd/logs /etc/httpd/modules /etc/httpd/run : :
詳細は割愛するが、この中で「/usr」以下のもの以外のファイルが今回httpdを実行させるために必要なファイルとなる。具体的には以下のとおりだ。
/etc/httpd/ディレクトリ以下のファイル /var/www/ディレクトリ以下のファイル /etc/logrotate.d/httpd /etc/rc.d/init.d/htcacheclean /etc/rc.d/init.d/httpd /etc/sysconfig/htcacheclean /etc/sysconfig/httpd /var/cache/mod_proxyディレクトリ /var/lib/davディレクトリ /var/log/httpdディレクトリ /var/run/httpdディレクトリ
これらのうち、ファイルについてはホストからコンテナ内にコピーし、またディレクトリについてはmkdirコマンドで作成しておく。
# cp -a /etc/httpd /var/lib/lxc/sshd01/rootfs/etc/ # cp -a /var/www /var/lib/lxc/sshd01/rootfs/var/ # cp /etc/sysconfig/{httpd,htcacheclean} /var/lib/lxc/sshd01/rootfs/etc/sysconfig/ # cp /etc/rc.d/init.d/{httpd,htcacheclean} /var/lib/lxc/sshd01/rootfs/etc/init.d/ # mkdir -p /var/lib/lxc/sshd01/rootfs/var/cache/mod_proxy # mkdir -p /var/lib/lxc/sshd01/rootfs/var/lib/dav # mkdir -p /var/lib/lxc/sshd01/rootfs/var/log/httpd # mkdir -p /var/lib/lxc/sshd01/rootfs/var/run/httpd
なお、/etc/logrotate.d/httpdファイルについては、今回はlogrotateを設定しないので省略している。また、httpdパッケージには含まれていないが、サービスの起動時にロックファイルを作成する/var/locl/subsysディレクトリもコンテナ内には用意されていないので、こちらも作成しておく。
# mkdir -p /var/lib/lxc/sshd01/rootfs/var/lock/subsys
さらに、ホスト名を/etc/hostsファイル経由で解決するため必要な/etc/nsswitch.confと/etc/host.conf、そしてhttpdがmimetypeの解決に使用する/etc/mime.typesファイルもコンテナ内にコピーしておく。
# cp -a /etc/{nsswitch.conf,host.conf} /var/lib/lxc/sshd01/rootfs/etc # cp -a /etc/mime.types /var/lib/lxc/sshd01/rootfs/etc
続いてはコンテナ内の作業だ。まず、httpdを動かすためのapacheグループおよびユーザーを作成する。
# groupadd -g 48 apache # useradd -g apache -u 48 apache
なお、ホスト上でのapacheユーザーのユーザー/グループIDがそれぞれ48になっていたので、今回はそちらに合わせてユーザー/グループIDを設定している。apacheユーザー/グループIDはホスト上で以下のようにして確認できる。
# cat /etc/passwd | grep apache apache:x:48:48:Apache:/var/www:/sbin/nologin # cat /etc/group | grep apache apache:x:48:
次に、/etc/hostsファイルを作成し、ホスト名からIPアドレスを取得できるように設定する。まず、hostnameコマンドでホスト名を確認する。
# hostname sshd01
続いて、/etc/hostsファイルを作成し、ホスト名とIPアドレス(ここでは127.0.0.1)の対応付けを記述しておく。
vi /etc/hosts 127.0.0.1 localhost.localdomain localhost sshd01
以上でhttpdを動かす準備は完了だ。/etc/init.d内のhttpdスクリプトを実行すれば、httpdが起動するはずだ。
# /etc/init.d/httpd start
コンテナ作成作業をカスタマイズする
さて、以上でhttpdが動作するコンテナが作成できたが、コンテナを新たに作成する際、いちいち同じ作業を行うのは面倒だ。そこで、テンプレートをカスタマイズして、コンテナの作成時にhttpdの設定を行うように変更してみよう。
今回使用したsshdテンプレートの中身はシェルスクリプトとなっており、lxc-createコマンドの実行時にlxc-createコマンドから呼び出されて実行されるようになっている。スクリプトの中身は渡された引数をパースする部分と、コンテナ内にファイルやディレクトリを作成する部分、そしてコンテナの起動時に実行される部分に分かれている。具体的には、前半部分で「install_ssh()」や「configure_sshd()」、「copy_configuration()」といったファイルやディレクトリの作成処理を行うサブルーチンが定義されており、後半部分では以下のようにそれらを呼び出して設定処理を実行させている。
# detect rootfs config="$path/config" if [ -z "$rootfs" ]; then if grep -q '^lxc.rootfs' $config 2>/dev/null ; then rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' $config) else rootfs=$path/rootfs fi fi install_sshd $rootfs if [ $? -ne 0 ]; then echo "failed to install sshd's rootfs" exit 1 fi configure_sshd $rootfs if [ $? -ne 0 ]; then echo "failed to configure sshd template" exit 1 fi copy_configuration $path $rootfs $name if [ $? -ne 0 ]; then echo "failed to write configuration file" exit 1 fi
そこで、今回は新たに「install_httpd()」および「configure_httpd()」というサブルーチンを追加して、そこでhttpd関連の設定処理を行うようにした。まずinstall_http()サブルーチンだが、ここではホストからhttpdや関連する設定ファイルをコンテナ内にコピーする作業を行っている。コピーを実行する際は毎回その戻り値をチェックし、エラーならば処理を中断するようにしている。
install_httpd() { rootfs=$1 cp -a /etc/httpd ${rootfs}/etc/ if [ $? -ne 0 ]; then return 1 fi cp -a /var/www ${rootfs}/var/ if [ $? -ne 0 ]; then return 1 fi cp /etc/sysconfig/{httpd,htcacheclean} ${rootfs}/etc/sysconfig/ if [ $? -ne 0 ]; then return 1 fi cp /etc/rc.d/init.d/{httpd,htcacheclean} ${rootfs}/etc/init.d/ if [ $? -ne 0 ]; then return 1 fi cp -a /etc/{nsswitch.conf,host.conf} ${rootfs}/etc if [ $? -ne 0 ]; then return 1 fi cp -a /etc/mime.types ${rootfs}/etc if [ $? -ne 0 ]; then return 1 fi tree="\ ${rootfs}/var/cache/mod_proxy \ ${rootfs}/var/lib/dav \ ${rootfs}/var/log/httpd \ ${rootfs}/var/run/httpd \ ${rootfs}/var/lock/subsys" mkdir -p $tree if [ $? -ne 0 ]; then return 1 fi return 0 }
また、設定を行うconfigure_httpd()サブルーチンでは、apacheユーザー/グループの作成と、/etc/hostsファイルの作成を行っている。
configure_httpd() { rootfs=$1 name=$2 echo apache:x:48:48:Apache:/var/www:/sbin/nologin >> ${rootfs}/etc/passwd echo apache:x:48: >> ${rootfs}/etc/group echo "127.0.0.1 localhost.localdomain localhost $name" >> ${rootfs}/etc/hosts return 0 }
これらは、configure_sshd()サブルーチンの実行後に実行するようにしておく。
configure_sshd $rootfs if [ $? -ne 0 ]; then echo "failed to configure sshd template" exit 1 fi install_httpd $rootfs if [ $? -ne 0 ]; then echo "failed to install httpd" exit 1 fi configure_httpd $rootfs $name if [ $? -ne 0 ]; then echo "failed to configure httpd" exit 1 fi
また、lxc_sshdテンプレートではこのスクリプト自体をコンテナ内でのinitスクリプトとして実行している。その際に実行されるのが以下の部分だ。ここで起動時にhttpdを開始させるよう、「/etc/init.d/httpd start」という1行を追加する(太字の部分)。これで、コンテナ起動時に自動的にhttpdが立ち上がるようになる。
if [ $0 = "/sbin/init" ]; then PATH="$PATH:/bin:/sbin:/usr/sbin" check_for_cmd /usr/sbin/init.lxc check_for_cmd sshd sshd_path=$cmd_path # run dhcp? if [ -f /run-dhcp ]; then check_for_cmd dhclient check_for_cmd ifconfig touch /etc/fstab rm -f /dhclient.conf cat > /dhclient.conf << EOF send host-name "<hostname>"; EOF ifconfig eth0 up dhclient eth0 -cf /dhclient.conf echo "Container IP address:" ifconfig eth0 |grep inet fi /etc/init.d/httpd start exec /usr/sbin/init.lxc -- $sshd_path exit 1 fi
なお、スクリプトの全文はSourceForge.JPの個人リポジトリ上で公開しているので、詳細はこちらを参照してほしい。
スクリプトの編集が完了したら、以下のようにしてこのコンテナを作成・起動できる。作成したlxc-httpdテンプレートの基本部分はsshdテンプレートと同一なので、-Sオプションでログインに使用する公開鍵を指定する点も同じだ。
# lxc-create -n httpd01 -t httpd -- -S ~hylom/.ssh/id_rsa.pub # lxc-start -n httpd01 -d -L httpd01_log
コンテナの設定管理を容易にするには
以上、LXCにおけるコンテナのカスタマイズについて簡単にまとめたが、正直なところ設定ファイルの作成などはやや面倒である。さらにシェルスクリプトでコンテナ内へのファイルのインストール作業などを実装しなければならないため、複雑な作業を定義するのは大変だ。こういった問題を解決できるのが、コンテナ管理ツールとして最近話題のDockerだ。Dockerではより記述しやすいフォーマットの設定ファイルを採用し、またさまざまな周辺ツールも用意されている。これによって、より手軽にコンテナの構築や利用が可能となる。次回はこのDockerについて紹介しよう。