LXDでコンテナごとに異なるユーザ権限でコンテナを起動する
$ lxc version Client version: 3.0.1 Server version: 3.0.1
な環境の Ubuntu 18.04 上で試してます。
以下で何の前提もなく書いてるサブ ID のお話は、私の連載
に書いていますのでそちらをどうぞ。
LXD でコンテナを起動すると、デフォルトでは非特権コンテナが起動します。
$ lxc launch ubuntu:18.04 c1 Creating c1 Starting c1 $ lxc list +------+---------+--------------------+-----------------------------------------------+------------+-----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | +------+---------+--------------------+-----------------------------------------------+------------+-----------+ | c1 | RUNNING | 10.56.2.183 (eth0) | fd42:dce3:4ae3:1cb3:216:3eff:fe25:e3c3 (eth0) | PERSISTENT | 0 | +------+---------+--------------------+-----------------------------------------------+------------+-----------+
誰の権限で起動しているか確認してみます。
$ ps auxf : (snip) root 13607 0.0 0.1 530556 7344 ? Ss 13:31 0:00 [lxc monitor] /var/lib/lxd/containers c1 100000 13626 0.1 0.2 159436 8816 ? Ss 13:31 0:00 \_ /sbin/init : (snip)
このように uid: 100000 のユーザで起動しています。
ここでもうひとつコンテナを起動します。
$ lxc launch ubuntu:18.04 c2 Creating c2 Starting c2 $ ps aux : (snip) root 14507 0.0 0.1 529148 7264 ? Ss 13:34 0:00 [lxc monitor] /var/lib/lxd/containers c2 100000 14532 1.4 0.2 77420 8660 ? Ss 13:34 0:00 /sbin/init : (snip)
こちらも uid: 100000 のユーザで起動します。
このようにデフォルトでは同一ホスト上で起動するコンテナはすべて同じユーザ権限で起動します。マルチテナントで、コンテナごとにユーザが異なる場合、セキュリティを考えると異なるユーザ権限でコンテナが実行されている方が望ましいでしょう。
LXD ではこれを制御する設定 security.idmap.isolated
があります。デフォルトではこれは false
ですので、コンテナもしくはプロファイルで true
としておくと、異なる uid/gid の範囲を使ってコンテナを起動します。
この辺りは、公式の userns-idmap あたりに詳しいです。
/etc/subuid, /etc/subgid の設定
デフォルトではコンテナには 65536 個の ID を割り当てるので、Ubuntu でユーザを登録すると、subuid/subgid として 65536 個の ID を使えるように設定されています(LXDはコンテナをrootユーザから起動するのでrootに対して)。
$ cat /etc/sub{u,g}id | grep root root:100000:65536 root:100000:65536
十分なサブ ID が確保されていない場合、
Error: Failed container creation: Not enough uid/gid available for the container.
みたいなエラーでコンテナを作成できません。
コンテナごとに異なる ID の範囲を割り当てる場合、コンテナごとに 65536 必要ですから、sub{u,g}id でも必要な個数設定しておく必要があります。
とりあえずサブ ID はユーザごとに異なっている必要もありませんので、ケチケチ設定する必要もないので、適当に 200000 個くらい設定しましょう。(LXD がどういう計算しているかわからないけど、コンテナ 2 つ起動するから 131072 とか設定すると上記エラーが出ます)
$ cat /etc/sub{u,g}id | grep root root:100000:200000 root:100000:200000
このあと、LXD を再起動しておきます。(サブ ID のマップを起動時に読み込むから)
$ sudo systemctl restart lxd
もう一つ気をつけること。ドキュメントにも書かれていないと思います。この辺りで発言があります。
/etc/sub{u,g}id に設定する ID の個数ですが、設定した最初の 65536 個(security.idmap.size
の設定)は security.idmap.isolated
を設定していないコンテナ専用です。
つまり先の例のように設定した場合、100000 〜 165535 までの ID は security.idmap.isolated
を設定していないコンテナが使いますので、security.idmap.isolated
を設定したコンテナを起動したい場合は、さらに 65536 個の ID が必要です。
ですので、ID の個数として /etc/sub{u,g}id に 65536 を設定してある環境で、ひとつもコンテナが起動していないところに、security.idmap.isolated
を設定したコンテナを起動しようとしても失敗します。
少なくとも 131073 以上の設定が必要です。まあ、ケチることはないので先の例のように多めに確保しておきましょう。
profile の設定
default
プロファイルの設定を変えてもいいですが、同じ ID 範囲で起動するコンテナを起動したい場合のためにそれは置いておいて、新しいプロファイルを作ります。
$ lxc profile copy default secure $ lxc profile set secure security.idmap.isolated true $ lxc profile show secure config: security.idmap.isolated: "true" : (snip)
コンテナの起動
$ lxc launch ubuntu:18.04 c1 --profile=secure Creating c1 Starting c1 $ lxc launch ubuntu:18.04 c2 --profile=secure Creating c2 Starting c2 $ ps aux : (snip) root 17072 0.0 0.1 604544 7324 ? Ss 14:03 0:00 [lxc monitor] /var/lib/lxd/containers c1 165536 17093 0.2 0.2 159472 8892 ? Ss 14:03 0:00 \_ /sbin/init : (snip) root 17935 0.0 0.1 529148 7196 ? Ss 14:03 0:00 [lxc monitor] /var/lib/lxd/containers c2 231072 17953 0.2 0.2 159420 8840 ? Ss 14:03 0:00 \_ /sbin/init : (snip)
はい、違うユーザ権限で起動していますね。
cgroup v2 の nsdelegate オプション(1)〜 namespace 外へのプロセス移動の禁止
cgroup v2がカーネルに導入された時点では、cgroup v2にはマウントオプションはありませんでした。
しかし、4.13 で nsdelegate
というオプションが導入されました。これは現時点でも cgroup v2 唯一のマウントオプションです。
このオプションは初期の namespace でマウントするときにのみ指定できます。それ以外の namespace では無視されます。
cgroup namespace については、私の連載 の 第34回 で説明しました。ここでは、cgroup namespace で cgroup ツリーを独立させた後でも、cgroup namespace 内の /
(root)を超えてプロセスを移動できました。
これはある意味コンテナ内のプロセスを、別のコンテナに移動させるという意味になります。
普通にコンテナを起動すると、mount namespace を分離させますので、他のコンテナのファイルシステムは見えないはずです。したがってこのようなコンテナをまたいだプロセスの移動はできないはずです。しかし、何らかの理由で別の cgroup 階層が見えるような場合は移動ができることになります。このような操作は通常は行わないケースがほとんどで、禁止したいケースがほとんどであると思います。
nsdelegate を使うとこれを禁止できます。
試してみましょう。以下は Plamo 7.0(4.14.44 kernel)で試しています(sysvinit バンザイ!)。
namespace をまたいだプロセスの移動
nsdelegate がないとき
オプションを指定せずに cgroup v2 をマウントします。これは、私の連載の第34回で説明しています。詳しくはそちらをどうぞ。
# mount -t cgroup2 cgroup2 /sys/fs/cgroup/
test01
と test02
cgroup を作成します。
# mkdir /sys/fs/cgroup/test0{1,2}
現在のシェルの PID を test01
に登録します。
# echo $$ | tee /sys/fs/cgroup/test01/cgroup.procs 4213
unshare で cgroup namespace を作成してシェルを起動します。
# unshare --cgroup -- /bin/bash
起動したシェルは親 cgroup と同じ cgroup に属することになるので、namespace 作成時点で親と自身が cgroup の /
(root)にいることになります。namespace 作成時点にいる cgroup が namespace 内では root となる、これが cgroup namespace でした。
# echo $$ 4284 # cat /proc/4213/cgroup 0::/ # cat /proc/4284/cgroup 0::/
親の namespace から見ると /test01
cgroup にいることになっています。ちゃんと namespace として働いているのがわかりますね。
parent namespace # cat /proc/4213/cgroup 0::/test01 parent namespace # cat /proc/4284/cgroup 0::/test01
ここでおもむろに現在のシェルの PID を test01
と同じレベルの別階層にある test02
に登録します。
# echo $$ > /sys/fs/cgroup/test02/cgroup.procs
すると /
(root)の一つ上の test02
を表す /../test02
という cgroup に属することになっています。/test01
が root になっているので、同じレベルの別階層だとこうなるのはわかりますね。
# cat /proc/$$/cgroup 0::/../test02
nsdelegate があるとき
nsdelegate 機能を使うには cgroup v2 をマウントする際に nsdelegate オプションを指定します。既に cgroup v2 がマウント済みの場合は -o remount,nsdelegate
と指定して再マウントすれば使えるようになります。
# mount -t cgroup2 -o nsdelegate cgroup2 /sys/fs/cgroup/
先ほどと同様に test01
、test02
cgroup を作りましょう。
# mkdir /sys/fs/cgroup/test0{1,2}
そして、test01
にシェルのプロセスを登録し、所属する cgroup を確認します。
# echo $$ > /sys/fs/cgroup/test01/cgroup.procs # unshare --cgroup /bin/bash # cat /proc/$$/cgroup 0::/
test01
が root cgroup になりました。ここまではさきほどと同じですね。
ここで先程と同じようにシェルの PID を test02
に移動させてみましょう。
# echo $$ > /sys/fs/cgroup/test02/cgroup.procs bash: echo: write error: No such file or directory
エラーになりました。nsdelegate
を指定して cgroup v2 をマウントすると、このように namespace の境界(root)をまたいで、別階層の cgroup にプロセスを移動できません(ENOENT
が返ります)。
nsdelegate には他にも重要な機能がありますので、次回にでも。
LXD コンテナに物理NICを直接与える
あまり役に立たないメモです。
コンテナホスト上でコンテナを起動する場合、ホスト上にブリッジを作成し、そこにアタッチする veth インターフェースを接続する場合が多いかと思います。
しかし、ベアメタル上に物理 NIC が多数あったり、SR-IOV で物理 NIC 上で仮想的な NIC が多数作成できる場合は、LXD コンテナに直接 NIC をアタッチできます(ただし、最新の LXD は SR-IOV 対応していたのでこのブログエントリは関係ないかも? < 良く知らない)。
コンテナホスト上にブリッジを作成し、veth でアタッチしてコンテナを起動する場合、コンテナに割り当てる profile は default
として、veth でホスト上のブリッジにアタッチするデバイスが定義されています(環境依存です)。
ホスト上の NIC を直接割り当てる場合はこのような profile は無駄ですので、一旦デバイスとして nic が存在しないコンテナ用 profile を作成します。これは default プロファイルをコピーすれば良いでしょう。
LXD 用に、ネットワークは定義しないプロファイル nonet
を定義しましょう。default
(lxd init
時に設定されそうなので環境依存だと思う)をコピーして lxc profile edit nonet
とかやるとエディタで編集できます。
$ lxc profile copy default nonet $ lxc profile edit nonet $ lxc profile show nonet config: {} description: Default LXD profile devices: root: path: / pool: default type: disk name: nonet used_by: []
コンテナを作ります。
$ lxc init ubuntu:18.04 --profile=nonet Creating the container Container name is: still-hamster $ lxc list +---------------+---------+------+------+------------+-----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | +---------------+---------+------+------+------------+-----------+ | still-hamster | STOPPED | | | PERSISTENT | 0 | +---------------+---------+------+------+------------+-----------+
ネットワーク設定のないコンテナが作られます。
$ lxc config show still-hamster | grep devices devices: {}
ここで、ホスト上にあるけど使われていない物理 NIC を確認してみましょう。次のようなアドレスも割当らず、UP していないインターフェース eth1
がありました。この eth1
が接続されているネットワークには DHCP サーバがあり、そこからアドレスがもらえるとします。
$ ip a : (snip) 3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 0e:9a:02:d4:20:38 brd ff:ff:ff:ff:ff:ff
これを先ほど作成したコンテナ still-hamster
に割り当てます。
$ lxc config device add still-hamster eth0 nic nictype=physical name=eth0 parent=eth1 Device eth0 added to still-hamster
これでコンテナ内に eth0
という名前で、ホスト上の eth1
が割り当たります。
$ lxc config show still-hamster : (snip) devices: eth0: name: eth0 nictype: physical parent: eth1 type: nic : (snip)
コンテナを起動します。
$ lxc start still-hamster $ lxc list +---------------+---------+--------------------+------+------------+-----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | +---------------+---------+--------------------+------+------------+-----------+ | still-hamster | RUNNING | 10.7.11.252 (eth0) | | PERSISTENT | 0 | +---------------+---------+--------------------+------+------------+-----------+
起動してアドレスも割当たっています。ちなみに Ubuntu:18.04
イメージでは、DHCP でアドレスをもらえる設定がなされています。
$ lxc exec still-hamster -- cat /etc/netplan/50-cloud-init.yaml : (snip) network: version: 2 ethernets: eth0: dhcp4: true
コンテナ内でルーティングテーブルを見てみると次のようにちゃんと割当たっていることがわかります。
$ lxc exec still-hamster -- ip route show table main default via 10.7.0.1 dev eth0 proto dhcp src 10.7.11.252 metric 100 10.7.0.0/16 dev eth0 proto kernel scope link src 10.7.11.252 10.7.0.1 dev eth0 proto dhcp scope link src 10.7.11.252 metric 100
ちなみに、コンテナが起動した状態で eth1
を確認してみると、eth1
はコンテナの namespace に移動していますので、ホスト上からは見えません。
$ ip a | grep eth1
$
ここでは LXD での例を示しましたが、LXC でも設定ファイルにネットワークで物理インターフェースを指定することで同じことができます。
LXC 3.0 新機能の予習
ここ最近、新バージョンリリース時と、ドキュメント(man pages)に更新があったときに翻訳する以外、新しい機能について全く調査していませんでした。
なんとなく見てると LXC 3.0 が近いようですので、どう変わるのかをまとめてみます。
cgroup ドライバの整理
これまで LXC には cgroup 関連のドライバが 3 つ含まれていました。
- cgfs
- cgmanager
- cgfsng
cgfs ドライバはもっとも古くからあるドライバです。今や cgroup は /sys/fs/cgroup
にマウントされますが、昔は特にマウントする場所は決まっておらず、/dev/cgroup
にマウントしたり、/cgroup
にマウントしたりしていました。どこにマウントされるかわからないファイルシステムを検出するロジックなど、今や不要になったロジックが多く含まれています。
cgfs ドライバの機能は cgfsng でカバーされていますので、cgfs ドライバは削除されるようです。
cgmanager ドライバは Ubuntu 14.04 のあたりに導入された cgmanager を使って cgroup を管理するためのドライバでした。カーネルに cgroup namespace が導入された今となっては、cgmanager は不要で、すでに廃止予定のプロジェクトになっていますので、cgmanager ドライバも廃止されます。
テンプレートの整理
LXC 2.x までは、様々なディストリビューション用のコンテナイメージを作成するために、シェルスクリプトで書かれたテンプレートが各ディストリビューションごとに用意されていました。
LXC 3.0 では、ディストリビューション依存のテンプレートが削除されるようです。ただ、バサッと切り捨てるのではなく、lxc-templatesというプロジェクトに分離されました。
LXC プロジェクト配下には、新たに distrobuilder という、イメージを作成するための Go 言語によるツールの開発が始まっています。
distrobuilder
distrobuilder は、
- 最近は、ディストリビューションがクラウド用にイメージを準備していることが多いので、それを取得して LXC/LXD 用のイメージを作成する
- 各ディストリビューションが採用しているパッケージ管理コマンドを使って、従来のように LXC/LXD 用のイメージを作成する
という機能を持っているようです。
現時点では、各ディストリビューションが準備している
- alpine イメージ(alpine-minirootfs-*.tar.gz)
- arch イメージ(archlinux-bootstrap-*-x86_64.tar.gz)
- CentOS イメージ(iso イメージ?)
- Ubuntu イメージ(ubuntu-base--base-.tar.gz)
を使用してイメージを作成する機能と、
- debootstrap
を使用してイメージを作成する機能があるようです。
各種言語バインディングの分離
LXC のソースアーカイブに含まれていた、
は独立したリポジトリに分離されるようです。
pam_cgfs の LXC への移動
ユーザログイン時に、ユーザ用の cgroup を作成するための pam モジュールとして pam_cgfs
が LXCFS で開発されていましたが、LXC 3.0 からは LXC ツリー配下で開発されるようです。
cgroup v2 サポート
cgroup v2 がサポートされたようです。
他にもあるでしょうけど、とりあえずこのあたりで。
参考
- LXC Lands Unified cgroup Hierarchy Support
- On The Way To LXC 3.0: Removal of cgmanager And cgfs cgroup Drivers
- On The Way To LXC 3.0: Splitting Out Templates And Language Bindings
- On The Way To LXC 3.0: Moving The Cgroup Pam Module Into The LXC Tree (Including A Detour About Fully Unprivileged Containers)
GnuPGで鍵取得しようとするとdirmngrに繋がらないと怒られる
単なるメモ。Plamo-7.0 開発中環境でのお話。
gnupg 2.1.19 までは大丈夫なんだけど、gnupg 2.1.23、2.2.0、2.2.1 にすると、dirmngr がうまく動かない… (2.1.20 〜 22 は作ってないので知らない)。
$ gpg --recv-keys (鍵) gpg: connecting dirmngr at '/home/karma/.gnupg/S.dirmngr' failed: IPC connect呼び出しに失敗しました gpg: 鍵サーバからの受信に失敗しました: dirmngrがありません
となる。この時、dirmngr は起動しているけど、
tcp 0 1 10.200.200.232:45602 127.0.0.1:9050 SYN_SENT 27056/dirmngr
のように、どうやら Tor に接続に行っている模様。これコンパイル時に無効化できんの? もしくはデフォルト使わないってできんの?
もちろん dirmngr.conf で使わないように設定すれば使わない。でも Plamo では、システムワイドで dirmngr 起動するわけではなく、ユーザごとに起動することになるので、ユーザの dirmngr.conf にいちいち書かなければならない。
$ cat ~/.gnupg/dirmngr.conf no-use-tor
いや、GnuPG 詳しくないから知らんけど
マニュアルには The default is to use Tor if it is available on startup or after reloading dirmngr.
と書いてある。でも Tor なんて入ってないと思うんだけど…
GnuPGのdirmngrのコード見ると、dirmngr/server.c
で
/* This function returns true if a Tor server is running. The status * is cached for the current connection. */ static int is_tor_running (ctrl_t ctrl) { /* Check whether we can connect to the proxy. */ if (!ctrl || !ctrl->server_local) return 0; /* Ooops. */ if (!ctrl->server_local->tor_state) { assuan_fd_t sock; sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); if (sock == ASSUAN_INVALID_FD) ctrl->server_local->tor_state = -1; /* Not running. */ else { assuan_sock_close (sock); ctrl->server_local->tor_state = 1; /* Running. */ } } return (ctrl->server_local->tor_state > 0); }
こんな関数がある。ここで tor_state
が 1 になってる?
libassuan を見ると、src/assuan-socket.c
内に
_assuan_sock_connect_byname (assuan_context_t ctx, const char *host, unsigned short port, int reserved, const char *credentials, unsigned int flags) { assuan_fd_t fd; unsigned short socksport; if ((flags & ASSUAN_SOCK_TOR)) socksport = TOR_PORT; else if ((flags & ASSUAN_SOCK_SOCKS)) socksport = SOCKS_PORT; else { gpg_err_set_errno (ENOTSUP); return ASSUAN_INVALID_FD; } if (host && !*host) { /* Error out early on an empty host name. See below. */ gpg_err_set_errno (EINVAL); return ASSUAN_INVALID_FD; } fd = _assuan_sock_new (ctx, AF_INET, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) return fd; /* For HOST being NULL we pass an empty string which indicates to socks5_connect to stop midway during the proxy negotiation. Note that we can't pass NULL directly as this indicates IP address mode to the called function. */ if (socks5_connect (ctx, fd, socksport, credentials, host? host:"", port, NULL, 0)) { int save_errno = errno; assuan_sock_close (fd); gpg_err_set_errno (save_errno); return ASSUAN_INVALID_FD; } return fd; }
なんて関数がある。よくわからんw
いや、違うな。dirmngr に Tor のオプションを指定すると、オプションが --use-tor
か --no-use-tor
かに関わらず dirmngr はすぐに起動するけど、何も指定しないと起動後 127.0.0.1:9050 へのアクセスをして、だいぶタイムアウトを待った後に、使わない設定で起動するな。dirmngr.conf を準備して、ちゃんと設定しろ、ということかな。なんて不便なソフトウェアだ。
$ strace dirmngr --server --homedir /home/karma/.gnupg -vvv --debug-all :(snip) write(2, "dirmngr[27628]: enabled debug fl"..., 108dirmngr[27628]: enabled debug flags: x509 crypto memory cache memstat hashing ipc dns network lookup extprog) = 108 write(2, "\n", 1 ) = 1 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 connect(3, {sa_family=AF_INET, sin_port=htons(9050), sin_addr=inet_addr("127.0.0.1")}, 16 :(↑でだんまり) :(snip)
うん、接続を延々待ってるわ。これで失敗したあとにちゃんと dirmngr は起動して、クライアントからのリクエストに応える。不便すぎて涙出るな。