Set capabilities for a Container of Rootless in Kubernetes

1,020 阅读3分钟

背景简述

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

无权限ping.png

  1. 观察容器的当前用户

    uid 2000

  2. 执行 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

无权限getcap.png

因为ping是指向busybox的链接,所以直接查看busybox的Capabilities权限

getcap /bin/busybox

发现/bin/busybox没有设置Capabilities权限

再来看一下container的默认Capabilities权限

无权限capprint.png

可以发现,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仍为上文中的定义没有修改)

有权限capprint.png

有权限ping.png

/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

精简权限capprint.png

精简权限ping.png

以上的例子中都是使用shell去调用ping来验证,但实际容器中运行的是entrypoint(或cmd)指定的程序,那么这个程序包含的Capabilities权限是什么呢?

查看 /proc/<PID>/status中的定义 精简权限psaux.png 可以发现entrypoint(或cmd)指定的程序,被赋予了与capabilities.add中定义相同的权限。由此可知,当容器entrypoint(或cmd)指定的程序需要某些特殊提权时,只需在capabilities.add中添加相应的特权便可。

至此,通过精简只保留下必要的Capabilities权限,依然满足运行容器并支持提权。

总结

为了安全推荐使用rootless来运行容器;对于部分需要提权的组件,可以通过设置Linux Capabilities来满足需求;容器默认会分配很多Capabilities权限,需要精简、只保留下必要的便可。