背景简述
Kubernetes推荐使用非root用户运行container;但在实际项目中又会有一些例外,有需要超出rootless用户的权限情况,这样就造成了提权问题。很多建议使用setuid提权的方案并不理想,因为提权后默认等同于了root用户,这样就与使用rootless用户的初衷相违背。
可以借助Linux Capabilities功能,来解决rootless用户提权问题,对应Kubernetes参考章节Set capabilities for a Container中的介绍。
运行环境
kubernetes 1.24
集群使用kubeadm搭建
运行一个普通的rootless容器
deployment.yaml
containers:
- name: echo
image: alpine:3.15
securityContext:
readOnlyRootFilesystem: true # 根目录readonly
privileged: false # 非授权模式(即rootless)
allowPrivilegeEscalation: false # 不允许提权
securityContext:
runAsUser: 2000 # rootless
runAsNonRoot: true # 禁用root用户
runAsGroup: 3000 # rootless
fsGroup: 4000 # rootless
-
观察容器的当前用户
uid 2000
-
执行 ping localhost
permission denied(are you root?)
这时发现无法执行ping命令
先不要急着提权,再观察一下 Linux Capabilities
首先对启动image进行一下改造
FROM alpine:3.15
# 添加查看Capabilities的包依赖
RUN apk update && apk add --no-cache libcap libcap-ng-utils
因为ping是指向busybox的链接,所以直接查看busybox的Capabilities权限
getcap /bin/busybox
发现/bin/busybox没有设置Capabilities权限
再来看一下container的默认Capabilities权限
可以发现,container被默认分配了很多的Capabilities权限。
由此推断,只要/bin/busybox(ping命令的本体)含有container被分配的Capabilities权限,即可以被允许执行。
对启动image中的组件赋予Capabilities权限
再次对启动image进行改造,相关的Capabilities含义请查看capability.h。
FROM alpine:3.15
# 添加查看Capabilities的包依赖
RUN apk update && apk add --no-cache libcap libcap-ng-utils
# 赋予Capabilities权限(赋予cap_net_raw权限即可运行ping命令)
RUN setcap cap_net_raw+pei /bin/busybox
deploy后观察(注:此时的deployment.yaml仍为上文中的定义没有修改)
对/bin/busybox(ping命令的本体)添加了cap_net_raw权限后,再次执行ping就不再报权限错误了。
精简默认的Capabilities权限
经上述实验可以发现,一个没有额外配置的rootless容器会被默认分配Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap这么多的Capabilities权限;对于大多数只是运行一个http server的容器来说,默认分配的Capabilities权限太多,可以精简只保留下必要的权限即可。
精简权限的deployment.yaml
containers:
- name: echo
image: echo:latest
securityContext:
readOnlyRootFilesystem: true # 根目录readonly
privileged: false # 非授权模式(即rootless)
allowPrivilegeEscalation: true # 允许提权(如有capabilities配置,此处必须允许提权)
capabilities: # 提权内容
drop: [ALL] # 删除掉所有默认分配的权限(默认分配的权限太多,这里精简只保留必要的权限;务必在add之前定义)
add: [NET_BIND_SERVICE, NET_RAW] # 添加的额外权限(不要添加CAP_前缀; NET_BIND_SERVICE: http server启动并监听端口 NET_RAW: 允许执行ping命令)
securityContext:
runAsUser: 2000 # rootless
runAsNonRoot: true # 禁用root用户
runAsGroup: 3000 # rootless
fsGroup: 4000 # rootless
以上的例子中都是使用shell去调用ping来验证,但实际容器中运行的是entrypoint(或cmd)指定的程序,那么这个程序包含的Capabilities权限是什么呢?
查看 /proc/<PID>/status中的定义
可以发现entrypoint(或cmd)指定的程序,被赋予了与
capabilities.add中定义相同的权限。由此可知,当容器entrypoint(或cmd)指定的程序需要某些特殊提权时,只需在capabilities.add中添加相应的特权便可。
至此,通过精简只保留下必要的Capabilities权限,依然满足运行容器并支持提权。
总结
为了安全推荐使用rootless来运行容器;对于部分需要提权的组件,可以通过设置Linux Capabilities来满足需求;容器默认会分配很多Capabilities权限,需要精简、只保留下必要的便可。