SlideShare a Scribd company logo
1 of 6
Download to read offline
runc & User Namespaces
Container Runtime Meetup #1​ runcコードリーディング (2019/9/24)
対象とするruncのリビジョン: ​7507c64ff675606c5ff96b0dd8889a60c589f14d​ (2019/9/24時点で最新)
自己紹介
● GitHub: ​@AkihiroSuda​ / Twitter: ​@_AkihiroSuda_
● Moby (Docker)、containerd、BuildKitなどのメンテナ
● Rootlessコンテナなどセキュリティ関係を中心に取り組んでいる
User Namespaces とは
● 非rootユーザをrootユーザに見せかける
○ もしUserNS内のプロセスに脆弱性があっても、ホストのrootを奪われずにすむ
● UserNS内の見かけ上のrootは、真のrootとはもちろん異なる
○ UID・GIDはUserNS内では0に見えるが、UserNS外からは本来のユーザのUID・GIDとして見える
■ パーミッション制約は本来のユーザのUID・GID (kuid・kgid) に基づく
○ NS内に閉じた、見かけ上のケーパビリティを得られる (​CAP_SYS_ADMIN​など)
■ UserNSとあわせてMountNSも作るとファイルシステムのマウントもできる
● Bind-mount、tmpfs、procfs、sysfs くらいしかマウントできない
● Kernel 4.18 (2018年)からはFUSEもマウントできる
● ブロックデバイスはマウントできない
● OverlayFSはマウントできない。ただしUbuntuではカーネルにパッチが当てられてい
るのでマウントできる。
■ カーネルモジュールを読み込んだり、ホストを再起動したりなど、NS外に影響が及ぶ操作は
できない
● Kernel 2.6.23 (2007年)にて導入された
● Kernel 3.8 (2013年)からは非rootユーザが自分でUserNSを作れるようになった
利用事例
● Dockerでの利用事例
○ dockerd --userns-remap
■ コンテナ内のプロセスをUserNS内で動かす
■ runc自体、containerd自体、Docker自体は普通にrootで動く
■ runcがUserNSを作成した後のフローについて、脆弱性を軽減できる
● CVE-2016-3697​、​runc#1962​、​CVE-2019-5736​...
■ 将来的にはデフォルトになるかもしれない
○ Rootless Docker
■ コンテナ内のプロセスだけではなく、runc自体、containerd自体、Docker自体もUserNS内で
動かす
■ runcの脆弱性のみならず、Dockerデーモンなどの脆弱性も軽減できる
● CVE-2014-9357​、​CVE-2018-15664​、​CVE-2019-14271​...
■ Cgroup, checkpoint, AppArmorが使えない
● Cgroup v2移行後はcgroupも使えるようになる見込み
● Kubernetesでは未だに使えない
○ k3sは実験的にrootlessモードに対応
● LXDではデフォルトで用いられる (​dockerd --userns-remap​に類似)
Sub-users & sub-groups
● NS内に複数のUID・GIDをマップすることができる
○ NS内の見かけ上のrootから、NS内の非rootにスイッチすることで更に権限を分離できる
○ 複数UID・GIDを前提としているプログラムとの互換性
■ nginx、mysqlなど多くのミドルウェアはまずrootで起動してから、自身専用のUID・GIDに遷
移する
● /proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ に、UserNS内と外のUIDの対応表を書き込むことで設定できる
(書き込みはNS外から行う)
○ 対応表を書き込むまでは、NS内には”​nobody​” (65534)だけが存在
● 複数のUID・GIDをマップするには、NS外でのroot権限 (​CAP_SETUID​・​CAP_SETGID​)が必要
○ CAP_SETUID​ がないと、1エントリ (自分のUIDだけ) しか ​/proc/​PID​/uid_map​ に書き込めない
○ CAP_SETGID​ がないと、1エントリ (自分のUIDだけ) しか​ /proc/​PID​/gid_map​ に書き込めない
■ さらに、予め​ /proc/​PID​/setgroups​ に​ “deny”​ を書き込んでおく必要が生じる
(​setgroups(2)​を呼び出せなくなるので、supplementary groupsを設定できなくなる)
● なので、非rootユーザで複数のUID・GIDをマップしたいとき(Rootless Dockerなど)は、SETUID バイナリ
/usr/bin/newuidmap​ および​ ​/usr/bin/newgidmap​ を用いる必要がある
○ 予め​ /etc/subuid ​および​ /etc/subgid​ に、ユーザが利用して良いUID・GIDのリストを書いてお
く
■ LDAP環境では使いにくいという問題がある
○ SETUIDしているdistroが多いが、実際はfile capability (​CAP_SETUID​、​CAP_SETGID​) だけでも十分
● より詳しい情報は ​user_namespaces(7)​ 参照
● runcを使わなくても​ ​unshare -U​ コマンドで空のUserNSを作成できる
○ unshare(2)​ ​システムコールを呼び出している
runc と UserNSの関係
似て非なる組み合わせが色々ある
● runcにUserNSを作らせる場合
○ rootでruncにUserNSを作らせる場合 (​dockerd --userns-remap​ など)
○ 非rootでruncにUserNSを作らせる場合 (本来の “rootless runc”)
● 既存UserNS内でruncを実行する場合 (Rootless Dockerなど)
● runcを既存UserNSにjoinさせる場合
runcにUserNSを作らせる場合
config.json​ に次のような設定を渡すと、runcにUserNSを作成させることが出来る
"linux": {
"uidMappings": [
{
"containerID": 0,
"hostID": 1001,
"size": 1
},
{
"containerID": 1,
"hostID": 100000,
"size": 65536
}
],
"gidMappings": [
{
"containerID": 0,
"hostID": 1001,
"size": 1
},
{
"containerID": 1,
"hostID": 100000,
"size": 65536
}
],
"namespaces": [
{
"type": "user"
},
...
}
● config.json​ からlibcontainer configへの変換:
libcontainer/specconv/spec_linux.go:setupUserNamespace()
func setupUserNamespace(spec *specs.Spec, config *configs.Config) error {
create := func(m specs.LinuxIDMapping) configs.IDMap {
return configs.IDMap{
HostID: int(m.HostID),
ContainerID: int(m.ContainerID),
Size: int(m.Size),
}
}
if spec.Linux != nil {
for _, m := range spec.Linux.UIDMappings {
config.UidMappings = append(config.UidMappings, create(m))
}
for _, m := range spec.Linux.GIDMappings {
config.GidMappings = append(config.GidMappings, create(m))
}
}
rootUID, err := config.HostRootUID()
if err != nil {
return err
}
rootGID, err := config.HostRootGID()
if err != nil {
return err
}
for _, node := range config.Devices {
node.Uid = uint32(rootUID)
node.Gid = uint32(rootGID)
}
return nil
}
● git grep NEWUSER​ すると、libcontainer config変換以後の、UserNS関係のフローが見えてくる
rootでruncにUserNSを作らせる場合 (​dockerd --userns-remap​ など)
● 主要な部分は​ libcontainer/nsenter/nsexec.c​ ​に集中
● nsexecにてchildがやること:​ ​libcontainer/nsenter/nsexec.c:nsexec():JUMP_CHILD
○ unshare(CLONE_NEWUSER)​ を用いてUserNSを作成
○ parentとの通信用のFDに ​SYNC_USERMAP_PLS​ を書き込み、​uid_map​・​gid_map​の設定を要求
○ SYNC_USERMAP_ACK​を待ち、​setresuid(0)​してNS内でrootに昇格
case JUMP_CHILD:{
...
if (config.cloneflags & CLONE_NEWUSER) {
if (unshare(CLONE_NEWUSER) < 0)
bail("failed to unshare user namespace");
config.cloneflags &= ~CLONE_NEWUSER;
/*
* We don't have the privileges to do any mapping here (see the
* clone_parent rant). So signal our parent to hook us up.
*/
/* Switching is only necessary if we joined namespaces. */
if (config.namespaces) {
if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0)
bail("failed to set process as dumpable");
}
s = SYNC_USERMAP_PLS;
if (write(syncfd, &s, sizeof(s)) != sizeof(s))
bail("failed to sync with parent: write(SYNC_USERMAP_PLS)");
/* ... wait for mapping ... */
if (read(syncfd, &s, sizeof(s)) != sizeof(s))
bail("failed to sync with parent: read(SYNC_USERMAP_ACK)");
if (s != SYNC_USERMAP_ACK)
bail("failed to sync with parent: SYNC_USERMAP_ACK: got %u", s);
/* Switching is only necessary if we joined namespaces. */
if (config.namespaces) {
if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0)
bail("failed to set process as dumpable");
}
/* Become root in the namespace proper. */
if (setresuid(0, 0, 0) < 0)
bail("failed to become root in user namespace");
}
...
● nsexecにてparentがやること:
libcontainer/nsenter/nsexec.c:nsexec():JUMP_PARENT:SYNC_USERMAP_PLS
○ SYNC_USERMAP_PLS​ が来たら、​update_uidmap(); update_gidmap();​ して ​SYNC_USERMAP_ACK
を応答
○ update_uidmap(); update_gidmap();​ は単に​ /proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ に書き
込むだけ
○ parentはrootで動作しているので、​setgroups​を無効化したり、​newuidmap​・​newgidmap​ SUIDバイ
ナリを呼び出したりしなくてよい
case SYNC_USERMAP_PLS:
/*
* Enable setgroups(2) if we've been asked to. But we also
* have to explicitly disable setgroups(2) if we're
* creating a rootless container for single-entry mapping.
* i.e. config.is_setgroup == false.
* (this is required since Linux 3.19).
*
* For rootless multi-entry mapping, config.is_setgroup shall be true and
* newuidmap/newgidmap shall be used.
*/
if (config.is_rootless_euid && !config.is_setgroup)
update_setgroups(child, SETGROUPS_DENY);
/* Set up mappings. */
update_uidmap(config.uidmappath, child, config.uidmap, config.uidmap_len);
update_gidmap(config.gidmappath, child, config.gidmap, config.gidmap_len);
s = SYNC_USERMAP_ACK;
if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
kill(child, SIGKILL);
bail("failed to sync with child: write(SYNC_USERMAP_ACK)");
}
break;
● UserNSに入ったchildはデバイスノードを​mknod​できないので、ホストからbind-mount する:
libcontainer/rootfs_linux.go:createDevices()
func createDevices(config *configs.Config) error {
useBindMount := system.RunningInUserNS() || config.Namespaces.Contains(configs.NEWUSER)
oldMask := unix.Umask(0000)
for _, node := range config.Devices {
// containers running in a user namespace are not allowed to mknod
// devices so we can just bind mount it from the host.
if err := createDeviceNode(config.Rootfs, node, useBindMount); err != nil {
unix.Umask(oldMask)
return err
}
}
unix.Umask(oldMask)
return nil
}
非rootでruncにUserNSを作らせる場合 (本来の “rootless runc”)
● git grep RootlessEUID​ 、 ​git grep -i rootless_euid​ 、​git grep ‘os.Geteuid() != 0’​ すると、
非rootでruncを動作させる場合のフローが見えてくる
● parentはrootを持っていないので、​/proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ にそれぞれ1エントリしか書
き込めない
○ 複数エントリがconfigで指定されている場合は、SUIDビットまたはfile capabilityがついた​newuidmap
、​newgidmap​バイナリを呼び出して対応表を書き込む
○ 単一のエントリしか指定されていない場合は、​/proc/PID/setgroups​ に ​“deny”​ を書き込んでか
ら、​/proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ に書き込む
■ これが本来の”rootless runc”であるが、利用事例は稀
● Cgroup Managerがrootlessモードになる ​rootless_linux.go:shouldUseRootlessCgroupManager()
func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
...
if os.Geteuid() != 0 {
return true, nil
}
if !system.RunningInUserNS() {
// euid == 0 , in the initial ns (i.e. the real root)
return false, nil
}
// euid = 0, in a userns.
// As we are unaware of cgroups path, we can't determine whether we have the full
// access to the cgroups path.
// Either way, we can safely decide to use the rootless cgroups manager.
return true, nil
}
● rootlessモードのCgroup managerは、パーミッション関連のエラーを無視する:
libcontainer/cgroups/fs/apply_raw.go:*Manager.Apply()
○ Cgroupを使いたいなら、予めrootで​chmod​・​chown​しておく必要がある
■ Cgroup v1では非推奨
func (m *Manager) Apply(pid int) (err error) {
...
for _, sys := range m.getSubsystems() {
...
if err := sys.Apply(d); err != nil {
// In the case of rootless (including euid=0 in userns), where an explicit cgroup path
hasn't
// been set, we don't bail on error in case of permission problems.
// Cases where limits have been set (and we couldn't create our own
// cgroup) are handled by Set.
if isIgnorableError(m.Rootless, err) && m.Cgroups.Path == "" {
delete(m.Paths, sys.Name())
continue
}
return err
}
}
return nil
}
● checkpointやAppArmorは使えない
● UserNSと一緒にNetNSもunshareしたいなら工夫が必要 (「​既存UserNS内でruncを実行する場合​」参照)
既存UserNS内でruncを実行する場合 (Rootless Dockerなど)
● runcの外側で予めUserNSを作っておく必要がある
○ Docker、k3s、BuildKitなどのRootlessモードでは​RootlessKit​が使われる
○ PodmanのRootlessモードではPodman自身がUserNSを作成する
○ UserNSと一緒にNetNSもunshareしたいなら工夫が必要
■ NetNSをunshareしないと、コンテナ内のプロセスからコンテナ外の抽象UNIXソケットにア
クセスできてしまう
● →containerdのbreakoutに繋がる
■ 方法1: SUIDバイナリでNetNSを設定 (lxc-user-nic)
■ 方法2: NetNS内にTAPデバイスを作り、ユーザモードでTCP/IPをエミュレート (slirp4netns、
VPNKit)
■ RootlessKit系はlxc-user-nic、slirp4netns、VPNKitに対応
■ Podmanはslirp4netnsに対応
● git grep RunningInUserNS​ すると、非rootでruncを動作させる場合のフローが見えてくる
○ cgroup、checkpoint、AppArmorが使えない以外は、普通にrootでruncを動作させる場合とあまり変
わらない
runcを既存UserNSにjoinさせる場合
● 既存のUserNSのpathを指定して、nsenterさせることができる
{
...
"namespaces": [
{
"type": "user",
"path": "/proc/42/ns/user"
},
...
}
● 利用事例は稀
○ podman run --userns container:foo​ などで利用されている

More Related Content

What's hot

DockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐるDockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐるKohei Tokunaga
 
P2P Container Image Distribution on IPFS With containerd and nerdctl
P2P Container Image Distribution on IPFS With containerd and nerdctlP2P Container Image Distribution on IPFS With containerd and nerdctl
P2P Container Image Distribution on IPFS With containerd and nerdctlKohei Tokunaga
 
BuildKitによる高速でセキュアなイメージビルド
BuildKitによる高速でセキュアなイメージビルドBuildKitによる高速でセキュアなイメージビルド
BuildKitによる高速でセキュアなイメージビルドAkihiro Suda
 
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとはコンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとはksk_ha
 
Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Masahito Zembutsu
 
コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」Masahito Zembutsu
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Kohei Tokunaga
 
Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)
Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)
Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)NTT DATA Technology & Innovation
 
