Linux 3.8 の User Namespace 機能 (1)
コンテナを実現するのにカーネルに必要な機能としては大きく分けて
- 名前空間
- リソース制限
があります.
リソース制限は cgroup として実装されています (ここではこの話は置いときます).
名前空間は,その対象となる空間と他の空間を分ける機能を持っています.コンテナを作る場合,それぞれの空間で独立した uid/gid やネットワークインターフェースが存在しなければいけませんので,その機能を実現しています.既に Linux Kernel には色々な名前空間が実装されており,未実装でコンテナを安全に使うために必須と言われていたのが User Namespace (ユーザ名前空間) です.この実装は徐々に実装されてきていましたが,影響範囲が大きく,実装も難しいため実現していませんでしたが,ようやく kernel 3.8 で実装が完了するようです.
今までもコンテナごとに /etc/passwd などを置いて,それぞれでユーザ管理を行うことは可能でした.でもコンテナで UID=0 のユーザがいたとすると,ホストOS上や他のコンテナでもそのユーザは UID=0 でしたし,コンテナ内で root (UID=0) の権限で実行しているプロセスは,ホストOSや他のコンテナでも root (UID=0) の権限で実行している事になっていました.このため,コンテナ内で root 権限を持っている場合,ホスト OS 上の root 権限を持っている事になり,コンテナからホスト OS に対してなんでも出来る状態でした (例えばホストをリブートするとか).
これを解決するために新たにカーネル内での特権のチェックを行う専用の UID/GID が作られました.kernel UID/GID というもので,以下のように定義されています.
typedef struct { uid_t val; } kuid_t; typedef struct { gid_t val; } kgid_t;
この kernel UID/GID と名前空間内・コンテナ内での UID/GID を対応付ける表を名前空間内に保存しておき,コンテナ内であるユーザの権限で処理を行おうとした時は,カーネル内ではそれに対応する kernel UID/GID で処理が行われるため,コンテナ内とホスト OS 上で別の権限を持たせることが可能になります.このマッピングは /proc/PID/uid_map とか /proc/PID/gid_map というファイルに保存され,以下のような形式になっています.
0 100000 10000
これが uid のマッピングとすると,コンテナ内では UID=0 〜 10000 までのユーザが,ホスト OS 上では UID=100000 〜 110000 にマッピングされるという意味になります.コンテナ内で UID=10000 より大きな UID のユーザを作成すると,ホスト OS 上では存在しないユーザとなります.この時に kernel UID/GID として返る値は /proc/sys/kernel/overflowuid,/proc/sys/kernel/overflowgid の値になります.