安全上下文与网络策略
本教程共 65 篇 · 第 47 篇 · 更新于 2026-08-14 · 约 24 分钟阅读
本节目标:把 Pod 加固的两条主线串起来——
securityContext管”这个容器自己能干什么”,NetworkPolicy管”这个 Pod 能跟谁说话”。
前面三章讲的是谁能操作 Kubernetes。这一章讲的是Pod 跑起来之后自身有多大权力。两件事,两套机制。
安全上下文(第 16 章)和网络策略(第 30 章)你已经见过基础用法了。这一章不重复语法细节,而是把它们放到”加固”的视角下汇总——哪些配置真正值得配、配错了会怎样、组合起来怎么用。
47-1 加固的核心思路:假设容器会被拿下
安全设计不能假设”我的应用没漏洞”。合理的假设是:攻击者已经在你的某个容器里拿到了 shell。
那么接下来该问的是:
- 他在容器里是什么身份?root 还是普通用户?
- 他能不能提权到宿主机?
- 他能改容器的文件系统吗?
- 他能从这个容器连到数据库、连到别的服务、连到外网吗?
securityContext 回答前三个问题,NetworkPolicy 回答第四个。
47-2 securityContext 加固清单
securityContext 可以配在两个层级:
- Pod 级(
spec.securityContext)——对 Pod 内所有容器生效。 - 容器级(
spec.containers[*].securityContext)——只对该容器生效,会覆盖 Pod 级同名设置。
有些字段只在 Pod 级有(如 fsGroup),有些只在容器级有(如 capabilities、readOnlyRootFilesystem、privileged)。记不住就查一下,不用背。
下面这份是我认为最值得配的几项,按重要性排:
不要以 root 运行
这是投入产出比最高的一条。
spec:
securityContext:
runAsNonRoot: true # 强制非 root,否则 Pod 起不来
runAsUser: 10001 # 指定 UID
runAsGroup: 10001 # 指定 GID
runAsNonRoot: true 是个校验开关——kubelet 会检查最终用户是不是 root,是就拒绝启动容器。runAsUser 是指定具体 UID。两个一起配最稳妥。
Warning只写
runAsNonRoot: true而镜像里没有非 root 用户,Pod 会卡在CreateContainerConfigError,报错container has runAsNonRoot and image will run as root。这不是配置错误,是镜像本身的问题。要么改 Dockerfile 加USER,要么用runAsUser强行指定一个 UID(但要确保应用对文件系统的权限够用)。
多容器 Pod 建议给不同容器配不同 UID,避免它们互相干扰彼此的文件。
禁止权限提升
Linux 允许子进程申请比父进程更多的权限(通过 setuid 之类的机制)。关掉它:
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
这一项是 PSS restricted 级别的硬要求,而且几乎没有副作用。默认就该配上。
丢弃所有权能,按需加回
Linux 把 root 的权力拆成了三十来个权能(Capability)。比如 SYS_TIME 允许改系统时钟,NET_ADMIN 允许改路由表和网卡配置。root 拥有全部权能。
容器默认会拿到一批权能,其中不少你根本用不上。全丢掉:
containers:
- name: app
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # 只在确实需要绑 1024 以下端口时加
NET_BIND_SERVICE 是唯一被 PSS restricted 允许加回的权能——它让非 root 进程能绑定 80、443 这类特权端口。
Tip很多人为了让 Nginx 监听 80 端口就给容器加
NET_BIND_SERVICE。其实更干净的做法是让容器监听 8080,让 Service 把 80 映射过去。容器内的端口号跟外部访问的端口号本来就不需要一致。
绝对不要 privileged
# 反面教材,别学
securityContext:
privileged: true
privileged: true 基本等于把容器隔离全部关掉。容器里的 root 就是宿主机的 root,能访问所有设备,能挂载文件系统。拿下这个容器就等于拿下这台节点。
真正需要它的场景极少(某些存储驱动、某些网络插件)。业务应用一律不需要。PSS 的 baseline 级别就会挡掉它。
根文件系统设为只读
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
只读根文件系统挡住了一大类攻击:攻击者进来后写不了脚本、装不了工具、改不了二进制。
代价是你得把应用真正需要写的目录用 emptyDir 挂出来。/tmp 基本是必须的,剩下的看应用——Nginx 要 /var/cache/nginx 和 /var/run,Java 应用可能要临时目录。
Note排查方法:先不设只读,跑起来,用
kubectl exec进去看哪些目录被写了。或者直接设成只读,看应用报什么 permission denied,一个个补上去。第一次配比较费时间,但配完之后就一直有效。
Seccomp:限制系统调用
Seccomp 是 Linux 内核从 2.6.12 就有的特性,能沙箱化进程权限,限制它从用户态发起哪些系统调用。
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
RuntimeDefault 用容器运行时(containerd / CRI-O)提供的默认配置,屏蔽了一批危险系统调用。这是 PSS restricted 的要求,配上基本无痛。
想更细就用 type: Localhost 加 localhostProfile 指向节点上的自定义配置文件,但那需要在每个节点铺文件,运维成本高。
卷的属主:fsGroup
以非 root 运行之后经常撞上这个问题:挂载的持久卷是 root 拥有的,应用写不进去。
spec:
securityContext:
fsGroup: 10001
fsGroupChangePolicy: "OnRootMismatch"
fsGroup 让 kubelet 把卷的属主组改成指定 GID。fsGroupChangePolicy: "OnRootMismatch" 表示只在根目录属主不匹配时才递归改权限——大卷上这个优化很关键,不然每次挂载都要遍历几十万个文件,Pod 启动能卡好几分钟。
一份完整的加固模板
把上面这些拼起来,这就是能通过 PSS restricted 的标准配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hardened-app
spec:
replicas: 2
selector:
matchLabels:
app: hardened-app
template:
metadata:
labels:
app: hardened-app
spec:
automountServiceAccountToken: false # 不需要访问 API
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginxinc/nginx-unprivileged:1.27
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
把这个当模板存下来,新应用从它开始改,比事后加固省事得多。
47-3 NetworkPolicy:东西向流量隔离
securityContext 管容器内部,NetworkPolicy 管容器之间。默认集群是扁平网络,任意 Pod 能访问任意其他 Pod——攻击者拿下一个前端容器就能横向扫描、直连数据库。
完整语法第 30 章已讲过(30-2 CNI 前置、30-3 双方向、30-5 选择器、30-6 默认拒绝),这里只做要点回顾。
要点回顾
- 大前提:NetworkPolicy 由 CNI 实现,Calico、Cilium、Antrea 支持,Flannel 不支持——不报错、不生效。
- 白名单模型:只有允许、没有拒绝;策略相加取并集;连接要通,源端出口与目标端入口必须同时放行。
- 默认拒绝是基线:
podSelector: {}选中整个命名空间,policyTypes声明隔离方向;默认拒绝出站会连 DNS 一起拦掉,须单独放行 kube-dns 的 53(TCP+UDP)。 - 选择器语义:
from/to数组里少一个-,AND 变 OR,权限范围放大。 - 按命名空间选要用
kubernetes.io/metadata.name标签;访问集群外用ipBlock;端口范围用endPort。
与 securityContext 的配合
同一场攻击的两个环节:securityContext 管”容器被拿下后能干什么”,NetworkPolicy 管”容器被拿下后能连到哪里”。
- 先收紧容器,再收紧网络。 默认拒绝上线前先配好 securityContext,否则连不上时分不清是权限还是网络问题。
- NetworkPolicy 只管集群内。 拦不住出集群的流量——攻击者外连 C2 要靠云安全组或 CNI 出口网关能力。
- 三层配合。 PSA(第 46 章)强制配置、securityContext 限制容器能力、NetworkPolicy 限制可达性,缺一层都可能被绕过。
默认拒绝的运维建议与排查清单
- 先在测试命名空间试运行,确认放行规则齐全(尤其 DNS)再推生产。
- 逐步收紧:逐命名空间、逐应用推进,配合流量观察(Cilium flow log、Calico 策略审计)确认没误伤再继续。
- 排查清单(命令见 47-5):① CNI 是否支持、策略是否生效;② 策略是否选中目标 Pod(
kubectl describe networkpolicy);③ 源端出口和目标端入口两端都查;④ 临时调试 Pod 标签是否与业务 Pod 一致。
完整语法、示例与调试方法详见第 30 章。
47-4 两条主线怎么配合
securityContext 和 NetworkPolicy 解决的是同一场攻击的不同环节:
| 攻击环节 | 防御手段 |
|---|---|
| 在容器内提权到 root | runAsNonRoot、allowPrivilegeEscalation: false |
| 从容器逃逸到宿主机 | 禁 privileged、drop ALL 权能、Seccomp |
| 在容器内植入工具 | readOnlyRootFilesystem: true |
| 偷取 API 凭据 | automountServiceAccountToken: false |
| 横向扫描其他 Pod | NetworkPolicy 默认拒绝 |
| 直连数据库 | NetworkPolicy 限定 tier 标签 |
| 反向连接外部 C2 | NetworkPolicy 限制出站 |
还有一层在上面:用 PSA(第 46 章)把 securityContext 的要求变成命名空间级强制。不然全靠开发自觉写 securityContext,漏一个就是漏一个。
三者的分工是这样的:
- PSA 在命名空间层面强制”必须配加固字段”。
securityContext是具体配置,满足 PSA 的要求。NetworkPolicy独立地限制网络可达性。
47-5 排查与验证
验证 securityContext 生效了:
# 看容器内的用户
kubectl exec -it hardened-app-xxx -- id
# uid=10001 gid=10001
# 试着写根文件系统,应该失败
kubectl exec -it hardened-app-xxx -- touch /test
# touch: /test: Read-only file system
# 看容器实际拿到的权能
kubectl exec -it hardened-app-xxx -- cat /proc/1/status | grep Cap
验证 NetworkPolicy:
# 起个临时调试 Pod 试连通性
kubectl run netshoot --rm -it \
--image=nicolaka/netshoot -- /bin/bash
# 容器内:
# nc -zv backend-svc 8080
# nslookup backend-svc
# 看某个 Pod 被哪些策略选中
kubectl get networkpolicy -n production -o yaml
# describe 能看到策略解析后的规则
kubectl describe networkpolicy backend-policy -n production
Tip用临时 Pod 测试时注意:它的标签和你的业务 Pod 不一样,所以适用的策略也不一样。要模拟真实情况,给临时 Pod 打上和业务 Pod 相同的标签,用
kubectl run --labels="tier=frontend"。
47-6 加固优先级建议
一次全上容易搞砸。按这个顺序推进比较稳:
automountServiceAccountToken: false——零风险,改一行,先全铺。allowPrivilegeEscalation: false+capabilities.drop: ["ALL"]——绝大多数应用无感。seccompProfile.type: RuntimeDefault——基本无感。runAsNonRoot+runAsUser——可能要换镜像或调文件权限,逐个应用推。readOnlyRootFilesystem: true——要摸清写入目录,最费时间,放最后。NetworkPolicy默认拒绝——先在测试命名空间试,一定先加 DNS 放行。
每一步之后跑一遍完整回归。别指望一次改完不出问题。
小结
- 加固的正确假设是”攻击者已经进来了”,然后限制他能干什么。
securityContext加固清单:非 root、禁提权、drop ALL 权能、只读根文件系统、Seccomp RuntimeDefault、fsGroup处理卷权限。- 绝对不要
privileged: true。 NetworkPolicy需要 CNI 支持,否则静默不生效。- 两种隔离方向独立声明;策略相加不冲突;连接要通两端都得放行。
- 默认拒绝出站会拦掉 DNS,必须单独放行 53 的 TCP + UDP。
from/to数组里少一个-,AND就变成OR,权限范围会大幅放宽。securityContext是配置,PSA 是强制手段,NetworkPolicy是网络侧,三层配合。
安全部分到这里结束。下一章进入弹性与发布——先讲 HPA 自动扩缩。