年末を迎えて今年もAdvent Calendarが多数作られていますね。この連載の今回の記事はLinuxカーネルの機能を紹介するので、
さて、
ネットワークの話やサウンドの話はPlamo Linux以外でも十分に応用ができる話でしたし、
田向さん担当の記事のうち、
今回はこの一般ユーザがコンテナを起動する際に利用するLinuxカーネルのユーザ名前空間
ユーザ名前空間
ユーザ名前空間については第2回で少し説明しました。しかし、
ユーザ名前空間は3.
つまり名前空間内のUID、
この機能により、
このような機能であるため、
もちろん名前空間内の特権ユーザは本来の特権ユーザとは異なりますので、
名前空間内の特権ユーザに許可する操作についてはまだ議論がされていたりしますし、
それではユーザ名前空間について細かく見ていきましょう。
UID、GIDのマッピング
前述のように、
このマッピングは/proc
以下の各PID名のディレクトリ以下のuid_
とgid_
というファイルに書き込みます。つまり名前空間内のプロセスに対してマッピングを定義するわけですね。
この定義は同じユーザ名前空間内のプロセスにも継承されますので、
まずはホストOS上の通常のプロセスのこの2つのファイルの中身を見てみましょう。
# cat /proc/1/uid_map 0 0 4294967295 # cat /proc/1/gid_map 0 0 4294967295
3つの数字が並んでいますね。このようにuid_
とgid_
の書式は同じで、
(名前空間内の最初のID) (名前空間外の最初のID) (範囲)
名前空間内で使用する最初のUID/
そして、
最後に、
前述の例をマッピングファイルに書くと以下のようになります。
0 100000 65536
名前空間を作成した直後など、
- UIDとして
/proc/
の値sys/ kernel/ overflowuid - GIDとして
/proc/
の値sys/ kernel/ overflowgid
が使用されます。
ユーザ名前空間でのUID、GIDのマッピングの様子
では、
ここではUbuntu 14.unshare
コマンドにユーザ名前空間を作成する機能がありません。unshare
コマンドがユーザ名前空間をサポートするのはutil-linux 2.
まずは現在のユーザの確認です。
$ whoami ; id -u ; id -g ubuntu 1000 1000
ご覧のようにUID/ubuntu
というユーザです。
先に説明したマッピングがされない状態でのマッピング先も確認しておきます。
$ cat /proc/sys/kernel/overflowuid /proc/sys/kernel/overflowgid 65534 65534
次にunshare
コマンドを実行して新しいユーザ名前空間を作成してみましょう。ユーザ名前空間を作成するには--user
オプションを指定します。特にコマンドを指定しなければ作成した名前空間でシェルが起動します。
$ unshare --user (ユーザ名前空間を作成してシェルを起動) $ echo $$ (作成した名前空間内で実行されている) 1551
新しいユーザ名前空間で起動したシェルで、
$ grep "[U|G]id" /proc/1551/status Uid: 65534 65534 65534 65534 Gid: 65534 65534 65534 65534 $ ls -ld /proc/1551 dr-xr-xr-x 9 nobody nogroup 0 Dec 1 16:53 /proc/1551 $ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
まだマッピングのための操作を行っていませんので、/proc/
のUid/status
ファイルのUid
とGid
の行の意味はman 5 proc
で調べてくださいね。
さて、
$ grep "[U|G]id" /proc/1551/status Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000 $ ls -ld /proc/1551/status -r--r--r-- 1 ubuntu ubuntu 0 Dec 1 16:54 /proc/1551/status
ご覧のように親の名前空間上では、
- ホストOSの名前空間のUID/
GID=1000/ 1000 → 作成した名前空間内のUID/ GID=65534/ 65534
というマッピングがされており、
ではマッピングを作成してみましょう。このマッピング操作は作成したユーザ名前空間の親の名前空間から実行する必要があります。
新しく作成したユーザ名前空間内のUID/
$ echo '0 1000 1' > /proc/1551/uid_map $ echo '0 1000 1' > /proc/1551/gid_map $ grep "[U|G]id" /proc/1551/status Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000
- ※)
- その後、カーネルの修正によりgid_
mapへの書き込みは一般ユーザ権限ではできなくなっています (正確には`CAP_ SETGID`のケーパビリティが必要)。上記の echo '0 1000 1' > /proc/
という操作はエラーになりますので、1551/ gid_ map echo '0 1000 1' | sudo tee /proc/
のようにする必要があります。1551/ gid_ map
ホストOS上ではプロセスのUID/
ここで作成したユーザ名前空間のシェルに戻ってみましょう。
$ grep "[U|G]id" /proc/1551/status Uid: 0 0 0 0 Gid: 0 0 0 0 $ id uid=0(root) gid=0(root) groups=0(root),65534(nogroup) $ ls -ld /proc/1551 dr-xr-xr-x 9 root root 0 Dec 1 16:53 /proc/1551
ご覧のように、
ユーザ名前空間内でのいろいろな操作
このユーザ名前空間内でファイルを作成してみましょう。
$ pwd /home/ubuntu $ touch testfile $ ls -l testfile -rw-rw-r-- 1 root root 0 Dec 1 17:22 testfile
rootの所有でファイルが作成されましたね。親の名前空間でこのファイルを見ると、
$ ls -l /home/ubuntu/testfile -rw-rw-r-- 1 ubuntu ubuntu 0 Dec 1 17:22 /home/ubuntu/testfile
親の名前空間上のユーザの所有のファイルとして見えていますね。
次に、
ここまでの例では、
$ ip link show (親の名前空間でも作成した名前空間でも同じ結果が得られる)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:53:a8:9c brd ff:ff:ff:ff:ff:ff
通常はrootであればネットワークインターフェースをdownさせたり、
$ whoami root $ ip link set eth0 down RTNETLINK answers: Operation not permitted $ ip link add name veth0-host type veth peer name veth0-ct RTNETLINK answers: Operation not permitted
rootであっても、
せっかくrootユーザになったのに、
試しにホストOS上の一般ユーザでネットワーク名前空間を作るコマンドを実行してみましょう。
$ whoami
ubuntu
$ unshare --net (親の名前空間では一般ユーザはユーザ以外の名前空間は作れない)
unshare: unshare failed: Operation not permitted
以上のように親の名前空間で一般ユーザで実行するとエラーになりますね。ところが、
$ whoami root $ unshare --net (ユーザ名前空間内では新たにネットワーク名前空間を作れる) # ip link show (新しいネットワーク名前空間なのでループバックインターフェースのみ存在している) 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 # ip link add name veth0-host type veth peer name veth0-ct (vethインターフェースの作成) # ip link show (作成できた) 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: veth0-ct: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 76:29:7f:1f:8f:60 brd ff:ff:ff:ff:ff:ff 3: veth0-host: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether ce:89:db:2d:33:2b brd ff:ff:ff:ff:ff:ff
以上のように作成したユーザ名前空間内の特権ユーザであれば、
もちろん、
複数のIDのマッピング
ここまでの例では、ubuntu
ユーザのIDのみを、1
にしていました。
この場合、ubuntu
ユーザでマッピングファイルへの書き込みができました。自身で起動したプロセスに対する操作ですので権限があるためです。
マッピングファイルに書き込む値の
コンテナを使う場合、
このような一般ユーザでのコンテナの利用の際のマッピングの問題を解決するために、
この
このusermod
コマンドの-v
オプション-w
オプション
たとえば、test
ユーザにUID/
# usermod -v 200000-265535 -w 200000-265535 test
この定義は/etc/
と/etc/
に保存されます。
# grep test /etc/subuid test:200000:65536 # grep test /etc/subgid test:200000:65536
ちなみにUbuntuではインストール時に作成したubuntu
ユーザに対して、
この状態で新たにユーザを追加すると、
- ※)
- Ubuntu 14.
04 LTS と 14. 10 で確認
$ sudo adduser test Adding user `test' ... Adding new group `test' (1001) ... Adding new user `test' (1001) with group `test' ... Creating home directory `/home/test' ... Copying files from `/etc/skel' ... Enter new UNIX password: Retype new UNIX password: : (略) $ grep test /etc/subuid test:165536:65536 $ grep test /etc/subgid test:165536:65536
このサブUIDとサブGIDを使ってマッピングを作成するには、
$ sudo apt-get install uidmap $ ls -l /usr/bin/new?idmap -rwsr-xr-x 1 root root 33688 Jul 18 23:29 /usr/bin/newgidmap -rwsr-xr-x 1 root root 33688 Jul 18 23:29 /usr/bin/newuidmap
インストールすると、newuidmap
コマンドとnewgidmap
コマンドがインストールされます。
では、newuidmap
コマンドを使って複数のIDをユーザ名前空間に対してマッピングしてみましょう。
ここでは実験のためにデフォルトで定義されているサブUID以外にさらに定義を追加します。
$ sudo usermod -v 1000-1001 ubuntu $ cat /etc/subuid ubuntu:100000:65536 ubuntu:1000:2
1000と1001の2つがubuntu
ユーザで使えるようになりました。
新しいユーザ名前空間で起動しているPID=1777のシェルに対してマッピングを定義します。名前空間内のUID=0と1を名前空間外のUID=1000と1001にそれぞれマッピングします。
$ newuidmap 1777 0 1000 2 $ cat /proc/1777/uid_map 0 1000 2
newuidmap
コマンドには、uid_
、gid_
ファイルと似ていますね。
それではユーザ名前空間内で実効UIDを1に変えてみましょう。
$ echo $$ 1777 $ whoami root $ python Python 2.7.8 (default, Oct 20 2014, 15:05:19) [GCC 4.9.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.seteuid(1) >>> os.getpid() 1800
以上のようにPythonを起動し、seteuid
してみました。Pythonのpid=1800ですので、
$ ls -ld /proc/1800 dr-xr-xr-x 9 1001 ubuntu 0 Dec 1 20:13 /proc/1800 $ grep '[U|G]id' /proc/1800/status Uid: 1000 1001 1000 1001 Gid: 1000 1000 1000 1000
以上のように実効UID=1001で実行されているのがわかります。
LXCでは、newuidmap
とnewgidmap
コマンドを使って、
まとめ
今回は一般ユーザでコンテナを操作する際に使うLinuxカーネルの機能であるユーザ名前空間について説明しました。
次回はUbuntu上での一般ユーザでのコンテナの操作について説明したいと思います。
最近のLXC関連の動き
この記事の原稿を書いている間にLXC 1.
これまでLXC関連のプロジェクトはLXCとcgroupを管理するためのCGManagerの2つでしたが、
当初は文書だけでコードはない状態でしたが、
また、
この新しいサイトも12月の初めには正式に公開されました。URLは変わっていません。
英語のコンテンツはまだとりあえず揃えただけという感が強いですが一通りのコンテンツが揃いました。日本語の翻訳もとりあえず私が一通り行い、
言語独自のコンテンツを追加することも特に問題なさそうですので、
誰でもGitHub上でforkして変更や追加を行ってプルリクエストを送れますので、