initとプロセス再起動
initとプロセス再起動initとプロセス再起動
initとプロセス再起動Takashi Takizawa
 
Rootlessコンテナ
RootlessコンテナRootlessコンテナ
RootlessコンテナAkihiro Suda
 
コンテナネットワーキング(CNI)最前線
コンテナネットワーキング(CNI)最前線コンテナネットワーキング(CNI)最前線
コンテナネットワーキング(CNI)最前線Motonori Shindo
 
Kubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャー
Kubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャーKubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャー
Kubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャーToru Makabe
 
OCIランタイムの筆頭「runc」を俯瞰する
OCIランタイムの筆頭「runc」を俯瞰するOCIランタイムの筆頭「runc」を俯瞰する
OCIランタイムの筆頭「runc」を俯瞰するKohei Tokunaga
 
OSTree: OSイメージとパッケージシステムの間にGitのアプローチを
OSTree: OSイメージとパッケージシステムの間にGitのアプローチをOSTree: OSイメージとパッケージシステムの間にGitのアプローチを
OSTree: OSイメージとパッケージシステムの間にGitのアプローチをi_yudai
 
Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門
Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門
Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門Preferred Networks
 
TEE (Trusted Execution Environment)は第二の仮想化技術になるか?
TEE (Trusted Execution Environment)は第二の仮想化技術になるか?TEE (Trusted Execution Environment)は第二の仮想化技術になるか?
TEE (Trusted Execution Environment)は第二の仮想化技術になるか?Kuniyasu Suzaki
 
MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)
MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)
MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)Shinya Sugiyama
 

What's hot (20)

DockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐるDockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐる
 
P2P Container Image Distribution on IPFS With containerd and nerdctl
P2P Container Image Distribution on IPFS With containerd and nerdctlP2P Container Image Distribution on IPFS With containerd and nerdctl
P2P Container Image Distribution on IPFS With containerd and nerdctl
 
BuildKitによる高速でセキュアなイメージビルド
BuildKitによる高速でセキュアなイメージビルドBuildKitによる高速でセキュアなイメージビルド
BuildKitによる高速でセキュアなイメージビルド
 
ゼロからはじめるKVM超入門
ゼロからはじめるKVM超入門ゼロからはじめるKVM超入門
ゼロからはじめるKVM超入門
 
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとはコンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
 
Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編
 
コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行
 
Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)
Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)
Kubernetes環境に対する性能試験(Kubernetes Novice Tokyo #2 発表資料)
 
initとプロセス再起動
initとプロセス再起動initとプロセス再起動
initとプロセス再起動
 
Rootlessコンテナ
RootlessコンテナRootlessコンテナ
Rootlessコンテナ
 
コンテナネットワーキング(CNI)最前線
コンテナネットワーキング(CNI)最前線コンテナネットワーキング(CNI)最前線
コンテナネットワーキング(CNI)最前線
 
Kubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャー
Kubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャーKubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャー
Kubernetesのしくみ やさしく学ぶ 内部構造とアーキテクチャー
 
OCIランタイムの筆頭「runc」を俯瞰する
OCIランタイムの筆頭「runc」を俯瞰するOCIランタイムの筆頭「runc」を俯瞰する
OCIランタイムの筆頭「runc」を俯瞰する
 
Linux Namespaces
Linux NamespacesLinux Namespaces
Linux Namespaces
 
WebSocket / WebRTCの技術紹介
WebSocket / WebRTCの技術紹介WebSocket / WebRTCの技術紹介
WebSocket / WebRTCの技術紹介
 
OSTree: OSイメージとパッケージシステムの間にGitのアプローチを
OSTree: OSイメージとパッケージシステムの間にGitのアプローチをOSTree: OSイメージとパッケージシステムの間にGitのアプローチを
OSTree: OSイメージとパッケージシステムの間にGitのアプローチを
 
Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門
Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門
Kubernete Meetup Tokyo #18 - Kubebuilder/controller-runtime 入門
 
TEE (Trusted Execution Environment)は第二の仮想化技術になるか?
TEE (Trusted Execution Environment)は第二の仮想化技術になるか?TEE (Trusted Execution Environment)は第二の仮想化技術になるか?
TEE (Trusted Execution Environment)は第二の仮想化技術になるか?
 
MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)
MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)
MySQL InnoDB Clusterによる高可用性構成(DB Tech Showcase 2017)
 

