我试图以非root身份运行我的pod,并授予它一些功能。
这是我的配置:
containers:
- name: container-name
securityContext:
capabilities:
add: ["SETUID", "SYS_TIME"]
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1001
当我部署 Pod 并连接到它时,我运行
ps aux
并看到:
PID USER TIME COMMAND
1 root 0:32 node bla.js
205 root 0:00 /bin/bash
212 root 0:00 ps aux
然后我做
cat /proc/1/status
并看到:
CapPrm: 0000000000000000
CapEff: 0000000000000000
这意味着我没有能力处理这个容器的进程。
问题是,如果我从
runAsNonRoot: true
中删除 securityContext
标志,我可以看到我 do 具有多种功能。这是预期的行为。这些功能旨在将传统上与超级用户(root)相关的权限划分为不同的单元;非 root 用户无法启用/禁用此类功能,这可能会造成安全漏洞。
capabilities
键中的SecurityContext
功能旨在管理(限制或扩展)容器上下文的Linux功能;在以 root 身份运行的 pod 中,这意味着这些功能由进程继承,因为这些功能由 root 用户拥有;但是,如果 pod 以非 root 用户身份运行,则上下文是否启用这些功能并不重要,因为 Linux 内核不允许非 root 用户为进程设置功能。
这一点很容易说明。如果您将
runAsNonRoot
键设置为 true
来运行容器,并像在共享清单中那样添加功能,然后执行到 Pod 中,您应该能够看到这些功能已添加到上下文中命令:
$ capsh --print
Current: = 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_sys_time,cap_mknod,cap_audit_write,cap_setfcap+i
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_sys_time,cap_mknod,cap_audit_write,cap_setfcap
但是您会在用户 1001 运行的任何进程中看到
CapPrm
或 CapEff
设置为 x0:
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1001 1 0.0 0.0 4340 760 ? Ss 14:57 0:00 /bin/sh -c node server.js
1001 7 0.0 0.5 772128 22376 ? Sl 14:57 0:00 node server.js
1001 21 0.0 0.0 4340 720 pts/0 Ss 14:59 0:00 sh
1001 28 0.0 0.0 17504 2096 pts/0 R+ 15:02 0:00 ps aux
$ grep Cap proc/1/status
CapInh: 00000000aa0425fb
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 00000000aa0425fb
CapAmb: 0000000000000000
接受的答案只是故事的一部分。您可以实现您正在寻找的目标,但您还需要在正在执行的文件上设置功能。
您需要阅读 Linux capability(7) 手册。该手册讨论了两个不同来源的功能集,它们组合在一起形成最终的功能集:调用 execve() 之前“线程”的功能和文件功能。最终的能力集基本上是这两个来源的结合。当 execve 以 root 身份运行进程时,规则有点不同,但本质上文件功能被忽略。
当您通过 CLI 标志、或者在 docker compose 文件中、或者在 k8s securitycontext.capability 块中向 docker 提供一组功能时,最终将导致 containerd 导致 runc 执行具有这些请求功能的进程(如边界集、允许集和有效集)在 execve 之前设置。然后根据功能手册中的规则定义最终的功能集。
以 root 身份运行时的功能如果您查看
功能(7)手册“root 程序的功能和执行”部分,您将在接受的答案中找到所发生情况的描述。
如果进程的真实或有效用户ID为0(root), 那么文件可继承集和允许集将被忽略; 相反,它们在理论上被认为是全一(即, 启用所有功能)。最终结果是最终的功能集就是您提供的功能集。
例如给定 main.go:
package main
import "fmt"
import "kernel.org/pub/linux/libs/security/libcap/cap"
func main() {
c := cap.GetProc()
fmt.Printf("this process has these caps:", c)
}
docker文件:
FROM golang:1.18 as build
WORKDIR /go/src/app
COPY <<EOF ./go.mod
module "app"
go 1.18
EOF
RUN go get "kernel.org/pub/linux/libs/security/libcap/cap"
RUN go mod download
COPY main.go .
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM gcr.io/distroless/static-debian11
COPY --from=build --chmod=550 --chown=0:0 /go/bin/app /
CMD ["/app"]
和 docker-compose.yml:
version: '3.4'
services:
scratch:
user: 0:0
cap_drop:
- ALL
cap_add:
- SYS_TIME
build:
context: .
dockerfile: ./Dockerfile
运行scratch会打印出
this process has these caps:%!(EXTRA *cap.Set=cap_sys_time=ep)
。
以非 root 身份运行时的功能在这种情况下,文件功能不会被忽略。从手册
在 execve(2) 期间,内核计算新功能 使用以下算法的过程:
P'(ambient) = (file is privileged) ? 0 : P(ambient) P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient) P'(inheritable) = P(inheritable) [i.e., unchanged] P'(bounding) = P(bounding) [i.e., unchanged] where: P() denotes the value of a thread capability set before the execve(2) P'() denotes the value of a thread capability set after the execve(2) F() denotes a file capability set
因此,如果我们更新 dockerfile 并像这样编写文件: dockerfile:
FROM golang:1.18 as build
WORKDIR /go/src/app
COPY <<EOF ./go.mod
module "app"
go 1.18
EOF
RUN go get "kernel.org/pub/linux/libs/security/libcap/cap"
RUN go mod download
COPY main.go .
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM gcr.io/distroless/static-debian11:nonroot
COPY --from=build --chmod=550 --chown=65534:65534 /go/bin/app /
CMD ["/app"]
和 docker-compose.yml:
version: '3.4'
services:
scratch:
user: 65534:65534
cap_drop:
- ALL
cap_add:
- SYS_TIME
build:
context: .
dockerfile: ./Dockerfile
运行scratch会打印出
this process has these caps:%!(EXTRA *cap.Set==)
,即无!因此,当不以 root 身份运行时,您无法单独使用 docker compose 文件中的功能。 (注意 65534 是“nobody”用户和组)。
但是,如果我们在 dockerfile 中设置文件的功能:FROM golang:1.18 as build
RUN apt-get update
RUN apt-get install -y libcap2-bin
WORKDIR /go/src/app
COPY <<EOF ./go.mod
module "app"
go 1.18
EOF
RUN go get "kernel.org/pub/linux/libs/security/libcap/cap"
RUN go mod download
COPY main.go .
RUN CGO_ENABLED=0 go build -o /go/bin/app
RUN setcap 'cap_sys_time=ep' /go/bin/app
FROM gcr.io/distroless/static-debian11:nonroot
COPY --from=build --chmod=550 --chown=65534:65534 /go/bin/app /
CMD ["/app"]
无需对 docker compose 文件进行进一步更改,输出现在为
this process has these caps:%!(EXTRA *cap.Set=cap_sys_time=ep)
。因此,该进程不是以 root 身份运行,但确实具有您所追求的额外功能。然而,如果您想向在解释器中运行的某些东西(例如 python 或 bash)添加功能,这确实是一件令人头疼的事情——因为解释器是需要添加功能的东西。