Go中的命名空间–用户

在上一篇文章中,我们看到了如何使用Go在各种Linux命名空间中创建和运行进程。 我们剩下一些在新的Mount,UTS,IPC,PID,网络和用户名称空间中运行/bin/sh进程的代码。

您可能还记得,一旦将User命名空间添加到ns-process就不再需要以root用户身份运行它。 这是一个很棒的功能,因为它意味着ns-process可以更安全地运行。 但是,在将User名称空间添加到程序中时,我们无意中引入了一些不太理想的行为。

可以通过在添加用户名称空间之前和之后比较名称空间外壳中whoami的输出来证明此行为,如下所示。

  #Git仓库:https://github.com/teddyking/ns-process 
#Git标签:1.0
#在添加用户名称空间之前
  $去建立 
$ sudo ./ns-进程
-[ns-process]-#whoami

-[ns-process]-#id根
uid = 0(root)gid = 0(root)组= 0(root)
  #Git标签:1.1 
#添加用户名称空间后
  $去建立 
$ ./ns-进程
-[ns-process]-#whoami
没有人
-[ns-process]-#id没有人
uid = 65534(nobody)gid = 65534(nogroup)组= 65534(nogroup)

尽管我们现在能够以非root用户身份运行ns-process ,但是一旦进入命名空间shell,我们就失去了root身份。

在本文中,我们将解决此回归问题,并逐步了解User命名空间。

🗺UID和GID映射

失去身份的原因在于,我们缺少一些重要的配置。 仅仅添加CLONE_NEWUSER标志并期望用户名称空间可以使用是不够的。 为了正确设置名称空间,我们还需要提供称为UID和GID映射的信息。

如果您对理论不感兴趣,并渴望使用Go编码,请随时跳过本节的其余部分

ID映射及其与用户名称空间的关系本身就是一个巨大的话题,并且在本文中超出了范围。 话虽如此,您需要了解几件事,才能了解我们如何解决身份危机。 这是TL; DR要点。

  • 用户名称空间提供UID和GID的隔离
  • 在任何给定时间,同一主机上可能有多个不同的用户名称空间在使用
  • 每个Linux进程都在这些用户名称空间之一中运行
  • 用户名称空间允许用户名称空间1中的进程的UID与用户名称空间2中相同进程的UID不同
  • UID / GID映射提供了一种在两个单独的用户名称空间之间映射ID的机制

下图试图将上述内容可视化。

图中是两个用户名称空间1和2,以及它们相应的UID和GID表。 请注意,以non-root-user身份运行的进程C可以生成以root身份运行的进程D。

关键的实现细节以及防止Universe崩溃的原因是两个User命名空间之间的映射(在此由虚线表示)。

进程D仅在用户名称空间2的上下文中具有root特权。从用户命名空间1中的进程的角度来看,进程D以non-root-user身份运行,因此不具有那些非常重要的root特权。

此映射正是ns-process目前所缺少的,现在是我们进行整理的时候了。

👉走吧

可以通过在UidMappings上设置UidMappingsGidMappings字段来应用ID映射。 这两个字段都是Go的syscall软件包中找到的SysProcIDMap类型。

 type SysProcIDMap struct { 
ContainerID int // Container ID.
HostID int // Host ID.
Size int // Size.
}

ContainerIDHostID字段应该是不言自明的。 Size则稍小一些。 Size基本上决定了要映射的ID的范围 ,这使我们一次可以映射多个ID。 让我们更新程序以包括一些映射。

  #Git仓库:https://github.com/teddyking/ns-process 
#Git标签:2.0
#文件名:ns_process.go
  #... 
cmd.SysProcAttr =&syscall.SysProcAttr {
克隆标志:syscall.CLONE_NEWNS |
syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWUSER,
UidMappings:[] syscall.SysProcIDMap {
{
ContainerID:0,
主机ID:os.Getuid(),
大小:1
},
},
GidMappings:[] syscall.SysProcIDMap {
{
ContainerID:0,
主机ID:os.Getgid(),
大小:1
},
},
}
#...

在这里,我们添加了单个UID和GID映射。 我们将ContainerID设置为0,将HostID设置为当前用户的UID / GID,并将Size为1。换句话说,我们将新User命名空间中的ID = 0(又名root)映射到调用ns-process的用户的ID。 ns-process命令。

完成所有这些操作后,我们应该能够构建和运行ns-process并看到我们现在成为命名空间外壳程序内的root用户。

💁 以下已在带有Go 1.7.1的Ubuntu 16.04 Xenial上进行了测试

  $去建立 
$ ./ns-进程
-[ns-process]-#whoami

-[ns-process]-#id
uid = 0(root)gid = 0(root)组= 0(root)

我们终于得到它了! 通过添加简单的UidMapping/GidMapping我们能够在命名空间的shell中恢复我们的根身份,同时保留以非root用户身份运行ns-process的能力。

📺在下一个…

在下一篇文章中,我们将介绍reexec 。 什么是reexec ?为什么它与Go中的命名空间相关? 有关此问题的答案以及更多建议,敬请期待…

更新 :第4部分“ Go中的命名空间– Reexec”已发布,并在此处提供。