无根容器
无根容器是可以由非特权用户(与根用户相对)创建、运行和管理的容器。要被认为是完全无根的,容器运行时和容器都必须在没有根特权的情况下运行。
使用无根容器的一个优势是它们可以减轻容器突破漏洞的风险。防止容器内特权提升攻击的最佳方法是配置容器的应用程序以非特权用户身份运行。然而,以无根模式运行引入了一些额外的复杂性,特别是在网络方面。有关详细信息,请参见容器网络。
用户命名空间
无根容器使用Linux内核的一个特性,称为用户命名空间。用户命名空间隔离了与安全相关的标识符和属性,特别是用户ID(UID)和组ID(GID)、凭证、根目录、密钥和能力。进程的用户和组ID可以在用户命名空间内外不同。有了用户命名空间,进程的用户和组ID范围在其用户命名空间内映射到父命名空间中的一组用户和组ID;此映射在/etc/subuid
和/etc/subgid
文件中指定。
/etc/subuid
文件授权一个用户ID将其命名空间中的userID范围映射到子命名空间中;/etc/subgid
为组ID提供相同的功能。/etc/subuid
和/etc/subgid
中的每一行包含用户名或组名,以及子命名空间中的进程被允许使用的从属ID范围。每个条目分隔的三个字段是:
- 用户/组名或ID
- 数字从属ID
- 数字从属ID计数
例如,此条目显示对于用户Maria,从属ID从10001开始,范围为65536:
maria:10001:65536
用户和组ID转换
无根模式在用户命名空间内执行容器运行时和容器内的过程。容器的用户ID映射到主机的用户ID如下:
<subuid范围的开始> + <容器内的uid> – 1
类似地,容器用户的组ID映射到:
<subgid范围的开始> + <容器内的GID> - 1
例外的是,根用户(UID=0)映射到拥有用户命名空间的UID。
在设置数据卷目录的所有权时,您必须仔细考虑此映射。Podman包括一个unshare
实用程序,可以简化设置目录所有权(不需要运行命令的用户使用sudo
)。有关更多信息,请参见管理容器镜像存储。
例如,假设Solly的UID为1000。他的账户在/etc/subuid
中有solly:12000:65536
的映射。这意味着Solly的用户命名空间中的用户ID可以映射到主机上的65536个ID范围,从UID 12000开始:
$ cat /etc/subuid
solly:120000:65536
Solly使用此UID映射运行Podman。当他在Podman容器内启动Bash并检查当前用户时,他看到以下内容:
$ podman run --rm -it ubuntu bash
root@bfda7167e840:/# id
uid=0(root) gid=0(root) groups=0(root)
这表明Bash在用户命名空间内以root用户(0)运行。
然后他检查uid_map
文件:
root@bfda7167e840:/# cat /proc/self/uid_map
0 1000 1
1 120000 65536
这表明:
- 用户命名空间中的根用户(0)映射到主机上Solly的UID(1000) -命名空间中的用户ID 1映射到主机上的UID 120000
以下图表显示了另一个示例,其中容器运行时由Maria启动,她的用户ID为1600。/etc/subuid
文件包含以下映射:
maria:10001:65536
这意 味着Maria的用户命名空间中的过程映射到从10001开始的主机UID。因为容器运行时启动容器,容器过程也属于Maria的用户命名空间。
在此示例中:
- 用户命名空间中的根用户(0)映射到主机上Maria的UID(1600)
- 用户命名空间中的用户ID 1000映射到主机上的UID 11000
Solace容器中的默认用户ID为1000001。也就是说,如果您没有指定-u
参数,容器将以UID=1000001运行。在无根环境中,容器将无法运行,因为操作系统不会在用户命名空间内分配一个以1000001开始的UID范围。要解决这个问题,启动容器时请指定一个较小的非零用户ID作为-u
参数。
资源限制配置
非特权用户可以对容器施加的资源限制受到分配给用户的限制的限制。对非特权用户分配的限制的任何更改都必须由特权用户进行。
例如,在WSL2 Ubuntu 20.04 LTS发行版上,默认的非特权用户(因此也是由非特权用户创建的容器)同时打开文件的最大数量的硬限制为4096。使用此默认值意味着PubSub+客户端连接的数量受到配置最大值的限制(因为4096小于客户端连接的最大推荐限制)。
要允许非特权用户创建容器--ulimit nofile=2448:42192
,根用户必须修改/etc/security/limits.conf
文件中用户的nofile
硬限制配置。
目录和文件所有权
PubSub+容器旨在使用由容器用户和root组拥有的目录和文件工作。
如管理软件事件代理存储中所述,软件代理使用storage-group
来维护状态信息。我们建议将此storage-group
保存在外部存储中,并将其作为卷挂载到容器中(首选)或绑定挂载。
为确保软件代理容器具有访问storage-group
所需的权限,您必须使用主机上的podman unshare
命令修改持久存储的所有权:
podman unshare chown <container user's uid>:0 -R <directory>
podman unshare
命令允许您在与您的容器相同的用户命名空间中运行命令(此案例中为chown
)。由于所有由给定用户运行的无根容器都在同一用户命名空间内运行,您只需要运行一次podman unshare chown
即可允许所有用户的容器访问目录。
例如,对于UID为5的容器用户和作为卷挂载的storage-group
,使用podman unshare
运行chown
以在容器的用户命名空间内更改目录所有者:
$ podman unshare chown 5:0 -R /home/ec2-user/.local/share/containers/storage/volumes/solace/_data
现在使用podman unshare
运行ls
以在容器的用户命名空间内查看目录所有者:
$ podman unshare ls -laZ /home/ec2-user/.local/share/containers/storage/volumes/solace/_data
drwxrwxrwx. 9 5 root system_u:object_r:container_file_t:s0 165 Feb 24 14:40 _data
再次运行ls
,这次不使用podman unshare
,以从主机命名空间的角度查看目录所有者:
$ ls -laZ /home/ec2-user/.local/share/containers/storage/volumes/solace/_data
drwxrwxrwx. 9 100004 ec2-user system_u:object_r:container_file_t:s0 165 Feb 24 14:40 _data
Podman在首次启动无根容器时重置卷挂载中的目录和文件所有权,因此只需在首次启动容器后运行podman unshare
命令。或者,创建一个空目录并绑定挂载——在这种情况下,正确的目录/文件所有权会自动分配。
有根与无根容器
运行容器的可能组合有四种变体,如下表所示:
- 容器运行时作为根执行(左两种情况)与非根执行(右两种情况)
- 容器内的用户是根(上两种情况)与非根(下两种情况)
最安全的解决方案是右下角的场景,即容器作为非根运行,容器内的用户也是非根。
表中的示例使用Podman来说明四种场景:
容器运行时作为根执行容器内的过程作为根执行以root身份启动Podman,并在容器内指定根用户(-u 0 ):brian@ubuntu:/$ sudo bashroot@ubuntu:/# whoami> root root@ubuntu:/# podman run -u 0 ... solace-container 在容器内运行bash:root@ubuntu:/# podman exec -it solace-container bash 容器内的过程以root身份运行: whoami> root ps auxf> # 注意过程以root身份运行 从主机的角度来看,相同的过程在容器外也以root身份运行:root@ubuntu:/# ps auxf> # 注意所有容器过程都以root身份运行 | 容器运行时作为非根执行容器内的过程作为根执行以非特权用户身份启动Podman,并在容器内指定根用户(-u 0 ):brian@ubuntu:/$ whoami > brian (UID 1000) brian@ubuntu:/$ podman run -u 0 ... solace-container 在容器内运行bash:brian@ubuntu:/$ podman exec -it solace-container bash 容器内的过程以root身份运行 whoami> root`````` ps auxf> # 注意过程以root身份运行 从主机的角度来看,相同的过程在容器外以UID 1000(brian)的身份运行(由于用户命名空间映射)brian@ubuntu:/$ ps auxf> # 注意所有容器过程都以用户1000(brian)的身份运行 |
容器运行时作为根执行容器内的过程作为非根执行以root身份启动Podman,且不在容器内指定用户:brian@ubuntu:/$ sudo bashroot@ubuntu:/# whoami> rootroot@ubuntu:/# podman run ... solace-container 在容器内运行bash:root@ubuntu:/# podman exec -it solace-container bash 当您不指定用户时,容器过程以用户1000001运行(Solace容器中的默认用户ID为1000001):whoami> 1000001``````ps auxf> # 注意过程以用户1000001运行 从主机的角度来看,相同的过程在容器外也以用户1000001的身份运行(容器运行时作为根运行,因此没有用户命名空间映射)root@ubuntu:/# ps auxf> # 注意所有容器过程都以用户1000001运行 | 容器运行时作为非根执行容器内的过程作为非根执行以非根用户身份启动Podman,并在容器内指定非根用户:brian@ubuntu:/$ whoami> brian (UID 1000)brian@ubuntu:/$ podman run -u 5 ... solace-container 在容器内运行bash:brian@ubuntu:/$ podman exec -it solace-container bash容器内的过程以用户5的身份运行 whoami> 5``````ps auxf> # 注意过程以用户5运行从主机的角度来看,相同的过程在容器外以UID 100004的身份运行(由于用户命名空间映射) brian@ubuntu:/$ ps auxf> # 注意所有容器过程都以用户100004运行``` |