Similar to [Container Runtime Meetup] runc & User Namespaces

○○大学の本当にあった怖い話
○○大学の本当にあった怖い話○○大学の本当にあった怖い話
○○大学の本当にあった怖い話idkqh7 Nishino
 
TripleOの光と闇
TripleOの光と闇TripleOの光と闇
TripleOの光と闇Manabu Ori
 
2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!
2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!
2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!Midori Oge
 
ラズパイ2で動く Docker PaaSを作ってみたよ
ラズパイ2で動く Docker PaaSを作ってみたよラズパイ2で動く Docker PaaSを作ってみたよ
ラズパイ2で動く Docker PaaSを作ってみたよnpsg
 
20140612_Docker上でCloudStackを動かしてみる!!
20140612_Docker上でCloudStackを動かしてみる!!20140612_Docker上でCloudStackを動かしてみる!!
20140612_Docker上でCloudStackを動かしてみる!!Midori Oge
 
コンテナ情報交換会2
コンテナ情報交換会2コンテナ情報交換会2
コンテナ情報交換会2Masahide Yamamoto
 
Webサイト・フロントエンドの高速化とgrunt.jsについて
Webサイト・フロントエンドの高速化とgrunt.jsについてWebサイト・フロントエンドの高速化とgrunt.jsについて
Webサイト・フロントエンドの高速化とgrunt.jsについてTomo Fujita
 
スタート低レイヤー #0
スタート低レイヤー #0スタート低レイヤー #0
スタート低レイヤー #0Kiwamu Okabe
 
PF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaPF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaandroid sola
 
A_road_to_AMBER_simulations_ver_1.0
A_road_to_AMBER_simulations_ver_1.0A_road_to_AMBER_simulations_ver_1.0
A_road_to_AMBER_simulations_ver_1.0Satoshi Kume
 
Building production server on docker
Building production server on dockerBuilding production server on docker
Building production server on dockerHiroshi Miura
 
Building production server on docker
Building production server on dockerBuilding production server on docker
Building production server on dockerHiroshi Miura
 
NVIDIA Japan Seminar 2012
NVIDIA Japan Seminar 2012NVIDIA Japan Seminar 2012
NVIDIA Japan Seminar 2012Takuro Iizuka
 
Docker最新動向2017秋+セキュリティの落とし穴
Docker最新動向2017秋+セキュリティの落とし穴Docker最新動向2017秋+セキュリティの落とし穴
Docker最新動向2017秋+セキュリティの落とし穴Masahito Zembutsu
 
qemuのriscv64にDebianを入れてみた
qemuのriscv64にDebianを入れてみたqemuのriscv64にDebianを入れてみた
qemuのriscv64にDebianを入れてみたKazuhiro Nishiyama
 

Similar to [Container Runtime Meetup] runc & User Namespaces (20)

Dockerと継続的インテグレーション
Dockerと継続的インテグレーションDockerと継続的インテグレーション
Dockerと継続的インテグレーション
 
○○大学の本当にあった怖い話
○○大学の本当にあった怖い話○○大学の本当にあった怖い話
○○大学の本当にあった怖い話
 
initramfsについて
initramfsについてinitramfsについて
initramfsについて
 
TripleOの光と闇
TripleOの光と闇TripleOの光と闇
TripleOの光と闇
 
2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!
2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!
2013OSC関西@京都_CloudStackとCloudFoundaryがまるわかり!
 
ラズパイ2で動く Docker PaaSを作ってみたよ
ラズパイ2で動く Docker PaaSを作ってみたよラズパイ2で動く Docker PaaSを作ってみたよ
ラズパイ2で動く Docker PaaSを作ってみたよ
 
20140612_Docker上でCloudStackを動かしてみる!!
20140612_Docker上でCloudStackを動かしてみる!!20140612_Docker上でCloudStackを動かしてみる!!
20140612_Docker上でCloudStackを動かしてみる!!
 
コンテナ情報交換会2
コンテナ情報交換会2コンテナ情報交換会2
コンテナ情報交換会2
 
Node.jsでブラウザメッセンジャー
Node.jsでブラウザメッセンジャーNode.jsでブラウザメッセンジャー
Node.jsでブラウザメッセンジャー
 
