ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

ID Mapping/User Namespace再入門 その(3)

User Namespace の各コンテナでの対応状況その2。前回から引き続き:

udzura.hatenablog.jp

LXD を利用したコンテナ別のUser Namespaceの割り当て

LXDをUbuntu Bionicでセットアップする。一緒に、以下の記事の手順を参考にsubuid/subgidを多く割り当てる。

stgraber.org

$ 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の人のディレクトリをそのままシェアしたりも可能らしい。詳細は日本語でもドキュメントがある。

lxd-ja.readthedocs.io

また、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などを用いてその作業を簡易化できないか見てみる。

gihyo.jp

lwn.net

追記

Haconiwaだけpingできないのはこのあたりの問題と思われる。今試したら、hostnameの変更すらできない。

次回、やっぱりこの辺の話を先にして、Haconiwaに添付するmruby hacorb で検証コードを書くなどしたい。