先日、
さて今年もAdvent Calendarの季節がやってきました。この記事は、
毎年この連載でLinux Advent Calendarに参加していますので、
前回、
コンテナとファイルケーパビリティ
前回、
ここで問題になってくるのが、
実は、
これは、
ユーザ名前空間は一般ユーザで作成できます。ユーザ名前空間内のrootがファイルケーパビリティを設定できるとすると、
まずは古いカーネルではユーザ名前空間内でファイルケーパビリティが設定できないことを確認したあとに、
古いカーネルのユーザ名前空間内のファイルケーパビリティ(4.13以前)
Plamo 7.
$ uname -r 4.12.7-plamo64
LXCを使って一般ユーザ権限でコンテナを作り、
$ id -u 1000 (一般ユーザ) $ lxc-start c1 (一般ユーザ権限でコンテナを起動) $ lxc-info -p c1 PID: 8246 (コンテナのPIDを確認) $ cat /proc/8246/{u,g}id_map 0 200000 65536 0 200000 65536 (ユーザ名前空間内のrootはUID:200000のユーザにマッピングされている) $ ps aux | grep 8246 200000 8246 0.0 0.0 2472 788 pts/1 Ss+ 02:10 0:00 init [3] (UID:200000でコンテナが起動している)
このコンテナ上でファイルケーパビリティを設定してみます。
$ lxc-attach c1 (コンテナ内に入る) root@c1:~# id -u 0 (rootユーザ) root@c1:~# cp /bin/ping . root@c1:~# /sbin/getcap ./ping (ファイルケーパビリティは設定されていない) root@c1:~# /sbin/setcap cap_net_raw+p ./ping Failed to set capabilities on file `./ping' (Operation not permitted) (rootなのにファイルケーパビリティが設定できない) root@c1:~# grep Cap /proc/$$/status CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff CapAmb: 0000000000000000 (プロセスでケーパビリティが削られているわけではない)
rootで実行しているにも関わらずファイルケーパビリティが設定できません。ファイルケーパビリティを設定するためのケーパビリティが削られているわけでもありません。
以上のように、
ユーザ名前空間内のファイルケーパビリティ(4.14以降)
これを解決する機能がカーネルにマージされたのは4.
ユーザ名前空間内でファイルケーパビリティが設定できることを、
Plamo 7.
$ uname -r 5.3.11-plamo64 (5.3.11カーネルで起動している) $ id -u 1000 $ lxc-start c1 $ lxc-info -p c1 PID: 8668 $ cat /proc/8668/{u,g}id_map 0 200000 65536 0 200000 65536 $ ps aux | grep 8668 200000 8668 0.0 0.0 2468 1740 pts/1 Ss+ 17:55 0:00 init [3] (UID:200000でコンテナが起動している)
先の実行例と同じように一般ユーザ権限でコンテナを起動しました。確かにUID:200000でコンテナが起動しており、
このコンテナ内でファイルケーパビリティを設定してみましょう。
$ lxc-attach c1 root@c1:~# id -u 0 root@c1:~# cp /bin/ping . root@c1:~# /sbin/getcap ./ping (ファイルケーパビリティは設定されていない) root@c1:~# /sbin/setcap cap_net_raw+p ./ping (ファイルケーパビリティを設定してもエラーは発生しない) root@c1:~# /sbin/getcap ./ping ./ping = cap_net_raw+p (ファイルケーパビリティが設定されている)
このように非特権コンテナ内でファイルケーパビリティが設定できました。
このping
コマンドが実行できるかも確認しておきましょう。
root@c1:~# su - gihyo $ id -u 1000 (一般ユーザ) $ ./ping -c1 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.029 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.029/0.029/0.029/0.000 ms (pingが実行できた)
このように、
まずはコンテナの外でファイルケーパビリティを確認してみます。ホスト環境上で上の例で使ったping
コマンドを確認します。
$ cd ~/.local/share/lxc/c1/rootfs/home/gihyo/ (ホスト環境上でコンテナのファイルシステムがある場所に移動) $ ls -l ./ping -rwxr-xr-x 1 200000 200000 183,872 12月 1日 18:03 ./ping* (ユーザ名前空間内のrootにマッピングされていたUID所有になっている) $ /sbin/getcap ./ping ./ping = cap_net_raw+p (ファイルケーパビリティが設定されている)
このように、ping
コマンドを実行してみましょう。
$ id -u 1000 (一般ユーザ) $ ./ping -c1 127.0.0.1 ./ping: socket: Operation not permitted (名前空間の外で実行したため実行できない)
ファイルケーパビリティが設定されているにも関わらずエラーになっており、
カーネルデータ構造の変更
上の例では、
この機能がカーネルにマージされたのは"Introduce v3 namespaced file capabilities"というパッチです。
4.
#define VFS_CAP_REVISION_2 0x02000000
: (略)
struct vfs_cap_data {
__le32 magic_etc; /* Little endian */
struct {
__le32 permitted; /* Little endian */
__le32 inheritable; /* Little endian */
} data[VFS_CAP_U32];
};
上記のvfs_
構造体に加えて4.
#define VFS_CAP_REVISION_3 0x03000000
: (略)
struct vfs_ns_cap_data {
__le32 magic_etc;
struct {
__le32 permitted; /* Little endian */
__le32 inheritable; /* Little endian */
} data[VFS_CAP_U32];
__le32 rootid;
};
4.vfs_
構造体と、vfs_
構造体は、vfs_
構造体の最後にrootid
という変数が追加されている以外は同じです。
このvfs_
構造体は、
permitted
変数: Pemittedケーパビリティセットinheritable
変数: Inheritableケーパビリティセットmagic_
変数: Effectiveケーパビリティや、etc ファイルケーパビリティのバージョン rootid
変数: ユーザ名前空間内のrootユーザにマッピングされている名前空間外のUID
magic_
変数には、
ファイルケーパビリティのバージョンは、rootid
を含む情報であれば、VFS_
rootid
が設定されていないファイルケーパビリティであれば、magic_
にはVFS_
が設定されています。
このファイルケーパビリティのバージョンで、
rootid
変数がユーザ名前空間用の変数で、rootid
の値と名前空間にマッピングされているUIDの一致を確認して、
libcapは2.setcap
、getcap
コマンドにも-n
オプションが追加されています。
先の例で、getcap -n
を実行してみましょう。
$ lxc-attach c1 (コンテナ内に入る)
root@c1:~# /sbin/getcap -n ./ping
./ping = cap_net_raw+p [rootid=200000]
このコンテナは、[rootid=200000]
という表示が追加されています。
このようにgetcap
コマンドで-n
を使うと、rootid
に設定されているUIDを表示できます。
名前空間外から名前空間内で使うファイルケーパビリティを設定する
ここまでは、
まずはgetcap
です。getcap
の-n
オプションはコンテナ外からも同じように使えます。
$ /sbin/getcap -n ~/.local/share/lxc/c1/rootfs/home/gihyo/ping
(コンテナ外でgetcapコマンドを実行)
/home/gihyo/.local/share/lxc/c1/rootfs/home/gihyo/ping = cap_net_raw+p [rootid=200000]
setcap
の-n
オプションで、setcap
コマンドで-n
オプションを使って、rootid
にUIDを設定できます。
ここまでの例と同様にUID:200000とマッピングされるコンテナ向けにping
コマンドをコピーし、setcap
コマンドを実行します。
# cd /home/karma/.local/share/lxc/c1/rootfs/home/karma/ (コンテナ外でコンテナの/に移動) # cp ../../bin/ping . # /sbin/getcap -n ./ping (ケーパビリティは設定されていない) # /sbin/setcap -n 200000 cap_net_raw+p ./ping (UID:200000を指定してファイルケーパビリティを設定) # /sbin/getcap -n ./ping ./ping = cap_net_raw+p [rootid=200000] (設定された)
このようにコンテナ外から準備をした状態でコンテナを起動し、ping
コマンドを実行してみましょう。
$ lxc-start c1 $ lxc-attach c1 root@c1:~# /sbin/getcap -n ./ping ./ping = cap_net_raw+p [rootid=200000] (コンテナ外で設定した通りにコンテナ内でも見える) root@c1:~# su - gihyo gihyo@c1:~$ id -u 1000 (一般ユーザ) gihyo@c1:~$ ./ping -c1 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.029 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.029/0.029/0.029/0.000 ms (pingが実行できた)
コンテナ外で設定したrootid=200000
がコンテナ内でも設定できていることがgetcap
コマンドで確認できました。そして一般ユーザ権限でping
コマンドが実行できており、
コンテナイメージを作成する際はコンテナ起動前のホストOS上で実行しますので、
それでは、ping
コマンドが実行できるでしょうか? 試してみましょう。
$ id uid=200000(gihyo) gid=200000(gihyo) groups=200000(gihyo) (ホスト上でUID:200000のgihyoユーザになっている) $ ls -l ./ping -rwxr-xr-x 1 gihyo gihyo 183,872 12月 1日 22:49 ./ping $ /sbin/getcap -n ./ping ./ping = cap_net_raw+p [rootid=200000] (rootid=200000でファイルケーパビリティが設定されている) $ ./ping -c1 127.0.0.1 ./ping: socket: Operation not permitted (実行できない)
ファイルケーパビリティは設定されているものの、
もうひとつ、rootid
にコンテナのrootにマッピングされていないUIDを設定して、
$ sudo setcap -n 100000 cap_net_raw+p .local/share/lxc/c1/rootfs/home/gihyo/ping (コンテナ外からrootid=100000でファイルケーパビリティを設定) $ /sbin/getcap -n .local/share/lxc/c1/rootfs/home/gihyo/ping .local/share/lxc/c1/rootfs/home/gihyo/ping = cap_net_raw+p [rootid=100000] (設定されている)
このようにrootid
を100000に設定してみました。
$ id -u 1000 (一般ユーザ) $ /sbin/getcap -n ./ping ./ping = cap_net_raw+p [rootid=100000] (rootid=100000でファイルシステムケーパビリティが設定されている) $ ./ping 127.0.0.1 ./ping: socket: Operation not permitted (rootidが名前空間にマッピングされているUIDと異なるので実行できない)
以上のように、
- ユーザ名前空間内で実行されている
- ユーザ名前空間内のrootとマッピングされている名前空間外のUIDが、
ファイルケーパビリティに設定されている rootid
と一致している
まとめ
今回は、
このような名前空間内で機能するファイルケーパビリティは、
そして、
このように、
参考文献
- Unprivileged File Capabilities (brauner's blog)