Webサイト・フロントエンドの高速化とgrunt.jsについて
Webサイト・フロントエンドの高速化とgrunt.jsについてWebサイト・フロントエンドの高速化とgrunt.jsについて
Webサイト・フロントエンドの高速化とgrunt.jsについて
 
スタート低レイヤー #0
スタート低レイヤー #0スタート低レイヤー #0
スタート低レイヤー #0
 
PF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaPF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsola
 
Docker やってみた
Docker やってみたDocker やってみた
Docker やってみた
 
Rust-DPDK
Rust-DPDKRust-DPDK
Rust-DPDK
 
A_road_to_AMBER_simulations_ver_1.0
A_road_to_AMBER_simulations_ver_1.0A_road_to_AMBER_simulations_ver_1.0
A_road_to_AMBER_simulations_ver_1.0
 
Building production server on docker
Building production server on dockerBuilding production server on docker
Building production server on docker
 
Building production server on docker
Building production server on dockerBuilding production server on docker
Building production server on docker
 
NVIDIA Japan Seminar 2012
NVIDIA Japan Seminar 2012NVIDIA Japan Seminar 2012
NVIDIA Japan Seminar 2012
 
Docker最新動向2017秋+セキュリティの落とし穴
Docker最新動向2017秋+セキュリティの落とし穴Docker最新動向2017秋+セキュリティの落とし穴
Docker最新動向2017秋+セキュリティの落とし穴
 
qemuのriscv64にDebianを入れてみた
qemuのriscv64にDebianを入れてみたqemuのriscv64にDebianを入れてみた
qemuのriscv64にDebianを入れてみた
 

More from Akihiro Suda

20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...Akihiro Suda
 
20240321 [KubeCon EU Pavilion] Lima.pdf_
20240321 [KubeCon EU Pavilion] Lima.pdf_20240321 [KubeCon EU Pavilion] Lima.pdf_
20240321 [KubeCon EU Pavilion] Lima.pdf_Akihiro Suda
 
20240320 [KubeCon EU Pavilion] containerd.pdf
20240320 [KubeCon EU Pavilion] containerd.pdf20240320 [KubeCon EU Pavilion] containerd.pdf
20240320 [KubeCon EU Pavilion] containerd.pdfAkihiro Suda
 
20240201 [HPC Containers] Rootless Containers.pdf
20240201 [HPC Containers] Rootless Containers.pdf20240201 [HPC Containers] Rootless Containers.pdf
20240201 [HPC Containers] Rootless Containers.pdfAkihiro Suda
 
[Podman Special Event] Kubernetes in Rootless Podman
[Podman Special Event] Kubernetes in Rootless Podman[Podman Special Event] Kubernetes in Rootless Podman
[Podman Special Event] Kubernetes in Rootless PodmanAkihiro Suda
 
[KubeConNA2023] Lima pavilion
[KubeConNA2023] Lima pavilion[KubeConNA2023] Lima pavilion
[KubeConNA2023] Lima pavilionAkihiro Suda
 
[KubeConNA2023] containerd pavilion
[KubeConNA2023] containerd pavilion[KubeConNA2023] containerd pavilion
[KubeConNA2023] containerd pavilionAkihiro Suda
 
