Kubernetes 学习笔记(十七):SecurityContext 如何限制容器权限
Han Lv5

前言

上一篇写 ServiceAccount 和 RBAC 时,我把它们理解成 API 层面的权限控制:这个 Pod 里的程序能不能访问 Kubernetes API,能读什么、改什么。

但这还不够。即使一个应用不访问 API Server,它在节点上运行时仍然有权限边界:

1
2
3
4
5
容器用哪个用户运行?
能不能以 root 身份运行?
能不能提权?
文件系统能不能写?
能不能使用额外 Linux capabilities?

这些问题主要通过 SecurityContext 表达。

我目前这样区分:

1
2
3
4
5
RBAC
管访问 Kubernetes API 的权限

SecurityContext
管 Pod / 容器进程自身的运行权限

这篇先从几个最容易理解、最常用的字段开始。

Pod 级和 Container 级

SecurityContext 可以写在 Pod 层,也可以写在容器层。

1
2
3
4
5
Pod securityContext
给 Pod 内容器提供默认安全设置

Container securityContext
针对某个容器单独设置

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: demo-api
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: demo-api
image: example/demo-api:1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true

可以这样看:

1
2
3
4
5
Pod 层:
这个 Pod 默认不要用 root,文件组怎么处理

Container 层:
这个容器不能提权,根文件系统只读

如果容器级和 Pod 级都设置了相关字段,容器级通常更具体。

runAsNonRoot:不要用 root 跑应用

runAsNonRoot: true 表示容器应该以非 root 用户运行。

1
2
securityContext:
runAsNonRoot: true

如果镜像默认用户是 root,又没有指定非 root 用户,可能会启动失败。

这不是坏事。它是在提醒我:这个应用镜像没有把运行用户设计清楚。

更明确的写法:

1
2
3
securityContext:
runAsNonRoot: true
runAsUser: 1000

可以理解成:

1
2
不要用 root
用 UID 1000 运行进程

这要求镜像里的文件权限也要配合。否则应用可能因为没有权限读写自己的目录而失败。

allowPrivilegeEscalation:避免提权

allowPrivilegeEscalation: false 表示容器内进程不能获得比父进程更多的权限。

1
2
securityContext:
allowPrivilegeEscalation: false

我把它理解成:

1
2
进程进来时是什么权限
后面不要通过特殊方式抬高权限

这对普通业务容器通常是合理默认值。

如果一个应用要求提权,应该反过来问:它为什么需要?是镜像设计问题,还是它确实是系统级组件?

多数 Web API、worker、前端服务,都不应该需要提权。

readOnlyRootFilesystem:根文件系统只读

readOnlyRootFilesystem: true 会把容器根文件系统设为只读。

1
2
securityContext:
readOnlyRootFilesystem: true

这样应用不能随意往镜像层路径写文件。

1
2
3
/app       只读
/usr 只读
/etc 只读

如果应用需要写临时文件,可以显式挂载 Volume:

1
2
3
4
5
6
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

这样边界更清楚:

1
2
3
4
5
镜像内容
只读

需要写的目录
明确挂载

这会逼着应用把“程序文件”和“运行时数据”分开。

capabilities:少给额外能力

Linux capabilities 可以把 root 的一些能力拆开授权。Kubernetes 里可以给容器添加或移除 capabilities。

常见安全写法是先丢掉所有额外能力:

1
2
3
4
securityContext:
capabilities:
drop:
- ALL

如果确实需要,再明确加回来:

1
2
3
4
5
6
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

NET_BIND_SERVICE 常用于允许进程绑定低端口,例如 80。

我更倾向把 capabilities 当成精细工具,而不是默认全部保留。普通应用通常不需要很多额外能力。

privileged:尽量不要开

privileged: true 会给容器很高的权限。

1
2
securityContext:
privileged: true

这通常只适合少数基础设施组件,比如某些节点级 agent、网络或存储插件。

