LXCを使った権限分離とテンプレートのカスタマイズ

図3 cgroupsによるリソースのグルーピング

 LXCは単に仮想環境を構築するためだけでなく、権限を分離してセキュリティを高めるという目的にも利用可能だ。前回はLXCの基本について紹介したが、今回はLXCを使用した権限分離の例や、テンプレートをカスタマイズする例を紹介する。

LXCを利用した権限の分離

 LXCにはさまざまなLinuxディストリビューション環境を構築するためのテンプレートが用意されているため、仮想環境を構築するためのツールという認識を持っている人も少なくないだろう。しかし、LXCの核となっているのはLinux上のさまざまなリソースを管理する機能であるcgroupsであり、これを利用した権限の分離にLXCを利用することも可能だ。

図1 cgroupsによるリソースのグルーピング
図1 cgroupsによるリソースのグルーピング

 たとえば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について紹介しよう。