一个与 Capabilities 相关的问题
问题描述
在使用 k8s 部署容器中遇到了一些权限问题。
我想让当前容器可以生成 core dump
文件以便后续 debug,因此我需要设置允许的 core
文件的大小,我使用 ulimit -c
该命令来修改系统限制。
在执行以下命令时遇到了一些问题。
root$ ulimit -c 10000
bash: ulimit: core file size: cannot modify limit: Operation not permitted
看起来似乎是权限不够。但是我使用的是 root 用户,问题看起来有些棘手。
问题解决
实际上我是在 ssh
环境下执行的 ulimit -c
命令,需要考虑是否是 ssh
导致的该问题。该 sshd
进程是在容器的启动命令中以 root
身份,执行的如下命令。
sudo /usr/sbin/sshd
在上网搜索一番后,没有找到解决方法,虽然了解到一些 pam
相关的权限控制相关的东西,但是对我的问题没有帮助。
ld 说之前碰到过这个问题,并且在之前的系统中解决过,只是没有记录下解决流程,一个简单的解决方法是,启动 sshd
时去掉 sudo
,因为本身就以 root
用户权限执行。不过在原来的系统中由于不是 root
用户,所以并不是通过该方法解决的,具体解决方法忘记了。
实际操作了一下,在去掉sudo
后,再执行ulimit
命令不再出现权限问题。
问题是不是出在sudo
上呢,在容器中执行的sudo
和直接在容器中以root
用户执行命令的区别在哪?
经过搜索引擎的搜索以及对之前系统和现在系统申请容器时参数的比较,我发现新老系统在容器模板中的securityContext
参数存在差异。在原来的系统中存在如下参数:
securityContext:
capabilities:
add:
- CAP_SYS_RESOURCE
在增加了该参数到新系统后,ulimit -c
能够正常运行了!
Capabilities
那么问题来了,为什么会出现这样的现象呢?
这是由于 docker 容器中的 root 用户其实并不能完全拥有宿主机 root 用户的全部权限。如果让容器中的用户拥有全部权限,会对系统的安全埋下重要隐患,而且容器中的进程运行并不一定需要系统的全部权限。因此,docker 使用了 capabilities 这一 linux 系统原生的进程权限控制机制,对容器的权限进行控制。
Capabilities 机制
linux 中有两种 capabilities,分别是进程的 capabilities 和文件的 capabilities。
进程的 capabilities
每个进程有五个 capabilities 集合,每个集合用 64 位掩码表示,以 16 进制格式显示,分别为:
- Permitted
- Effective
- Inheritable
- Bounding
- Ambient
以下分别介绍每个集合的作用。
Permitted
是
Effective
和Inheritable
集合的有限超集。进程可以通过capset()
这一系统调用来在Effective
或者Inheritable
集合中添加和删除 capability。Effective
内核用来检查权限的集合,是实际上的使能集合。
Inheritable
在执行
execve()
这一系统调用运行文件时,Inheritable
集合将会保留。同时如果可执行文件设置了inheritable
位,Inheritable
集合中的 capabilities 会被添加到Permitted
集合中。但是需要注意的是,如果是非 root 用户调用execve()
,一般不会保留Inheritable
集合,因此主程序如果需要运行一些帮助程序,需要使用Ambient
集合来提权。Bounding
用来限制
execve()
过程可获取的 capabilities。Ambient
execve()
过程执行非特权程序时仍然保留的集合。保证其中的 capabilites 必须同时在Permitted
和Inheritable
集合中。如果执行了 SUID 或 SGID 程序(会改变 UID,GID),或者设置了 file capabilities 的程序,会直接清空该集合。在
execve()
过程中,Ambient
集合的元素会被加到Permitted
和Effective
集合中。
在执行 fork()
系统调用时,会复制父进程的这五个集合。
使用 capset()
系统调用,可以修改进程自己的 capabilities 集合。
文件的 capabilities
通过 setcap()
可以将 capability 集合与可执行文件关联到一起。这些集合被存在文件的扩展属性中。
对这一扩展属性进行操作需要进程具有 CAP_SETFCAP
的 capability。
这一属性决定了用户 execve()
之后进程的 capabilities。
一共包含三个集合:
Permitted
自动添加到进程的 Permitted 集合中。
Inheritable
与进程的 Inheritable 集合做与运算,将其加入到进程的 Permitted 集合中。
Effective
不是一个集合,而是单个单独的位。设置后,
execve()
系统调用中所有新加入到进程Permitted
集合的 capabilities 也会被加入到Effective
集合,否则则不会加入。通过使能该位,可以将通过另外两个集合的特性在
execve()
中添加到Permitted
集合的 capabilities 也加到Effective
集合中。
问题深究
在学习了以上机制后,猜测是 root 下直接执行和 sudo 执行时,sshd 进程的 capabilities 存在差异。因此需要对两种不同情况下的 capabilities 进行检查,以验证猜想。
验证 1
在 k8s 中启动两个容器,使用不同的启动命令,设置不同的 securityContext,输出在 root 和 sudo 环境下分别的 capabilities 集合。
使用命令
cat /proc/self/status
其中包含当前进程的 capabilities 集合信息 根据 securityContext 中是否包含 CAP_SYS_RESOURCE,以及使用 root 和直接用 sudo 共四种情况
root & no CAP_SYS_RESOURCE
CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000
root & CAP_SYS_RESOURCE
CapInh: 00000000a90425fb CapPrm: 00000000a90425fb CapEff: 00000000a90425fb CapBnd: 00000000a90425fb CapAmb: 0000000000000000
sudo & CAP_SYS_RESOURCE
CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000
sudo & CAP_SYS_RESOURCE
CapInh: 00000000a90425fb CapPrm: 00000000a90425fb CapEff: 00000000a90425fb CapBnd: 00000000a90425fb CapAmb: 0000000000000000
根据以上结果来看,似乎没有差异,因此必定与sshd
本身的行为相关。
验证 2
查看在不开启 securityContext ,两种运行 sshd 的方式下,通过 ssh 连接到容器后的终端进程的 capabilities。
root
CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000
sudo
CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000
似乎还是没有差异。。。
那应该是别的原因,在 securityContext 中增加 CAP_SYS_RESOURCE 应该只是一种解决方式。根本原因还是 sudo 和 root 下执行的环境有差异,继续研究吧。。
验证3
从 /usr/sbin/sshd
入手
首先
ls -l /usr/sbin/sshd
-rwsr-sr-x 1 root root 790952 Aug 12 2021 /usr/sbin/sshd
可以看到这是一个 SUID 的程序,即运行时会以 superuser 的身份执行所有的操作。 麻了,最终也没找到问题在于什么地方。。。
后记
最终,我并没有找个这个问题产生的根本原因,如果你有什么想法,请联系我,谢谢🙏🏻。