User Namespace の各コンテナでの対応状況その2。前回から引き続き:
LXD を利用したコンテナ別のUser Namespaceの割り当て
LXDをUbuntu Bionicでセットアップする。一緒に、以下の記事の手順を参考にsubuid/subgidを多く割り当てる。
$ sudo apt install lxd $ sudo vi /etc/subuid ... lxd:100000:655360 # <- 10倍の範囲にしてみる root:100000:655360 ... $ sudo systemctl restart lxd $ sudo cat /var/log/lxd/lxd.log | grep 'uid/gid map' -A 2 t=2019-02-28T08:05:15+0000 lvl=info msg="Kernel uid/gid map:" t=2019-02-28T08:05:15+0000 lvl=info msg=" - u 0 0 4294967295" t=2019-02-28T08:05:15+0000 lvl=info msg=" - g 0 0 4294967295" t=2019-02-28T08:05:15+0000 lvl=info msg="Configured LXD uid/gid map:" t=2019-02-28T08:05:15+0000 lvl=info msg=" - u 0 100000 655360" t=2019-02-28T08:05:15+0000 lvl=info msg=" - g 0 100000 655360"
ところで、LXDからコンテナを立ち上げると、デフォルトでUID Shiftされた状態のコンテナができる。ちなみにLXDはDocker等と同様クライアント/サーバ方式なので、 /var/lib/lxd/unix.socket
への読み書き権限があるユーザであれば操作ができる。
$ sudo lxc launch images:alpine/3.8 ippan-shimin-1 Creating ippan-shimin-1 Starting ippan-shimin-1 $ sudo lxc exec ippan-shimin-1 -- /bin/sh ~ # ls -l / total 0 drwxr-xr-x 1 root root 890 Feb 27 13:00 bin drwxr-xr-x 7 root root 420 Feb 28 08:13 dev drwxr-xr-x 1 root root 670 Feb 28 08:13 etc drwxr-xr-x 1 root root 0 Jun 26 2018 home drwxr-xr-x 1 root root 444 Feb 27 13:00 lib drwxr-xr-x 1 root root 28 Jun 26 2018 media drwxr-xr-x 1 root root 0 Jun 26 2018 mnt # こちらもnobody所有 dr-xr-xr-x 193 nobody nobody 0 Feb 28 08:13 proc drwx------ 1 root root 24 Feb 28 08:14 root drwxr-xr-x 4 root root 200 Feb 28 08:13 run drwxr-xr-x 1 root root 1662 Feb 27 13:00 sbin drwxr-xr-x 1 root root 0 Jun 26 2018 srv dr-xr-xr-x 13 nobody nobody 0 Feb 27 07:44 sys drwxrwxrwt 1 root root 36 Feb 28 08:13 tmp drwxr-xr-x 1 root root 40 Jun 26 2018 usr drwxr-xr-x 1 root root 78 Feb 28 08:13 var
そして、複数のコンテナを立ち上げた場合、デフォルトでは同じオフセットでshiftされる。
$ ps auxf ... root 15283 0.0 0.7 463296 7320 ? Ss 08:13 0:00 [lxc monitor] /var/lib/lxd/containers ippan-shimin-1 100000 15301 0.0 0.0 1516 76 ? Ss 08:13 0:00 \_ /sbin/init 100000 15551 0.0 0.0 1516 44 ? Ss 08:13 0:00 \_ /sbin/syslogd -Z 100000 15578 0.0 0.0 1516 44 ? Ss 08:13 0:00 \_ /usr/sbin/crond -c /etc/crontabs 100000 15622 0.0 0.0 1516 48 ? Ss 08:13 0:00 \_ udhcpc -b -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:ippan-shimin-1 100000 15633 0.0 0.0 1516 4 pts/2 Ss+ 08:13 0:00 \_ /sbin/getty 38400 console root 15739 0.0 0.7 609608 7456 ? Ss 08:15 0:00 [lxc monitor] /var/lib/lxd/containers ippan-shimin-2 100000 15760 0.0 0.0 1516 76 ? Ss 08:15 0:00 \_ /sbin/init 100000 16006 0.0 0.0 1516 44 ? Ss 08:15 0:00 \_ /sbin/syslogd -Z 100000 16033 0.0 0.0 1516 48 ? Ss 08:15 0:00 \_ /usr/sbin/crond -c /etc/crontabs 100000 16078 0.0 0.0 1516 44 ? Ss 08:15 0:00 \_ udhcpc -b -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:ippan-shimin-2 100000 16089 0.0 0.0 1516 4 pts/3 Ss+ 08:15 0:00 \_ /sbin/getty 38400 console
オフセットを変更したい場合、 security.idmap.isolated
というコンテナごとの設定を有効にするだけでOK。LXD使える範囲から範囲で割り振ってくれる。
$ sudo lxc config set ippan-shimin-2 security.idmap.isolated true $ sudo lxc restart ippan-shimin-2 $ ps auxf ... root 15283 0.0 0.7 463296 7320 ? Ss 08:13 0:00 [lxc monitor] /var/lib/lxd/containers ippan-shimin-1 100000 15301 0.0 0.0 1516 76 ? Ss 08:13 0:00 \_ /sbin/init 100000 15551 0.0 0.0 1516 44 ? Ss 08:13 0:00 \_ /sbin/syslogd -Z 100000 15578 0.0 0.0 1516 44 ? Ss 08:13 0:00 \_ /usr/sbin/crond -c /etc/crontabs 100000 15622 0.0 0.0 1516 48 ? Ss 08:13 0:00 \_ udhcpc -b -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:ippan-shimin-1 100000 15633 0.0 0.0 1516 4 pts/2 Ss+ 08:13 0:00 \_ /sbin/getty 38400 console root 16249 0.0 0.7 609608 7260 ? Ss 08:22 0:00 [lxc monitor] /var/lib/lxd/containers ippan-shimin-2 165536 16266 0.0 0.0 1516 72 ? Ss 08:22 0:00 \_ /sbin/init 165536 16512 0.0 0.0 1516 44 ? Ss 08:22 0:00 \_ /sbin/syslogd -Z 165536 16539 0.0 0.0 1516 48 ? Ss 08:22 0:00 \_ /usr/sbin/crond -c /etc/crontabs 165536 16583 0.0 0.0 1516 48 ? Ss 08:22 0:00 \_ udhcpc -b -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:ippan-shimin-2 165536 16604 0.0 0.0 1516 4 pts/3 Ss+ 08:22 0:00 \_ /sbin/getty 38400 console
オフセットの間隔を変える場合 security.idmap.size
という設定を指定する。とはいえ、LXDは基本的にシステムコンテナを作るし、ちゃんと65536個IDを割り振ってあげるべきだろう。実際、それより少ないと例えばaptが権限チェック apt-acquire-privs-test
に失敗するなど、思った通りの操作ができない可能性がある。
そのほか、カスタムマッピングも可能で、ホストの特定のIDの人のディレクトリをそのままシェアしたりも可能らしい。詳細は日本語でもドキュメントがある。
また、cgroupも有効。こちらもLXDがcgroupを作る。
vagrant@ubuntu-bionic:~$ sudo cat /proc/16266/cgroup 12:devices:/lxc/ippan-shimin-2 11:memory:/lxc/ippan-shimin-2 10:cpu,cpuacct:/lxc/ippan-shimin-2 9:freezer:/lxc/ippan-shimin-2 8:pids:/lxc/ippan-shimin-2 7:rdma:/lxc/ippan-shimin-2 6:net_cls,net_prio:/lxc/ippan-shimin-2 5:perf_event:/lxc/ippan-shimin-2 4:hugetlb:/lxc/ippan-shimin-2 3:blkio:/lxc/ippan-shimin-2 2:cpuset:/lxc/ippan-shimin-2 1:name=systemd:/lxc/ippan-shimin-2 0::/lxc/ippan-shimin-2
ということで、LXC/LXDのUser Namespace/ID Mappingの対応は良くできているという感想になった。
Haconiwa の対応状況
Haconiwaには config.namespace.set_uid_mapping
/ config.namespace.set_gid_mapping
という設定がある。
以下のようなHacofileを用意する。
# Pass environment variable OFFSET=100000/200000/300000/... # Then assigned user id range will be changed Haconiwa.define do |config| offset = (ENV['OFFSET'] || 0).to_i config.name = "uid-mapping-#{offset}" config.init_command = "/bin/bash" root = offset == 0 ? \ Pathname.new("/opt/haconiwa/debian-slim") : \ Pathname.new("/opt/haconiwa/debian-slim-#{offset}") config.chroot_to root config.mount_network_etc(root, host_root: "/etc") config.mount_independent "procfs" config.mount_independent "sysfs" config.mount_independent "devtmpfs" config.mount_independent "devpts" config.mount_independent "shm" config.namespace.unshare "mount" config.namespace.unshare "ipc" config.namespace.unshare "uts" config.namespace.unshare "pid" config.cgroup['cpu.cfs_quota_us'] = 50000 config.cgroup['memory.limit_in_bytes'] = 512 * 1024 * 1024 if offset > 0 config.namespace.set_uid_mapping min: 0, max: 65536, offset: offset config.namespace.set_gid_mapping min: 0, max: 65536, offset: offset end config.filesystem.use_legacy_chroot = true config.filesystem.masked_paths = [] end
/opt/haconiwa/debian-slim.*
というrootfsは、例えば docker export
などでdebian-slimなコンテナのrootfsを持ってきて、必要に応じてchownすれば作成できる。
$ sudo chown 100000:100000 -R /opt/haconiwa/debian-slim-100000
これを立ち上げると、他の非特権コンテナのように立ち上げ可能。オフセットを外の環境変数で注入してるけど、LXDのように動的に範囲を決めるには、例えば外部のAPI(CMDB)なりからコンテナごとに使えるID Rangeを取得し、DSLで設定するという方法が考えられる。
$ sudo env OFFSET=100000 haconiwa run sample/uid_mapping.haco Create lock: #<Lockfile path=/var/lock/.uid-mapping-100000.hacolock> Container fork success and going to wait: pid=16679 root@uid-mapping-100000:/# ls -l total 64 drwxr-xr-x 2 root root 4096 Feb 4 00:00 bin drwxr-xr-x 2 root root 4096 Jan 22 13:47 boot drwxr-xr-x 16 nobody nogroup 3680 Feb 28 08:10 dev drwxr-xr-x 28 root root 4096 Feb 27 06:26 etc drwxr-xr-x 2 root root 4096 Jan 22 13:47 home drwxr-xr-x 8 root root 4096 Feb 4 00:00 lib drwxr-xr-x 2 root root 4096 Feb 4 00:00 lib64 drwxr-xr-x 2 root root 4096 Feb 4 00:00 media drwxr-xr-x 2 root root 4096 Feb 4 00:00 mnt drwxr-xr-x 2 root root 4096 Feb 4 00:00 opt dr-xr-xr-x 194 nobody nogroup 0 Feb 28 08:32 proc drwx------ 2 root root 4096 Feb 27 07:09 root drwxr-xr-x 3 root root 4096 Feb 4 00:00 run drwxr-xr-x 2 root root 4096 Feb 4 00:00 sbin drwxr-xr-x 2 root root 4096 Feb 4 00:00 srv dr-xr-xr-x 13 nobody nogroup 0 Feb 27 07:44 sys drwxrwxrwt 2 root root 4096 Feb 4 00:00 tmp drwxr-xr-x 10 root root 4096 Feb 4 00:00 usr drwxr-xr-x 11 root root 4096 Feb 4 00:00 var
現在のHaconiwaの制限、他雑感
- pivot_root が失敗するので、chrootを使う設定にする (
config.filesystem.use_legacy_chroot
) - masked_pathsで
/proc
の下のファイルをマスクしようとすると失敗するものがあるので空を指定する
このあたりは、rootfsを構築してpivot_rootするタイミングとを調整すれば対応できると思う。
- devがnobody所有じゃん... 多分マウントオプションがある。か、devtmpfsをやめてmknodで必要なものだけ作れば良さそう。複雑なことをすると今は困りそう。
# Othersに対してrwの権限があるので、nobody所有でも使えなくはない。 root@uid-mapping-100000:/# stat /dev/null File: /dev/null Size: 0 Blocks: 0 IO Block: 4096 character special file Device: 6h/6d Inode: 6 Links: 1 Device type: 1,3 Access: (0666/crw-rw-rw-) Uid: (65534/ nobody) Gid: (65534/ nogroup) Access: 2019-02-27 06:49:44.192000000 +0000 Modify: 2019-02-27 06:49:44.192000000 +0000 Change: 2019-02-27 06:49:44.192000000 +0000 Birth: -
- 自動マウント系のファイルシステムは、uid=... などの指定ができるやつはしていて、過去の自分偉かった。
root@uid-mapping-100000:/# mount tmpfs on /etc/resolv.conf type tmpfs (ro,relatime,size=100856k,mode=755) /dev/sda1 on /etc/hosts type ext4 (ro,relatime,data=ordered) proc on /proc type proc (rw,relatime) sysfs on /sys type sysfs (rw,relatime) devtmpfs on /dev type devtmpfs (rw,relatime,size=491364k,nr_inodes=122841,mode=755) devpts on /dev/pts type devpts (rw,relatime,uid=100000,gid=100000,mode=600,ptmxmode=000) tmpfs on /dev/shm type tmpfs (rw,relatime,uid=100000,gid=100000)
- cgroup は効かせることができる。
config.cgroup['cpu.cfs_quota_us'] = 50000
としているので、例えばコンテナの中でyes > /dev/null
などを発動し、ホストでtopを見れば、50%までしかCPU利用率が上がらないことはわかる。
vagrant@ubuntu-bionic:~$ sudo cat /proc/16679/cgroup 12:devices:/user.slice 11:memory:/uid-mapping-100000 10:cpu,cpuacct:/uid-mapping-100000 9:freezer:/user/root/0 8:pids:/user.slice/user-1000.slice/session-44.scope 7:rdma:/ 6:net_cls,net_prio:/ 5:perf_event:/ 4:hugetlb:/ 3:blkio:/user.slice 2:cpuset:/ 1:name=systemd:/user/root/0 0::/user.slice/user-1000.slice/session-44.scope
- ip も付けることができる。
config.network.container_ip
などは通じる。そういえばsetuidしても ping ができないんだけど、何が必要なんだろう...
root@uid-mapping-100000:/# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 56: veth0@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 7a:e3:06:13:f5:a8 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.0.10/24 scope global veth0 valid_lft forever preferred_lft forever inet6 fe80::78e3:6ff:fe13:f5a8/64 scope link tentative valid_lft forever preferred_lft forever root@uid-mapping-100000:/# ping 8.8.8.8 ping: socket: Operation not permitted root@uid-mapping-100000:/# stat `which ping` File: /bin/ping Size: 61240 Blocks: 120 IO Block: 4096 regular file Device: 801h/2049d Inode: 1840871 Links: 1 Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2019-02-28 09:08:58.530761999 +0000 Modify: 2016-11-10 06:23:32.000000000 +0000 Change: 2019-02-28 09:08:45.760380005 +0000 Birth: -
ということで、現状のHaconiwaは割と対応が必要そうで、 unshare(CLONE_NEWUSER)
のタイミングをコンテナの準備が整ってからにすると想定した感じになりそう。 haconiwa
コマンドから一般ユーザがコンテナを作れるようにするかどうかは、一旦保留。
で、今回見たように、rootfsをいちいち用意して、手動でchownをしないとID Mappingでまともなコンテナにできない。次回は、Overlayfsやshiftfsなどを用いてその作業を簡易化できないか見てみる。
追記
https://t.co/NihjjRgYbf
— Uchio KONDO 🔫 (@udzura) February 28, 2019
なぜか過去の自分、user namespaceのunshareをわざわざ一番最後にしている。これが原因っぽい。https://t.co/V0Kz79fn6m
ここを読んでも同時または先にunshareすべきに読める。流し読みだけど... https://t.co/5BstiBHFwg
Haconiwaだけpingできないのはこのあたりの問題と思われる。今試したら、hostnameの変更すらできない。
次回、やっぱりこの辺の話を先にして、Haconiwaに添付するmruby hacorb
で検証コードを書くなどしたい。