概述
Kubernetes 默认配置的安全性可以用一个词概括:开放。默认 ServiceAccount 拥有集群内几乎所有 API 的访问权限(取决于版本和 PSP 配置),Pod 以 root 用户运行,容器可以挂载宿主机文件系统,网络全通。这种"默认开放"的设计降低了上手门槛,但在生产环境中是巨大的安全隐患。
从 RBAC 权限模型、ServiceAccount 管理、Pod 安全标准、SecurityContext、网络策略到审计日志,详细梳理 K8s 安全加固的实践方法。
本文基于 Kubernetes v1.30。参考 Kubernetes 安全文档
RBAC 权限模型
RBAC 四个核心对象
| 对象 | 作用域 | 功能 |
|---|---|---|
Role | 命名空间 | 定义命名空间内的权限 |
ClusterRole | 集群 | 定义集群范围或命名空间内的权限 |
RoleBinding | 命名空间 | 将 Role/ClusterRole 绑定到用户/组/SA |
ClusterRoleBinding | 集群 | 将 ClusterRole 绑定到用户/组/SA |
核心关系:
用户/组/ServiceAccount ──Binding──→ Role/ClusterRole ──包含──→ 权限规则
Role 与 ClusterRole
# Role:命名空间级别的权限,只影响指定 namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
resourceNames: ["app-config"] # 限制只能访问特定 ConfigMap
# ClusterRole:集群级别的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
经验法则:如果权限只涉及命名空间内资源(Pod、Service、ConfigMap 等),用
Role;如果涉及集群范围资源(Node、Namespace、PV 等)或需要在多个命名空间复用,用ClusterRole。即使权限是命名空间级别的,也推荐用ClusterRole+RoleBinding的组合,方便复用。
RoleBinding 与 ClusterRoleBinding
# RoleBinding:将 ClusterRole 绑定到指定命名空间的 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: pod-reader-binding
subjects:
- kind: ServiceAccount
name: myapp-sa
namespace: production
roleRef:
kind: ClusterRole # 引用 ClusterRole,但权限范围被 RoleBinding 限制在 production 命名空间
name: pod-reader # ClusterRole 名
apiGroup: rbac.authorization.k8s.io
# ClusterRoleBinding:集群范围绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-admin-binding
subjects:
- kind: User
name: admin@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
verbs 详解
| Verb | 含义 | 风险等级 |
|---|---|---|
get | 获取单个资源 | 低 |
list | 获取资源列表 | 低 |
watch | 监听资源变更 | 低 |
create | 创建资源 | 中 |
update | 更新整个资源 | 中 |
patch | 部分更新 | 中 |
delete | 删除资源 | 高 |
deletecollection | 批量删除 | 高 |
* | 所有操作 | 极高 |
安全红线:尽量避免在生产环境给普通 SA 绑定
*权限。即使需要宽泛权限,也应明确列出所需 verbs。
常见 RBAC 配置模式
模式一:只读审计角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-viewer
rules:
- apiGroups: ["", "apps", "batch", "extensions"]
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec", "pods/portforward"]
verbs: [] # 显式拒绝 exec 和端口转发
模式二:CI/CD 部署角色
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: deployer
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "delete"] # 可以删 Pod 触发滚动更新
- apiGroups: [""]
resources: ["services", "configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "update", "patch"]
模式三:命名空间管理员
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-a
name: namespace-admin
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# 注意:Role 的 * 只影响当前命名空间,不影响集群级资源
使用 impersonate 进行权限验证
在授予 RBAC 权限前,可以用 --as 参数模拟目标用户验证权限是否正确:
# 模拟 SA 验证权限
kubectl auth can-i --list \
--as=system:serviceaccount:production:myapp-sa \
-n production
# 模拟 SA 验证特定操作
kubectl auth can-i delete pods \
--as=system:serviceaccount:production:myapp-sa \
-n production
# 批量检查敏感权限
for verb in create delete deletecollection patch update; do
for resource in pods secrets configmaps deployments; do
result=$(kubectl auth can-i $verb $resource \
--as=system:serviceaccount:production:myapp-sa \
-n production 2>/dev/null)
if [ "$result" = "yes" ]; then
echo "[WARN] myapp-sa can $verb $resource"
fi
done
done
ServiceAccount 管理
为什么不能用默认 ServiceAccount
每个命名空间都有一个名为 default 的 ServiceAccount,Pod 如果不指定 SA 就会自动使用它。问题在于:
- 多个 Pod 共享同一个
defaultSA,无法做细粒度权限控制 defaultSA 的 token 会自动挂载到 Pod 的/var/run/secrets/kubernetes.io/serviceaccount/,应用代码可以直接访问 K8s API- 一旦某个 Pod 被攻破,攻击者可以用
defaultSA 的 token 操作集群
为每个工作负载创建独立 SA
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: production
annotations:
description: "MyApp backend service account"
automountServiceAccountToken: false # 如果应用不需要访问 K8s API,设为 false
# Pod 中引用 SA
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
serviceAccountName: myapp-sa
automountServiceAccountToken: true # 覆盖 SA 级别的设置
containers:
- name: app
image: myapp:v1
ServiceAccount Token 管理演进
K8s v1.24 起,ServiceAccount 的 token 不再自动生成 Secret,而是通过 TokenRequest API 按需生成带过期时间的 token:
# 为 SA 生成临时 token(默认1小时过期)
kubectl create token myapp-sa -n production
# 指定有效期
kubectl create token myapp-sa -n production --duration=24h
如果需要长期 token(如 CI/CD 场景),可以手动创建 Secret:
apiVersion: v1
kind: Secret
metadata:
name: myapp-sa-token
namespace: production
annotations:
kubernetes.io/service-account.name: myapp-sa
type: kubernetes.io/service-account-token
SA 权限审计
# 查看某个 SA 的所有绑定
kubectl get rolebindings,clusterrolebindings -A -o json | \
jq '.items[] | select(.subjects[]? | select(.kind=="ServiceAccount" and .name=="myapp-sa" and .namespace=="production")) | .metadata.name'
# 查看 SA 可以做什么
kubectl auth can-i --list --as=system:serviceaccount:production:myapp-sa -n production
# 检查 SA 是否有敏感权限
kubectl auth can-i --list --as=system:serviceaccount:production:myapp-sa -n production | \
grep -E "delete|exec|create.*secrets"
Pod Security Standards
三个安全级别
K8s v1.25 起,Pod Security Standards(PSS)替代了已废弃的 Pod Security Policies(PSP)。PSS 定义了三个安全级别:
| 级别 | 说明 | 适用场景 |
|---|---|---|
privileged | 不限制,最大权限 | 系统组件、特殊场景 |
baseline | 防止已知提权,允许部分非特权配置 | 一般应用 |
restricted | 最严格,遵循最佳安全实践 | 生产环境应用 |
命名空间级别配置
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# 强制当前命名空间使用 restricted 策略
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# 审计模式:违规只记录日志,不拒绝
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
# 告警模式:违规发出告警事件
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
| 模式 | 行为 | 适用阶段 |
|---|---|---|
enforce | 违规 Pod 被拒绝创建/更新 | 已加固完成 |
audit | 允许创建,但记录审计日志 | 加固迁移中 |
warn | 允许创建,但返回告警信息 | 加固迁移中 |
推荐迁移策略:先 warn + audit 收集违规清单 → 逐个修复 → 切换到 enforce。
restricted 级别要求
restricted 级别强制要求以下配置:
spec:
securityContext:
runAsNonRoot: true # 必须非 root 运行
runAsUser: 1000 # 必须指定非 0 的 UID
fsGroup: 2000 # 必须指定 fsGroup
seccompProfile:
type: RuntimeDefault # 必须使用默认 seccomp profile
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # 禁止提权
readOnlyRootFilesystem: true # 只读根文件系统
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL # 必须丢弃所有 Linux capabilities
add:
- NET_BIND_SERVICE # 按需添加
restricted 级别的完整限制清单
| 限制项 | 要求 | 原因 |
|---|---|---|
privileged | 必须 false | privileged 容器等于宿主机 root |
hostNetwork | 必须 false | 不能共享宿主机网络栈 |
hostPID | 必须 false | 不能查看宿主机进程 |
hostIPC | 必须 false | 不能共享宿主机 IPC |
hostPath | 禁止使用 | 不能挂载宿主机文件系统 |
runAsNonRoot | 必须 true | 禁止 root 运行 |
runAsUser | 必须 > 0 | 非 root UID |
capabilities.drop | 必须包含 ALL | 丢弃所有 capabilities |
seccompProfile | 必须 RuntimeDefault 或 Localhost | 限制系统调用 |
allowPrivilegeEscalation | 必须 false | 防止 setuid 提权 |
SecurityContext
Pod 级别与容器级别
SecurityContext 可以在 Pod 和 Container 两个级别配置,容器级别覆盖 Pod 级别:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
# ===== Pod 级别 =====
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
fsGroupChangePolicy: "OnRootMismatch"
seccompProfile:
type: RuntimeDefault
sysctls:
- name: net.core.somaxconn
value: "4096"
containers:
- name: app
image: myapp:v1
securityContext:
# ===== Container 级别(覆盖 Pod 级别)=====
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
privileged: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp # 只读根文件系统需要可写目录
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
关键安全配置详解
| 配置 | 默认值 | 生产建议 | 说明 |
|---|---|---|---|
privileged | false | 必须 false | privileged 容器等于宿主机 root |
allowPrivilegeEscalation | true | 必须 false | 防止 setuid 提权 |
runAsNonRoot | 未设置 | 必须 true | 禁止 root 运行 |
runAsUser | 未设置 | 指定非0 UID | 明确运行用户 |
readOnlyRootFilesystem | false | 建议 true | 只读根文件系统,减少攻击面 |
capabilities.drop | 未设置 | drop ALL | 丢弃所有 capabilities |
seccompProfile | 未设置 | RuntimeDefault | 限制系统调用 |
readOnlyRootFilesystem 的注意事项
设为 true 后,容器根文件系统只读。应用需要写入的目录必须挂载为 volume:
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir:
medium: Memory # tmpfs,内存支持
- name: logs
emptyDir: {}
- name: cache
emptyDir: {}
常见需要可写目录的场景:
| 路径 | 方案 |
|---|---|
/tmp | emptyDir + medium: Memory (tmpfs) |
/var/log | emptyDir 或 PVC |
/app/cache | emptyDir |
/app/uploads | PVC(需要持久化) |
Linux Capabilities 按需添加
丢弃所有 capabilities 后,某些应用可能需要特定能力:
| Capability | 用途 | 常见需要的服务 |
|---|---|---|
NET_BIND_SERVICE | 绑定 1024 以下端口 | Nginx、HAProxy(绑定 80/443) |
CHOWN | 修改文件属主 | 初始化容器 |
SETUID / SETGID | 切换用户 | 需要降权启动的服务 |
NET_RAW | 原始网络包 | 网络诊断工具(慎用) |
SYS_PTRACE | 跟踪进程 | 调试工具 |
网络策略
默认全通的问题
K8s 默认网络模型是全通的:任何 Pod 可以访问任何 Pod,任何 Pod 可以访问任何 Service。在微服务架构中,一个 Pod 被攻破就意味着攻击者可以扫描整个集群内部网络。
NetworkPolicy 基本语法
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # 选择命名空间内所有 Pod
policyTypes:
- Ingress
- Egress
# 不定义 ingress/egress 规则 = 全部拒绝
这是最重要的安全基线:先创建
default-deny-all策略拒绝所有流量,然后逐个放行需要的流量。
白名单模式
# 允许前端访问后端
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
---
# 允许后端访问数据库
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
---
# 允许所有 Pod 访问 DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
网络策略限制
| 限制 | 说明 |
|---|---|
| 需要 CNI 支持 | Calico、Cilium 支持,Flannel 默认不支持 |
| 只支持 L3/L4 | 不能基于 URL 路径过滤,需要 Service Mesh |
| 无默认拒绝 | 没有 NetworkPolicy 的命名空间默认全通 |
| 命名空间隔离需要标签 | namespaceSelector 依赖命名空间标签 |
# 给命名空间打标签
kubectl label namespace kube-system kubernetes.io/metadata.name=kube-system
允许出口流量到外部服务
# 允许后端 Pod 访问外部数据库
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-external-db
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.5.100/32 # 外部数据库 IP
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector: {} # 允许访问集群内所有命名空间(用于访问 kube-dns 等)
ports:
- protocol: UDP
port: 53
审计日志
启用审计日志
# /etc/kubernetes/audit/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived
rules:
# 不记录系统组件的 get/list/watch
- level: None
users: ["system:apiserver", "system:kube-controller-manager", "system:kube-scheduler"]
verbs: ["get", "list", "watch"]
# 记录 Secret 操作(完整记录请求体)
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# 记录所有写操作
- level: Request
verbs: ["create", "update", "patch", "delete"]
# 记录认证授权相关操作
- level: Metadata
resources:
- group: "rbac.authorization.k8s.io"
# 默认:只记录元数据
- level: Metadata
审计日志级别
| 级别 | 记录内容 | 日志量 |
|---|---|---|
None | 不记录 | 无 |
Metadata | 请求元数据(who/what/when) | 小 |
Request | 元数据 + 请求体 | 中 |
RequestResponse | 元数据 + 请求体 + 响应体 | 大 |
配置 API Server
# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
- --audit-log-format=json
volumeMounts:
- mountPath: /etc/kubernetes/audit
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit
name: audit-log
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: Directory
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate
审计日志分析
# 查找谁删除了 Pod
cat /var/log/kubernetes/audit/audit.log | \
jq 'select(.verb=="delete" and .objectRef.resource=="pods") | \
{user: .user.username, ns: .objectRef.namespace, pod: .objectRef.name, time: .requestReceivedTimestamp}'
# 查找谁访问了 Secret
cat /var/log/kubernetes/audit/audit.log | \
jq 'select(.objectRef.resource=="secrets") | \
{user: .user.username, ns: .objectRef.namespace, secret: .objectRef.name, verb: .verb}'
# 统计各用户的 API 调用次数
cat /var/log/kubernetes/audit/audit.log | \
jq -r '.user.username' | sort | uniq -c | sort -rn
# 查找失败的请求
cat /var/log/kubernetes/audit/audit.log | \
jq 'select(.responseStatus.code >= 400) | \
{user: .user.username, verb: .verb, resource: .objectRef.resource, code: .responseStatus.code}'
安全加固检查清单
RBAC
- 没有使用
defaultSA 运行工作负载 - 每个工作负载有独立的 ServiceAccount
- SA 的 token 默认不自动挂载(
automountServiceAccountToken: false) - 没有
cluster-admin权限的 SA - 定期审计 RBAC 绑定,清理不再需要的权限
- CI/CD 使用的 SA 权限限制在部署命名空间内
Pod Security
- 生产命名空间配置
restrictedPSS - 所有 Pod 以非 root 用户运行
- 所有容器
allowPrivilegeEscalation: false - 所有容器
readOnlyRootFilesystem: true - 所有容器
capabilities.drop: [ALL] - 配置 seccompProfile
网络策略
- 每个命名空间有
default-deny-all策略 - 按白名单模式逐个放行流量
- DNS 流量已放行
- CNI 支持 NetworkPolicy
审计与监控
- API Server 启用审计日志
- Secret 操作记录在审计日志中
- 审计日志定期归档和分析
- 异常 API 调用有告警
总结
K8s 安全加固不是一次性工作,而是一个持续的过程。核心原则是最小权限原则(Principle of Least Privilege)——任何身份(SA、用户)只授予完成其功能所需的最小权限。
实施路径建议:
- 第一步:为所有工作负载创建独立 SA,关闭默认 token 挂载。这是成本最低、收益最高的安全措施。
- 第二步:在命名空间上配置 PSS,先从
warn+audit模式开始,收集违规清单后逐个修复。 - 第三步:配置 NetworkPolicy,先
default-deny-all再逐步放行。这一步需要确认 CNI 支持。 - 第四步:开启审计日志,建立安全事件的监控和告警能力。
- 第五步:定期安全审计,使用
kubectl auth can-i --list检查权限蔓延,清理冗余权限。
安全没有银弹,但每一层防护都在提高攻击者的成本。多层防御(Defense in Depth)的核心思想是:不依赖单一安全机制,RBAC、PSS、NetworkPolicy、审计日志各司其职,共同构成纵深防御体系。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Kubernetes 安全文档 — Kubernetes 官方,参考了Kubernetes 安全文档相关内容