非根用户下的容器与设备
Non-root Containers And Devices
Author: Mikko Ylinen (Intel)
当用户希望在Linux上部署使用加速器设备的容器(通过Kubernetes Device Plugins )时,Pod的securityContext中与用户/组ID相关的安全设置会导致一个问题
在这篇博文中,我将讨论这个问题,并描述到目前为止为解决这个问题所做的工作。解决该issue 并不需要长篇大论。
相反,这篇文章的目的是提高人们对这个问题的认识,并强调重要的设备用例。这是Kubernetes需要的,因为Kubernetes要处理新的相关特性,比如对用户名称空间的支持。
为什么非根容器无权使用设备?
在
Kubernetes中运行容器的关键安全原则之一是最小权限原则:
Pod/container securityContext指定要设置的配置选项,例如Linux功能(CAP)、MAC策略和用户/组ID值。
此外,集群管理员还可以使用PodSecurityPolicy(已弃用)或PodSecurity Admission(alpha)等工具来对部署在集群中的Pod实施所需的安全设置
例如,这些设置可能要求容器必须是runAsNonRoot,或者禁止它们在runAsGroup或supplementalGroups中使用root的组ID运行
在Kubernetes中,kubelet构建容器可用的设备资源列表(基于来自设备插件的输入),
该列表包含在发送给CRI容器运行时的CreateContainer CRI消息中。
每个设备包含很少的信息: 主机/容器设备路径和所需的设备组权限。
{
        "type": "<string>",
        "path": "<string>",
        "major": <int64>,
        "minor": <int64>,
        "fileMode": <uint32>,
        "uid": <uint32>,
        "gid": <uint32>
},
CRI容器运行时(containerd, CRI-O)负责从主机获取每个设备的信息。默认情况下,运行时复制主机设备的用户和组id:
uid(uint32,可选)-容器命名空间中设备所有者的idgid(uint32,可选)-容器命名空间中设备组的id
类似地,运行时还提供了一些其他配置选项。基于CRI字段的config.json部分进行定义,
包括securityContext: runAsUser/runAsGroup中定义的部分内容,
它通过以下方式成为POSIX平台用户结构的一部分:
uid(int, 必需): 指定容器名称空间中的用户IDgid(int, 必需): 指定容器名称空间中的组IDadditionalGids(int数组, 可选): 在容器名称空间中指定要添加到进程的附加组id。
然而,以config.json中的配置运行容器时将导致以下问题:
当运行容器既添加了设备,又通过runAsUser/runAsGroup设置了非根用户uid/gid的容器时,将导致以下问题:
容器用户进程没有使用设备的权限(即使设备的组id是允许非根用户组使用的)。
这是因为容器用户不属于那个主机组(例如,通过additionalGids)。
如何解决这个问题呢?
您可能已经从问题定义中注意到,至少可以通过手动将设备gid添加到supplementalGroups来解决问题。
或者在只有一个设备的情况下,将runAsGroup设置为设备的组id。
然而,这是有问题的,因为设备的gid可能有不同的值,这取决于集群中的节点的发行版/版本。
例如,对于不同的发行版和版本,下面的命令返回不同的gid:
Fedora 33:
$ ls -l /dev/dri/
total 0
drwxr-xr-x. 2 root root         80 19.10. 10:21 by-path
crw-rw----+ 1 root video  226,   0 19.10. 10:42 card0
crw-rw-rw-. 1 root render 226, 128 19.10. 10:21 renderD128
$ grep -e video -e render /etc/group
video:x:39:
render:x:997:
Ubuntu 20.04:
$ ls -l /dev/dri/
total 0
drwxr-xr-x 2 root root         80 19.10. 17:36 by-path
crw-rw---- 1 root video  226,   0 19.10. 17:36 card0
crw-rw---- 1 root render 226, 128 19.10. 17:36 renderD128
$ grep -e video -e render /etc/group
video:x:44:
render:x:133:
所以说在securityContext中应该设置哪个数字? 
此外,如果runAsGroup/runAsUser值是通过外部安全策略自动分配的,不能硬编码,该怎么办?
与带有fsGroup属性的卷不同,这些设备没有CRI运行时(或kubelet)能够使用的deviceGroup/deviceUser的正式概念。
如果使用由设备插件设置的容器注释(例如,io.kubernetes.cri.hostDeviceSupplementalGroup/)来获得自定义的OCI的conf.json中uid/gid值。
这将需要改变所有现有的设备插件,这不是理想的。
相反,这里有一个对终端用户更好的解决方案 -> 设备复用securityContext的runAsUser和runAsGroup值:
- 设备
uid对应runAsUser - 设备
gid对应runAsGroup 
{
        "type": "c",
        "path": "/dev/foo",
        "major": 123,
        "minor": 4,
        "fileMode": 438,
        "uid": <runAsUser>,
        "gid": <runAsGroup>
},
使用runc OCI运行时(在non-rootless模式下)下,设备将在容器名称空间中创建(通过mknod(2)),并且使用chmod(2)将所有权更改为runAsUser/runAsGroup。
在容器名称空间中更新所有权是合理的,因为用户进程是唯一访问设备的进程。
只考虑runAsUser/runAsGroup,例如,容器中的USER设置当前被忽略。
containerd与CRI-O中通过配置下面参数生效(设备权限从安全上下文中获取: 默认false)
device_ownership_from_security_context (bool)
使用样例