对普通业务应用来说,它太宽了。

1
2
3
4
5
普通 Web 服务
不应该 privileged

节点级系统组件
可能需要,但要单独评估

如果某个业务容器必须 privileged 才能跑,我会先怀疑镜像或应用设计,而不是直接放行。

seccomp、AppArmor 和 SELinux

SecurityContext 还能配置更底层的安全机制:

1
2
3
4
5
6
7
8
seccomp
限制系统调用

AppArmor
使用程序安全配置文件限制行为

SELinux
使用安全标签控制访问

这些能力依赖节点操作系统和集群环境支持。

一个常见 seccomp 写法:

1
2
3
securityContext:
seccompProfile:
type: RuntimeDefault

可以理解成:使用容器运行时提供的默认 seccomp 配置,不让容器拥有完全不受限制的系统调用面。

这一层细节很多,入门阶段我先建立一个方向:普通业务应用尽量用默认受限策略,只有确实需要时再放开。

Pod Security Standards

Kubernetes 现在有 Pod Security Standards,分成三个层级:

1
2
3
4
5
6
7
8
Privileged
最宽松,适合受信任的系统级工作负载

Baseline
阻止已知提权方式,适合很多普通场景

Restricted
更严格,接近加固后的最佳实践

Pod Security Admission 可以在 Namespace 级别执行这些标准。

例如给 Namespace 打标签:

1
2
3
4
metadata:
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest

这表示在这个 Namespace 里创建 Pod 时,要按 restricted 策略检查。

这里需要注意版本背景:PodSecurityPolicy 已经在 Kubernetes v1.25 移除,现在更应该看 Pod Security Admission 或第三方策略引擎。

一个相对收敛的业务容器示例

下面是我会考虑作为普通 Web 服务起点的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-api
spec:
replicas: 2
selector:
matchLabels:
app: demo-api
template:
metadata:
labels:
app: demo-api
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: demo-api
image: example/demo-api:1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

这不是所有应用的万能模板,但它表达了几个方向:

1
2
3
4
5
6
非 root
不提权
少 capabilities
根文件系统只读
需要写的目录显式挂载
使用 RuntimeDefault seccomp

如果应用因此跑不起来,我会优先改镜像和文件权限,而不是立刻删掉安全限制。

排查安全上下文问题

SecurityContext 配得更严格后,常见问题是权限不足。

例如:

1
2
3
Permission denied
Read-only file system
container has runAsNonRoot and image will run as root

排查时我会看:

1
2
kubectl describe pod <pod-name>
kubectl logs <pod-name>

常见方向:

  • 镜像默认用户是不是 root;
  • 应用是否写了只读路径;
  • 需要写的目录有没有挂载 Volume;
  • 文件 UID/GID 是否和 runAsUser 对得上;
  • 是否真的需要某个 capability。

这类问题表面上是 Pod 起不来,实际是在帮我发现镜像和运行时边界没有设计好。

小结

这一篇整理下来,我对 SecurityContext 的理解可以概括成几句话:

  • RBAC 管 API 权限,SecurityContext 管容器运行权限;
  • SecurityContext 可以写在 Pod 层或容器层;
  • 普通业务容器尽量非 root 运行;
  • allowPrivilegeEscalation: false 可以避免进程提权;
  • readOnlyRootFilesystem: true 能让镜像内容保持只读;
  • capabilities 应该按需添加,默认尽量少;
  • privileged: true 只适合少数系统级组件;
  • Pod Security Standards 提供了 Privileged、Baseline、Restricted 三个安全层级;
  • PodSecurityPolicy 已在 Kubernetes v1.25 移除,应关注 Pod Security Admission 或策略引擎。

下一篇准备整理 NetworkPolicy。SecurityContext 管单个容器权限,NetworkPolicy 要解决 Pod 之间网络访问能不能被限制的问题。

参考资料

 评论