[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf
[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf
[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdfAkihiro Suda
 
[CNCF TAG-Runtime] Usernetes Gen2
[CNCF TAG-Runtime] Usernetes Gen2[CNCF TAG-Runtime] Usernetes Gen2
[CNCF TAG-Runtime] Usernetes Gen2Akihiro Suda
 
[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...
[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...
[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...Akihiro Suda
 
The internals and the latest trends of container runtimes
The internals and the latest trends of container runtimesThe internals and the latest trends of container runtimes
The internals and the latest trends of container runtimesAkihiro Suda
 
[KubeConEU2023] Lima pavilion
[KubeConEU2023] Lima pavilion[KubeConEU2023] Lima pavilion
[KubeConEU2023] Lima pavilionAkihiro Suda
 
[KubeConEU2023] containerd pavilion
[KubeConEU2023] containerd pavilion[KubeConEU2023] containerd pavilion
[KubeConEU2023] containerd pavilionAkihiro Suda
 
[Container Plumbing Days 2023] Why was nerdctl made?
[Container Plumbing Days 2023] Why was nerdctl made?[Container Plumbing Days 2023] Why was nerdctl made?
[Container Plumbing Days 2023] Why was nerdctl made?Akihiro Suda
 
[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile
[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile
[FOSDEM2023] Bit-for-bit reproducible builds with DockerfileAkihiro Suda
 
[CNCF TAG-Runtime 2022-10-06] Lima
[CNCF TAG-Runtime 2022-10-06] Lima[CNCF TAG-Runtime 2022-10-06] Lima
[CNCF TAG-Runtime 2022-10-06] LimaAkihiro Suda
 
[KubeCon EU 2022] Running containerd and k3s on macOS
[KubeCon EU 2022] Running containerd and k3s on macOS[KubeCon EU 2022] Running containerd and k3s on macOS
[KubeCon EU 2022] Running containerd and k3s on macOSAkihiro Suda
 
[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...
[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...
[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...Akihiro Suda
 
[Docker Tokyo #35] Docker 20.10
[Docker Tokyo #35] Docker 20.10[Docker Tokyo #35] Docker 20.10
[Docker Tokyo #35] Docker 20.10Akihiro Suda
 
[KubeCon EU 2021] Introduction and Deep Dive Into Containerd
[KubeCon EU 2021] Introduction and Deep Dive Into Containerd[KubeCon EU 2021] Introduction and Deep Dive Into Containerd
[KubeCon EU 2021] Introduction and Deep Dive Into ContainerdAkihiro Suda
 

More from Akihiro Suda (20)

20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
 
20240321 [KubeCon EU Pavilion] Lima.pdf_
20240321 [KubeCon EU Pavilion] Lima.pdf_20240321 [KubeCon EU Pavilion] Lima.pdf_
20240321 [KubeCon EU Pavilion] Lima.pdf_
 
20240320 [KubeCon EU Pavilion] containerd.pdf
20240320 [KubeCon EU Pavilion] containerd.pdf20240320 [KubeCon EU Pavilion] containerd.pdf
20240320 [KubeCon EU Pavilion] containerd.pdf
 
20240201 [HPC Containers] Rootless Containers.pdf
20240201 [HPC Containers] Rootless Containers.pdf20240201 [HPC Containers] Rootless Containers.pdf
20240201 [HPC Containers] Rootless Containers.pdf
 
[Podman Special Event] Kubernetes in Rootless Podman
[Podman Special Event] Kubernetes in Rootless Podman[Podman Special Event] Kubernetes in Rootless Podman
[Podman Special Event] Kubernetes in Rootless Podman
 
[KubeConNA2023] Lima pavilion
[KubeConNA2023] Lima pavilion[KubeConNA2023] Lima pavilion
[KubeConNA2023] Lima pavilion
 
[KubeConNA2023] containerd pavilion
[KubeConNA2023] containerd pavilion[KubeConNA2023] containerd pavilion
[KubeConNA2023] containerd pavilion
 
[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf
[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf
[DockerConハイライト] OpenPubKeyによるイメージの署名と検証.pdf
 
[CNCF TAG-Runtime] Usernetes Gen2
[CNCF TAG-Runtime] Usernetes Gen2[CNCF TAG-Runtime] Usernetes Gen2
[CNCF TAG-Runtime] Usernetes Gen2
 
[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...
[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...
[DockerCon 2023] Reproducible builds with BuildKit for software supply chain ...
 
The internals and the latest trends of container runtimes
The internals and the latest trends of container runtimesThe internals and the latest trends of container runtimes
The internals and the latest trends of container runtimes
 
[KubeConEU2023] Lima pavilion
[KubeConEU2023] Lima pavilion[KubeConEU2023] Lima pavilion
[KubeConEU2023] Lima pavilion
 
[KubeConEU2023] containerd pavilion
[KubeConEU2023] containerd pavilion[KubeConEU2023] containerd pavilion
[KubeConEU2023] containerd pavilion
 
[Container Plumbing Days 2023] Why was nerdctl made?
[Container Plumbing Days 2023] Why was nerdctl made?[Container Plumbing Days 2023] Why was nerdctl made?
[Container Plumbing Days 2023] Why was nerdctl made?
 
[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile
[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile
[FOSDEM2023] Bit-for-bit reproducible builds with Dockerfile
 
[CNCF TAG-Runtime 2022-10-06] Lima
[CNCF TAG-Runtime 2022-10-06] Lima[CNCF TAG-Runtime 2022-10-06] Lima
[CNCF TAG-Runtime 2022-10-06] Lima
 
[KubeCon EU 2022] Running containerd and k3s on macOS
[KubeCon EU 2022] Running containerd and k3s on macOS[KubeCon EU 2022] Running containerd and k3s on macOS
[KubeCon EU 2022] Running containerd and k3s on macOS
 
[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...
[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...
[Paris Container Day 2021] nerdctl: yet another Docker & Docker Compose imple...
 
[Docker Tokyo #35] Docker 20.10
[Docker Tokyo #35] Docker 20.10[Docker Tokyo #35] Docker 20.10
[Docker Tokyo #35] Docker 20.10
 
[KubeCon EU 2021] Introduction and Deep Dive Into Containerd
[KubeCon EU 2021] Introduction and Deep Dive Into Containerd[KubeCon EU 2021] Introduction and Deep Dive Into Containerd
[KubeCon EU 2021] Introduction and Deep Dive Into Containerd
 

[Container Runtime Meetup] runc & User Namespaces

  • 1. runc & User Namespaces Container Runtime Meetup #1​ runcコードリーディング (2019/9/24) 対象とするruncのリビジョン: ​7507c64ff675606c5ff96b0dd8889a60c589f14d​ (2019/9/24時点で最新) 自己紹介 ● GitHub: ​@AkihiroSuda​ / Twitter: ​@_AkihiroSuda_ ● Moby (Docker)、containerd、BuildKitなどのメンテナ ● Rootlessコンテナなどセキュリティ関係を中心に取り組んでいる User Namespaces とは ● 非rootユーザをrootユーザに見せかける ○ もしUserNS内のプロセスに脆弱性があっても、ホストのrootを奪われずにすむ ● UserNS内の見かけ上のrootは、真のrootとはもちろん異なる ○ UID・GIDはUserNS内では0に見えるが、UserNS外からは本来のユーザのUID・GIDとして見える ■ パーミッション制約は本来のユーザのUID・GID (kuid・kgid) に基づく ○ NS内に閉じた、見かけ上のケーパビリティを得られる (​CAP_SYS_ADMIN​など) ■ UserNSとあわせてMountNSも作るとファイルシステムのマウントもできる ● Bind-mount、tmpfs、procfs、sysfs くらいしかマウントできない ● Kernel 4.18 (2018年)からはFUSEもマウントできる ● ブロックデバイスはマウントできない ● OverlayFSはマウントできない。ただしUbuntuではカーネルにパッチが当てられてい るのでマウントできる。 ■ カーネルモジュールを読み込んだり、ホストを再起動したりなど、NS外に影響が及ぶ操作は できない ● Kernel 2.6.23 (2007年)にて導入された ● Kernel 3.8 (2013年)からは非rootユーザが自分でUserNSを作れるようになった 利用事例 ● Dockerでの利用事例 ○ dockerd --userns-remap ■ コンテナ内のプロセスをUserNS内で動かす ■ runc自体、containerd自体、Docker自体は普通にrootで動く ■ runcがUserNSを作成した後のフローについて、脆弱性を軽減できる ● CVE-2016-3697​、​runc#1962​、​CVE-2019-5736​... ■ 将来的にはデフォルトになるかもしれない ○ Rootless Docker ■ コンテナ内のプロセスだけではなく、runc自体、containerd自体、Docker自体もUserNS内で 動かす ■ runcの脆弱性のみならず、Dockerデーモンなどの脆弱性も軽減できる ● CVE-2014-9357​、​CVE-2018-15664​、​CVE-2019-14271​... ■ Cgroup, checkpoint, AppArmorが使えない ● Cgroup v2移行後はcgroupも使えるようになる見込み ● Kubernetesでは未だに使えない ○ k3sは実験的にrootlessモードに対応 ● LXDではデフォルトで用いられる (​dockerd --userns-remap​に類似)
  • 2. Sub-users & sub-groups ● NS内に複数のUID・GIDをマップすることができる ○ NS内の見かけ上のrootから、NS内の非rootにスイッチすることで更に権限を分離できる ○ 複数UID・GIDを前提としているプログラムとの互換性 ■ nginx、mysqlなど多くのミドルウェアはまずrootで起動してから、自身専用のUID・GIDに遷 移する ● /proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ に、UserNS内と外のUIDの対応表を書き込むことで設定できる (書き込みはNS外から行う) ○ 対応表を書き込むまでは、NS内には”​nobody​” (65534)だけが存在 ● 複数のUID・GIDをマップするには、NS外でのroot権限 (​CAP_SETUID​・​CAP_SETGID​)が必要 ○ CAP_SETUID​ がないと、1エントリ (自分のUIDだけ) しか ​/proc/​PID​/uid_map​ に書き込めない ○ CAP_SETGID​ がないと、1エントリ (自分のUIDだけ) しか​ /proc/​PID​/gid_map​ に書き込めない ■ さらに、予め​ /proc/​PID​/setgroups​ に​ “deny”​ を書き込んでおく必要が生じる (​setgroups(2)​を呼び出せなくなるので、supplementary groupsを設定できなくなる) ● なので、非rootユーザで複数のUID・GIDをマップしたいとき(Rootless Dockerなど)は、SETUID バイナリ /usr/bin/newuidmap​ および​ ​/usr/bin/newgidmap​ を用いる必要がある ○ 予め​ /etc/subuid ​および​ /etc/subgid​ に、ユーザが利用して良いUID・GIDのリストを書いてお く ■ LDAP環境では使いにくいという問題がある ○ SETUIDしているdistroが多いが、実際はfile capability (​CAP_SETUID​、​CAP_SETGID​) だけでも十分 ● より詳しい情報は ​user_namespaces(7)​ 参照 ● runcを使わなくても​ ​unshare -U​ コマンドで空のUserNSを作成できる ○ unshare(2)​ ​システムコールを呼び出している runc と UserNSの関係 似て非なる組み合わせが色々ある ● runcにUserNSを作らせる場合 ○ rootでruncにUserNSを作らせる場合 (​dockerd --userns-remap​ など) ○ 非rootでruncにUserNSを作らせる場合 (本来の “rootless runc”) ● 既存UserNS内でruncを実行する場合 (Rootless Dockerなど) ● runcを既存UserNSにjoinさせる場合
  • 3. runcにUserNSを作らせる場合 config.json​ に次のような設定を渡すと、runcにUserNSを作成させることが出来る "linux": { "uidMappings": [ { "containerID": 0, "hostID": 1001, "size": 1 }, { "containerID": 1, "hostID": 100000, "size": 65536 } ], "gidMappings": [ { "containerID": 0, "hostID": 1001, "size": 1 }, { "containerID": 1, "hostID": 100000, "size": 65536 } ], "namespaces": [ { "type": "user" }, ... } ● config.json​ からlibcontainer configへの変換: libcontainer/specconv/spec_linux.go:setupUserNamespace() func setupUserNamespace(spec *specs.Spec, config *configs.Config) error { create := func(m specs.LinuxIDMapping) configs.IDMap { return configs.IDMap{ HostID: int(m.HostID), ContainerID: int(m.ContainerID), Size: int(m.Size), } } if spec.Linux != nil { for _, m := range spec.Linux.UIDMappings { config.UidMappings = append(config.UidMappings, create(m)) } for _, m := range spec.Linux.GIDMappings { config.GidMappings = append(config.GidMappings, create(m)) } } rootUID, err := config.HostRootUID() if err != nil { return err } rootGID, err := config.HostRootGID() if err != nil { return err } for _, node := range config.Devices { node.Uid = uint32(rootUID) node.Gid = uint32(rootGID) } return nil } ● git grep NEWUSER​ すると、libcontainer config変換以後の、UserNS関係のフローが見えてくる
  • 4. rootでruncにUserNSを作らせる場合 (​dockerd --userns-remap​ など) ● 主要な部分は​ libcontainer/nsenter/nsexec.c​ ​に集中 ● nsexecにてchildがやること:​ ​libcontainer/nsenter/nsexec.c:nsexec():JUMP_CHILD ○ unshare(CLONE_NEWUSER)​ を用いてUserNSを作成 ○ parentとの通信用のFDに ​SYNC_USERMAP_PLS​ を書き込み、​uid_map​・​gid_map​の設定を要求 ○ SYNC_USERMAP_ACK​を待ち、​setresuid(0)​してNS内でrootに昇格 case JUMP_CHILD:{ ... if (config.cloneflags & CLONE_NEWUSER) { if (unshare(CLONE_NEWUSER) < 0) bail("failed to unshare user namespace"); config.cloneflags &= ~CLONE_NEWUSER; /* * We don't have the privileges to do any mapping here (see the * clone_parent rant). So signal our parent to hook us up. */ /* Switching is only necessary if we joined namespaces. */ if (config.namespaces) { if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) bail("failed to set process as dumpable"); } s = SYNC_USERMAP_PLS; if (write(syncfd, &s, sizeof(s)) != sizeof(s)) bail("failed to sync with parent: write(SYNC_USERMAP_PLS)"); /* ... wait for mapping ... */ if (read(syncfd, &s, sizeof(s)) != sizeof(s)) bail("failed to sync with parent: read(SYNC_USERMAP_ACK)"); if (s != SYNC_USERMAP_ACK) bail("failed to sync with parent: SYNC_USERMAP_ACK: got %u", s); /* Switching is only necessary if we joined namespaces. */ if (config.namespaces) { if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0) bail("failed to set process as dumpable"); } /* Become root in the namespace proper. */ if (setresuid(0, 0, 0) < 0) bail("failed to become root in user namespace"); } ... ● nsexecにてparentがやること: libcontainer/nsenter/nsexec.c:nsexec():JUMP_PARENT:SYNC_USERMAP_PLS ○ SYNC_USERMAP_PLS​ が来たら、​update_uidmap(); update_gidmap();​ して ​SYNC_USERMAP_ACK を応答 ○ update_uidmap(); update_gidmap();​ は単に​ /proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ に書き 込むだけ ○ parentはrootで動作しているので、​setgroups​を無効化したり、​newuidmap​・​newgidmap​ SUIDバイ ナリを呼び出したりしなくてよい case SYNC_USERMAP_PLS: /* * Enable setgroups(2) if we've been asked to. But we also * have to explicitly disable setgroups(2) if we're * creating a rootless container for single-entry mapping. * i.e. config.is_setgroup == false. * (this is required since Linux 3.19). * * For rootless multi-entry mapping, config.is_setgroup shall be true and * newuidmap/newgidmap shall be used. */ if (config.is_rootless_euid && !config.is_setgroup) update_setgroups(child, SETGROUPS_DENY);
  • 5. /* Set up mappings. */ update_uidmap(config.uidmappath, child, config.uidmap, config.uidmap_len); update_gidmap(config.gidmappath, child, config.gidmap, config.gidmap_len); s = SYNC_USERMAP_ACK; if (write(syncfd, &s, sizeof(s)) != sizeof(s)) { kill(child, SIGKILL); bail("failed to sync with child: write(SYNC_USERMAP_ACK)"); } break; ● UserNSに入ったchildはデバイスノードを​mknod​できないので、ホストからbind-mount する: libcontainer/rootfs_linux.go:createDevices() func createDevices(config *configs.Config) error { useBindMount := system.RunningInUserNS() || config.Namespaces.Contains(configs.NEWUSER) oldMask := unix.Umask(0000) for _, node := range config.Devices { // containers running in a user namespace are not allowed to mknod // devices so we can just bind mount it from the host. if err := createDeviceNode(config.Rootfs, node, useBindMount); err != nil { unix.Umask(oldMask) return err } } unix.Umask(oldMask) return nil } 非rootでruncにUserNSを作らせる場合 (本来の “rootless runc”) ● git grep RootlessEUID​ 、 ​git grep -i rootless_euid​ 、​git grep ‘os.Geteuid() != 0’​ すると、 非rootでruncを動作させる場合のフローが見えてくる ● parentはrootを持っていないので、​/proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ にそれぞれ1エントリしか書 き込めない ○ 複数エントリがconfigで指定されている場合は、SUIDビットまたはfile capabilityがついた​newuidmap 、​newgidmap​バイナリを呼び出して対応表を書き込む ○ 単一のエントリしか指定されていない場合は、​/proc/PID/setgroups​ に ​“deny”​ を書き込んでか ら、​/proc/​PID​/uid_map​、​/proc/​PID​/gid_map​ に書き込む ■ これが本来の”rootless runc”であるが、利用事例は稀 ● Cgroup Managerがrootlessモードになる ​rootless_linux.go:shouldUseRootlessCgroupManager() func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) { ... if os.Geteuid() != 0 { return true, nil } if !system.RunningInUserNS() { // euid == 0 , in the initial ns (i.e. the real root) return false, nil } // euid = 0, in a userns. // As we are unaware of cgroups path, we can't determine whether we have the full // access to the cgroups path. // Either way, we can safely decide to use the rootless cgroups manager. return true, nil } ● rootlessモードのCgroup managerは、パーミッション関連のエラーを無視する: libcontainer/cgroups/fs/apply_raw.go:*Manager.Apply() ○ Cgroupを使いたいなら、予めrootで​chmod​・​chown​しておく必要がある ■ Cgroup v1では非推奨 func (m *Manager) Apply(pid int) (err error) { ... for _, sys := range m.getSubsystems() { ... if err := sys.Apply(d); err != nil { // In the case of rootless (including euid=0 in userns), where an explicit cgroup path
  • 6. hasn't // been set, we don't bail on error in case of permission problems. // Cases where limits have been set (and we couldn't create our own // cgroup) are handled by Set. if isIgnorableError(m.Rootless, err) && m.Cgroups.Path == "" { delete(m.Paths, sys.Name()) continue } return err } } return nil } ● checkpointやAppArmorは使えない ● UserNSと一緒にNetNSもunshareしたいなら工夫が必要 (「​既存UserNS内でruncを実行する場合​」参照) 既存UserNS内でruncを実行する場合 (Rootless Dockerなど) ● runcの外側で予めUserNSを作っておく必要がある ○ Docker、k3s、BuildKitなどのRootlessモードでは​RootlessKit​が使われる ○ PodmanのRootlessモードではPodman自身がUserNSを作成する ○ UserNSと一緒にNetNSもunshareしたいなら工夫が必要 ■ NetNSをunshareしないと、コンテナ内のプロセスからコンテナ外の抽象UNIXソケットにア クセスできてしまう ● →containerdのbreakoutに繋がる ■ 方法1: SUIDバイナリでNetNSを設定 (lxc-user-nic) ■ 方法2: NetNS内にTAPデバイスを作り、ユーザモードでTCP/IPをエミュレート (slirp4netns、 VPNKit) ■ RootlessKit系はlxc-user-nic、slirp4netns、VPNKitに対応 ■ Podmanはslirp4netnsに対応 ● git grep RunningInUserNS​ すると、非rootでruncを動作させる場合のフローが見えてくる ○ cgroup、checkpoint、AppArmorが使えない以外は、普通にrootでruncを動作させる場合とあまり変 わらない runcを既存UserNSにjoinさせる場合 ● 既存のUserNSのpathを指定して、nsenterさせることができる { ... "namespaces": [ { "type": "user", "path": "/proc/42/ns/user" }, ... } ● 利用事例は稀 ○ podman run --userns container:foo​ などで利用されている