【K8S】Kubernetes RBAC与安全体系全解析:从ServiceAccount到OPA Gatekeeper的生产级安全实践
本文系统性地剖析 Kubernetes 安全体系的核心组件,涵盖认证授权链路、RBAC 权限模型、ServiceAccount 机制演进、Pod Security Standards、Secrets 管理以及 OPA Gatekeeper 策略引擎,结合大量生产级 YAML 配置与实战场景,帮助读者构建完整的集群安全防护体系。
一、Kubernetes 安全体系概览
1.1 请求处理的三阶段安全模型
每一个到达 kube-apiserver 的请求,都必须经过三道安全关卡:
客户端请求 → Authentication(认证) → Authorization(授权) → Admission Control(准入控制) → 持久化到 etcd
第一阶段:认证(Authentication)—— “你是谁?”
认证阶段负责确认请求者身份。Kubernetes 支持多种认证方式,且可同时启用多个认证器,任一认证成功即通过:
| 认证方式 | 适用场景 | 说明 |
|---|---|---|
| X509 客户端证书 | 集群管理员、组件间通信 | 最常见,kubeconfig 默认使用 |
| ServiceAccount Token | Pod 内应用访问 API | 自动挂载到 Pod 中 |
| OIDC(OpenID Connect) | 企业 SSO 集成 | 对接 Keycloak、Dex、Azure AD 等 |
| Webhook Token Authentication | 自定义认证服务 | 将 Token 验证委托给外部服务 |
| Bootstrap Token | 节点加入集群 | kubeadm 引导阶段使用 |
第二阶段:授权(Authorization)—— “你能做什么?”
认证通过后,授权模块判定该用户是否有权执行本次操作:
| 授权模式 | 说明 |
|---|---|
| RBAC | 基于角色的访问控制,生产标准 |
| ABAC | 基于属性的访问控制,需重启 apiserver 更新策略,已不推荐 |
| Node | 专用于 kubelet 的授权,限制节点只能访问自身关联资源 |
| Webhook | 将授权决策委托给外部 HTTP 服务 |
第三阶段:准入控制(Admission Control)—— “资源是否合规?”
即使授权通过,准入控制器仍然可以修改(Mutating)或拒绝(Validating)请求:
- MutatingAdmissionWebhook:修改资源(如注入 sidecar)
- ValidatingAdmissionWebhook:校验资源(如强制设置 resource limits)
- Pod Security Admission:强制 Pod 安全标准
1.2 为什么 RBAC 是生产标准
RBAC 相比其他授权模式的核心优势:
- 声明式管理:通过 Kubernetes 原生资源(Role/ClusterRole/Binding)管理权限,无需重启 apiserver
- 细粒度控制:可精确到 API Group、Resource、Verb、甚至具体 resourceName
- namespace 隔离:天然支持多租户场景
- 可审计:所有权限配置均为 API 对象,可通过
kubectl查询和版本管理 - 社区生态:几乎所有 K8s 工具和平台都围绕 RBAC 设计
自 Kubernetes 1.8 起,RBAC 成为默认授权模式。确认集群是否启用:
# 检查 apiserver 启动参数
kubectl cluster-info dump | grep authorization-mode
# 输出应包含: --authorization-mode=Node,RBAC
二、ServiceAccount 详解
2.1 默认 ServiceAccount
每个 namespace 创建时,Kubernetes 会自动创建一个名为 default 的 ServiceAccount:
$ kubectl get sa -n default
NAME SECRETS AGE
default 0 365d
$ kubectl get sa -n kube-system
NAME SECRETS AGE
default 0 365d
coredns 0 365d
kube-proxy 0 365d
如果 Pod 未显式指定 ServiceAccount,则自动使用该 namespace 的 default SA。
2.2 SA 与 Pod 的关系
Pod 创建时,kubelet 会自动将 ServiceAccount 的 Token 投射到容器文件系统中:
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: production
spec:
serviceAccountName: my-app-sa # 指定使用的 SA
automountServiceAccountToken: true # 默认为 true
containers:
- name: app
image: my-app:v1.0
2.3 Token 挂载位置与内容
Token 被挂载到 Pod 内的固定路径 /var/run/secrets/kubernetes.io/serviceaccount/,包含三个文件:
$ kubectl exec -it my-app -- ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt # 集群 CA 证书,用于验证 apiserver 的 TLS 证书
namespace # 当前 Pod 所在的 namespace 名称
token # ServiceAccount 的 JWT Token
应用可使用这些凭证访问 Kubernetes API:
# 在 Pod 内部访问 apiserver
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
curl --cacert $CACERT \
-H "Authorization: Bearer $TOKEN" \
"https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods"
2.4 Kubernetes 1.24+ 的重大变化
1.24 之前:创建 SA 时自动创建一个永不过期的 Secret(类型为 kubernetes.io/service-account-token),Token 永久有效,存在严重安全风险。
1.24 及之后:
- 不再自动创建永久 Secret
- Pod 中的 Token 通过 TokenRequest API 动态签发,具有以下特性:
- 有限生命周期:默认 1 小时,自动轮转
- 绑定受众(Audience):Token 只对特定 API server 有效
- 绑定对象引用:Token 与 Pod 绑定,Pod 删除则 Token 失效
# 如果确实需要长期 Token(不推荐),必须手动创建:
apiVersion: v1
kind: Secret
metadata:
name: my-sa-token
annotations:
kubernetes.io/service-account.name: my-app-sa
type: kubernetes.io/service-account-token
推荐方式 —— 使用 TokenRequest API 获取短时 Token:
# 签发一个有效期为 600 秒的 Token
kubectl create token my-app-sa --duration=600s -n production
2.5 创建自定义 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: cicd-deployer
namespace: production
labels:
app.kubernetes.io/managed-by: platform-team
annotations:
description: "CI/CD pipeline 专用服务账号"
---
# 在 Pod 中使用
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-agent
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: deploy-agent
template:
metadata:
labels:
app: deploy-agent
spec:
serviceAccountName: cicd-deployer
automountServiceAccountToken: true
containers:
- name: agent
image: deploy-agent:v2.1
安全建议:对于不需要访问 API 的 Pod,显式禁用 Token 挂载:
apiVersion: v1
kind: ServiceAccount
metadata:
name: no-api-access
namespace: production
automountServiceAccountToken: false # SA 级别禁用
三、RBAC 核心概念与资源
3.1 四种 RBAC 资源
RBAC 通过四种 API 资源实现权限管理:
┌─────────────────────────────────────────────────────────────┐
│ Namespace 范围 │
│ Role ──bindTo──→ RoleBinding ──referTo──→ User/Group/SA │
├─────────────────────────────────────────────────────────────┤
│ 集群范围 │
│ ClusterRole ──bindTo──→ ClusterRoleBinding ──referTo──→ … │
└─────────────────────────────────────────────────────────────┘
关键区别:
Role+RoleBinding:权限仅在指定 namespace 生效ClusterRole+ClusterRoleBinding:权限在整个集群生效ClusterRole+RoleBinding:可将集群角色"降级"为 namespace 级使用(复用角色定义)
3.2 RBAC 规则四要素
rules:
- apiGroups: [""] # API 组,"" 表示 core API group
resources: ["pods"] # 资源类型
verbs: ["get", "list"] # 允许的操作
resourceNames: ["my-pod"] # (可选)限制到特定资源名
apiGroups 常见值:
| apiGroups | 包含资源 |
|---|---|
"" (core) |
pods, services, configmaps, secrets, nodes, namespaces, persistentvolumeclaims |
apps |
deployments, statefulsets, daemonsets, replicasets |
batch |
jobs, cronjobs |
networking.k8s.io |
networkpolicies, ingresses |
rbac.authorization.k8s.io |
roles, clusterroles, rolebindings, clusterrolebindings |
policy |
poddisruptionbudgets |
verbs 完整列表:
| Verb | 说明 | 对应 HTTP 方法 |
|---|---|---|
get |
获取单个资源 | GET(具体资源) |
list |
列出资源集合 | GET(资源集合) |
watch |
监听资源变化 | GET(带 ?watch=true) |
create |
创建资源 | POST |
update |
全量更新资源 | PUT |
patch |
部分更新资源 | PATCH |
delete |
删除单个资源 | DELETE(具体资源) |
deletecollection |
批量删除资源 | DELETE(资源集合) |
特殊 verbs:
bind:用于 Role/ClusterRole 的绑定操作escalate:允许提升权限(创建超出自身权限的 Role)impersonate:允许模拟其他用户
3.3 完整 YAML 示例
只读角色 —— 开发人员查看 Pod 和日志:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: development
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/status"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
运维角色 —— 完整的应用管理权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ops-admin
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "persistentvolumeclaims"]
verbs: ["*"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["*"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["*"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"] # Secret 只给读权限
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"] # 节点只读
开发者角色 —— 自己 namespace 的部署管理:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: team-alpha
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/exec", "pods/portforward"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["apps"]
resources: ["deployments/scale"]
verbs: ["update", "patch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "update"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"] # 不允许创建/修改 Secret
3.4 聚合 ClusterRole
通过 aggregationRule 可以自动聚合带有特定 label 的 ClusterRole 规则,实现模块化权限管理:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-aggregate
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 规则由系统自动填充
---
# 子角色 1:Prometheus 指标查看
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-metrics
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
---
# 子角色 2:Event 查看
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-events
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch"]
四、RBAC 实战场景
场景1:为开发团队创建 namespace 级别只读权限
需求:开发团队 team-frontend 需要查看 staging namespace 中所有资源的能力,但不允许任何修改操作。
# 1. 创建 ClusterRole(可跨 namespace 复用)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-viewer
rules:
- apiGroups: ["", "apps", "batch", "networking.k8s.io"]
resources: ["*"]
verbs: ["get", "list", "watch"]
---
# 2. 创建 RoleBinding 将权限限定在 staging namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-frontend-viewer
namespace: staging
subjects:
- kind: Group
name: team-frontend # 对应 OIDC/证书中的 group
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-viewer
apiGroup: rbac.authorization.k8s.io
验证权限:
# 以 team-frontend 组成员身份测试
kubectl auth can-i list pods -n staging --as-group=team-frontend --as=developer1
# yes
kubectl auth can-i delete pods -n staging --as-group=team-frontend --as=developer1
# no
kubectl auth can-i list pods -n production --as-group=team-frontend --as=developer1
# no(权限仅在 staging namespace)
# 查看用户所有权限
kubectl auth can-i --list -n staging --as=developer1 --as-group=team-frontend
场景2:为 CI/CD Pipeline 创建部署权限
需求:GitLab CI/CD pipeline 需要能够更新 production namespace 中的 Deployment 镜像和 ConfigMap,但不能操作 Secret、不能删除资源。
# 1. 创建专用 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-ci-deployer
namespace: production
annotations:
description: "GitLab CI/CD 专用部署账号"
---
# 2. 创建精细化 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-deployer
namespace: production
rules:
# Deployment:允许查看和更新(滚动发布)
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "update", "patch"]
# Deployment Scale:允许扩缩容
- apiGroups: ["apps"]
resources: ["deployments/scale"]
verbs: ["get", "update", "patch"]
# Pod:只允许查看状态(用于部署后验证)
- apiGroups: [""]
resources: ["pods", "pods/status", "pods/log"]
verbs: ["get", "list", "watch"]
# ConfigMap:允许管理(配置热更新)
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "create", "update", "patch"]
# ReplicaSet:查看(用于 rollout 状态确认)
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get", "list", "watch"]
# Event:查看(排障用)
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list"]
---
# 3. 绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitlab-ci-deployer-binding
namespace: production
subjects:
- kind: ServiceAccount
name: gitlab-ci-deployer
namespace: production
roleRef:
kind: Role
name: ci-deployer
apiGroup: rbac.authorization.k8s.io
在 GitLab CI 中使用:
# 获取短期 Token
TOKEN=$(kubectl create token gitlab-ci-deployer -n production --duration=1800s)
# 配置 kubeconfig
kubectl config set-credentials ci-deployer --token=$TOKEN
kubectl config set-context ci-context \
--cluster=production-cluster \
--user=ci-deployer \
--namespace=production
# 执行部署(仅允许 set image,不允许 delete)
kubectl set image deployment/my-app \
app=my-registry.com/my-app:${CI_COMMIT_SHA} \
--context=ci-context
# 验证部署状态
kubectl rollout status deployment/my-app --context=ci-context --timeout=300s
场景3:多租户隔离
需求:三个团队(alpha、beta、gamma)各自拥有独立 namespace,互相不可见。
# 批量创建 namespace 和 RBAC(以 team-alpha 为例)
apiVersion: v1
kind: Namespace
metadata:
name: team-alpha
labels:
team: alpha
pod-security.kubernetes.io/enforce: baseline
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: team-alpha-admin
namespace: team-alpha
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: team-admin
namespace: team-alpha
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["*"]
verbs: ["*"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-admin-binding
namespace: team-alpha
subjects:
- kind: Group
name: team-alpha-members
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: team-alpha-admin
namespace: team-alpha
roleRef:
kind: Role
name: team-admin
apiGroup: rbac.authorization.k8s.io
---
# 网络策略:默认拒绝跨 namespace 流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-cross-namespace
namespace: team-alpha
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {} # 只允许同 namespace 内的 Pod
egress:
- to:
- podSelector: {}
- to: # 允许访问 DNS
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
场景4:限制用户只能查看自己 namespace 的资源
结合 OIDC + RBAC 实现:
# 快速为每个开发者创建独立 namespace 和权限绑定的脚本
#!/bin/bash
DEVELOPER=$1
NAMESPACE="dev-${DEVELOPER}"
# 创建 namespace
kubectl create namespace $NAMESPACE
# 添加标签
kubectl label namespace $NAMESPACE owner=$DEVELOPER
# 创建 RoleBinding(使用预定义的 ClusterRole)
kubectl create rolebinding ${DEVELOPER}-admin \
--clusterrole=admin \
--user=${DEVELOPER}@company.com \
--namespace=$NAMESPACE
# 验证
kubectl auth can-i --list --as=${DEVELOPER}@company.com -n $NAMESPACE
kubectl auth can-i list pods --as=${DEVELOPER}@company.com -n default
# 输出: no
五、Pod Security Standards (PSS) / Pod Security Admission (PSA)
5.1 PodSecurityPolicy 已废弃
PodSecurityPolicy(PSP)在 Kubernetes 1.21 标记为废弃,1.25 正式移除。其替代方案为内置的 Pod Security Admission(PSA) 控制器。
5.2 PSA 三个安全级别
| 级别 | 说明 | 典型场景 |
|---|---|---|
privileged |
无限制,允许所有配置 | 系统组件(kube-system)、需要特权的 DaemonSet |
baseline |
最小限制,阻止已知特权提升 | 大部分通用工作负载 |
restricted |
严格限制,遵循 Pod 安全最佳实践 | 安全敏感应用、多租户环境 |
5.3 三种执行模式
| 模式 | 行为 |
|---|---|
enforce |
违规 Pod 将被拒绝创建 |
audit |
违规行为记录到审计日志,但不拒绝 |
warn |
向用户发出警告信息,但不拒绝 |
5.4 配置方式:通过 Namespace Label 启用
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# 格式: pod-security.kubernetes.io/<模式>: <级别>
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.28 # 锁定版本
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
5.5 restricted 级别的具体限制
restricted 级别要求 Pod 必须满足:
# 一个满足 restricted 级别的 Pod 示例
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: production
spec:
securityContext:
runAsNonRoot: true # 必须:不以 root 运行
seccompProfile:
type: RuntimeDefault # 必须:启用 seccomp
containers:
- name: app
image: my-app:v1.0
securityContext:
allowPrivilegeEscalation: false # 必须:禁止特权提升
readOnlyRootFilesystem: true # 推荐:只读根文件系统
runAsNonRoot: true
capabilities:
drop:
- ALL # 必须:移除所有 capabilities
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
违规示例 —— 以下 Pod 会被 restricted 级别拒绝:
apiVersion: v1
kind: Pod
metadata:
name: violation-pod
namespace: production # 假设已设置 enforce: restricted
spec:
containers:
- name: bad-container
image: nginx:latest
securityContext:
privileged: true # 违规:特权容器
runAsUser: 0 # 违规:以 root 运行
allowPrivilegeEscalation: true # 违规:允许特权提升
ports:
- containerPort: 80
hostPort: 80 # 违规:使用 hostPort
hostNetwork: true # 违规:使用宿主机网络
hostPID: true # 违规:使用宿主机 PID namespace
提交后报错信息:
Error from server (Forbidden): error when creating "pod.yaml": pods "violation-pod" is
forbidden: violates PodSecurity "restricted:v1.28": privileged (container "bad-container"
must not set securityContext.privileged=true), allowPrivilegeEscalation != false (container
"bad-container" must set securityContext.allowPrivilegeEscalation=false), runAsNonRoot !=
true (pod or container "bad-container" must set securityContext.runAsNonRoot=true),
hostPort (container "bad-container" uses hostPort 80)
5.6 渐进式迁移策略
# 第一步:先用 warn 模式观察影响
kubectl label namespace production \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# 第二步:检查审计日志中的违规记录
# 修复所有违规工作负载
# 第三步:启用强制执行
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted
六、Secrets 安全管理最佳实践
6.1 Secret 类型
| 类型 | 用途 | 示例 |
|---|---|---|
Opaque |
通用键值对(默认) | 数据库密码、API Key |
kubernetes.io/dockerconfigjson |
镜像仓库凭证 | 私有仓库认证信息 |
kubernetes.io/tls |
TLS 证书 | HTTPS 证书和私钥 |
kubernetes.io/service-account-token |
SA Token | ServiceAccount 令牌 |
kubernetes.io/basic-auth |
基本认证 | 用户名和密码 |
6.2 Secret 的存储真相
重要认知:Kubernetes Secret 中的数据仅做了 base64 编码,并非加密!
# 创建 Secret
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='S3cr3t!P@ss'
# 直接解码即可获取明文
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
# 输出: S3cr3t!P@ss
这意味着:
- 任何有 Secret GET 权限的用户/SA 都能获取明文
- etcd 中存储的也是 base64 编码(除非启用加密)
- Secret 的 YAML 备份中包含明文
6.3 启用 etcd 加密
在 kube-apiserver 上配置 EncryptionConfiguration:
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps # 建议也加密 configmap
providers:
# 加密提供者(按顺序选择第一个用于加密,所有都可用于解密)
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key> # 生成: head -c 32 /dev/urandom | base64
- identity: {} # 回退:未加密数据仍可读取
应用配置:
# 1. 修改 apiserver manifest 添加参数
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
# 2. 重启 apiserver 后,重新加密已有 Secret
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
# 3. 验证加密是否生效(需要 etcd 访问权限)
ETCDCTL_API=3 etcdctl get /registry/secrets/default/db-credentials \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key | hexdump -C
# 输出应为加密后的二进制数据,而非明文
6.4 Secret 使用方式对比
方式一:环境变量(不推荐)
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
缺点:
- 进程的环境变量可通过
/proc/<pid>/environ读取 - 日志框架可能打印环境变量
- 子进程自动继承所有环境变量
- Secret 更新后 Pod 需重启
方式二:Volume 挂载(推荐)
apiVersion: v1
kind: Pod
metadata:
name: app-with-secrets
spec:
containers:
- name: app
image: my-app:v1.0
volumeMounts:
- name: secrets-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets-volume
secret:
secretName: db-credentials
defaultMode: 0400 # 文件权限: 只有 owner 可读
items:
- key: username
path: db-username # 映射为 /etc/secrets/db-username
- key: password
path: db-password # 映射为 /etc/secrets/db-password
优点:
- Secret 更新后自动同步到 Pod(kubelet 定期同步)
- 可设置严格的文件权限
- tmpfs 存储,不落盘
6.5 外部 Secret 管理方案
使用 External Secrets Operator 对接外部密钥管理系统:
# 安装 External Secrets Operator
# helm install external-secrets external-secrets/external-secrets -n external-secrets-system
# 1. 配置 SecretStore(以阿里云 KMS 为例)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aliyun-kms
namespace: production
spec:
provider:
alibaba:
regionID: cn-hangzhou
auth:
secretRef:
accessKeyIDSecretRef:
name: aliyun-credentials
key: access-key-id
accessKeySecretSecretRef:
name: aliyun-credentials
key: access-key-secret
---
# 2. 声明需要同步的 Secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h # 每小时同步一次
secretStoreRef:
name: aliyun-kms
kind: SecretStore
target:
name: db-credentials # 生成的 K8s Secret 名称
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: /production/database/username
- secretKey: password
remoteRef:
key: /production/database/password
6.6 RBAC 限制 Secret 访问
# 严格限制哪些 SA 可以访问 Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader-limited
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-config", "tls-cert"] # 只能访问指定的 Secret
verbs: ["get"]
# 注意:不给 list 权限,防止枚举所有 Secret
七、OPA Gatekeeper 策略引擎
7.1 为什么需要 Gatekeeper
RBAC 解决的是 “谁能做什么” 的问题,但无法控制 “资源怎么创建”。例如:
- RBAC 可以限制用户只能创建 Deployment,但无法要求 Deployment 必须设置 resource limits
- RBAC 可以允许用户创建 Pod,但无法禁止使用
latest标签 - RBAC 无法强制要求所有资源带有
team和cost-centerlabel
OPA Gatekeeper 作为 Validating Admission Webhook,在资源创建/更新时强制执行组织策略。
7.2 架构
kubectl apply → apiserver → Authentication → Authorization(RBAC)
→ MutatingWebhook → ValidatingWebhook(Gatekeeper) → etcd
↓
ConstraintTemplate (Rego策略定义)
↓
Constraint (策略实例化)
核心概念:
- ConstraintTemplate:使用 Rego 语言定义策略逻辑模板
- Constraint:将 ConstraintTemplate 实例化并配置参数,指定作用范围
7.3 安装 Gatekeeper
# 使用 Helm 安装
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set replicas=3 \
--set audit.replicas=2
# 验证安装
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeper
7.4 策略示例一:强制所有 Pod 设置 Resource Limits
# ConstraintTemplate:定义策略逻辑
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
spec:
crd:
spec:
names:
kind: K8sRequiredResources
validation:
openAPIV3Schema:
type: object
properties:
limits:
type: array
items:
type: string
requests:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
provided := {resource_type | container.resources.limits[resource_type]}
required := {resource_type | resource_type := input.parameters.limits[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("容器 '%v' 缺少以下 resource limits: %v", [container.name, missing])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
provided := {resource_type | container.resources.requests[resource_type]}
required := {resource_type | resource_type := input.parameters.requests[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("容器 '%v' 缺少以下 resource requests: %v", [container.name, missing])
}
---
# Constraint:实例化策略
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: must-have-resource-limits
spec:
enforcementAction: deny # deny | warn | dryrun
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
limits:
- cpu
- memory
requests:
- cpu
- memory
7.5 策略示例二:禁止使用 latest 标签
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowedtags
spec:
crd:
spec:
names:
kind: K8sDisallowedTags
validation:
openAPIV3Schema:
type: object
properties:
tags:
type: array
items:
type: string
exemptImages:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowedtags
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not is_exempt(container.image)
tag := get_tag(container.image)
disallowed_tag := input.parameters.tags[_]
tag == disallowed_tag
msg := sprintf("容器 '%v' 使用了禁止的镜像标签 '%v',镜像: %v", [container.name, tag, container.image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not is_exempt(container.image)
not contains(container.image, ":")
msg := sprintf("容器 '%v' 未指定镜像标签(等同于 latest),镜像: %v", [container.name, container.image])
}
get_tag(image) = tag {
parts := split(image, ":")
tag := parts[count(parts) - 1]
}
is_exempt(image) {
exempt := input.parameters.exemptImages[_]
startswith(image, exempt)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowedTags
metadata:
name: no-latest-tag
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces:
- kube-system
parameters:
tags: ["latest"]
exemptImages:
- "gcr.io/gatekeeper/" # 豁免 Gatekeeper 自身
7.6 策略示例三:要求所有资源带特定 Label
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: object
properties:
key:
type: string
allowedRegex:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := input.review.object.metadata.labels
required := input.parameters.labels[_]
not has_label(provided, required.key)
msg := sprintf("资源缺少必需的 label: %v", [required.key])
}
violation[{"msg": msg}] {
provided := input.review.object.metadata.labels
required := input.parameters.labels[_]
has_label(provided, required.key)
required.allowedRegex != ""
value := provided[required.key]
not re_match(required.allowedRegex, value)
msg := sprintf("label '%v' 的值 '%v' 不匹配正则: %v", [required.key, value, required.allowedRegex])
}
has_label(labels, key) {
labels[key]
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: must-have-team-label
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet"]
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
labels:
- key: "app.kubernetes.io/team"
allowedRegex: "^(platform|backend|frontend|data|sre)$"
- key: "app.kubernetes.io/env"
allowedRegex: "^(dev|staging|production)$"
7.7 策略示例四:限制特权容器
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8spsprivilegedcontainer
spec:
crd:
spec:
names:
kind: K8sPSPPrivilegedContainer
validation:
openAPIV3Schema:
type: object
properties:
exemptImages:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spsprivilegedcontainer
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not is_exempt(container.image)
container.securityContext.privileged == true
msg := sprintf("特权容器不被允许: %v (image: %v)", [container.name, container.image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not is_exempt(container.image)
container.securityContext.privileged == true
msg := sprintf("特权 initContainer 不被允许: %v (image: %v)", [container.name, container.image])
}
is_exempt(image) {
exempt := input.parameters.exemptImages[_]
startswith(image, exempt)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: deny-privileged-containers
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
parameters:
exemptImages:
- "registry.company.com/istio/proxyv2" # Istio sidecar 可能需要特权
7.8 查看策略违规情况
# 查看所有 Constraint 的违规统计
kubectl get constraints
# 查看具体某个 Constraint 的违规详情
kubectl describe k8sdisallowedtags no-latest-tag
# 使用 dryrun 模式测试策略效果(不实际拦截)
# 修改 Constraint 的 enforcementAction 为 dryrun
# 然后查看审计结果
kubectl get k8sdisallowedtags no-latest-tag -o yaml | grep -A 50 "status:"
八、安全加固 Checklist
8.1 最小权限原则
# 审计当前谁有 cluster-admin 权限
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") |
{name: .metadata.name, subjects: .subjects}'
# 审计 SA 权限
kubectl auth can-i --list --as=system:serviceaccount:production:default -n production
8.2 禁用 default SA 的 Token 自动挂载
# 对每个 namespace 的 default SA 执行
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: production
automountServiceAccountToken: false
批量操作脚本:
# 获取所有 namespace 并禁用 default SA 的 Token 挂载
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl patch serviceaccount default -n $ns \
-p '{"automountServiceAccountToken": false}'
done
8.3 定期审计 RBAC
# 查看指定用户的所有权限
kubectl auth can-i --list --as=developer@company.com
# 检查谁可以访问 Secrets
kubectl auth can-i get secrets --all-namespaces \
--as=system:serviceaccount:production:my-sa
# 查找过度授权的 ClusterRoleBinding(绑定到 cluster-admin 的非系统账号)
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.roleRef.name=="cluster-admin") |
select(.subjects[]?.name | test("^system:") | not) | .metadata.name'
# 列出所有 RoleBinding 和权限主体
kubectl get rolebindings,clusterrolebindings --all-namespaces \
-o custom-columns='KIND:kind,NAMESPACE:metadata.namespace,NAME:metadata.name,ROLE:roleRef.name'
8.4 启用 Audit Log
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 记录所有对 Secret 的操作(脱敏)
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# 记录 RBAC 变更的完整请求体
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# 记录对 Pod 的写操作
- level: Request
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["pods"]
- group: "apps"
resources: ["deployments", "statefulsets"]
# 只记录读操作的元数据
- level: Metadata
verbs: ["get", "list", "watch"]
# 忽略对 kube-system 的健康检查
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services"]
在 kube-apiserver 中启用:
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
--audit-log-path=/var/log/kubernetes/audit.log
--audit-log-maxage=30
--audit-log-maxbackup=10
--audit-log-maxsize=100
8.5 网络策略配合
RBAC 控制 API 访问,NetworkPolicy 控制网络层面流量:
# 默认拒绝所有入站流量(零信任基线)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 精确开放需要的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend-api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
8.6 镜像安全
# 限制镜像只能来自信任的仓库(通过 Gatekeeper 策略)
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith_any(container.image, input.parameters.repos)
msg := sprintf("容器 '%v' 的镜像 '%v' 来自不受信任的仓库。允许的仓库: %v",
[container.name, container.image, input.parameters.repos])
}
startswith_any(str, prefixes) {
prefix := prefixes[_]
startswith(str, prefix)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: trusted-repos-only
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
parameters:
repos:
- "registry.company.com/"
- "gcr.io/company-project/"
8.7 完整安全 Checklist 总结
## 认证与账号安全
- [ ] 所有人员使用 OIDC 认证,禁止共享 kubeconfig
- [ ] 禁用 default SA 的 automountServiceAccountToken
- [ ] 为每个应用创建独立 SA,遵循最小权限
- [ ] 使用短时 Token(TokenRequest API),避免长期 Secret
## RBAC 权限管理
- [ ] 禁止非必要的 cluster-admin 绑定
- [ ] 使用 namespace 级 Role 而非 ClusterRole(除非必要)
- [ ] 限制 Secret 的访问权限(配合 resourceNames)
- [ ] 定期审计 RBAC 配置(kubectl auth can-i)
- [ ] CI/CD 账号严格限定操作范围
## Pod 安全
- [ ] 生产 namespace 启用 PSA restricted 或 baseline
- [ ] 禁止特权容器和 hostPath 挂载
- [ ] 强制 runAsNonRoot 和 readOnlyRootFilesystem
- [ ] 设置 seccompProfile: RuntimeDefault
## 数据保护
- [ ] 启用 etcd 加密(EncryptionConfiguration)
- [ ] Secret 通过 Volume 挂载而非环境变量
- [ ] 使用外部密钥管理(Vault/KMS + External Secrets Operator)
- [ ] 敏感数据不进入版本控制
## 策略强制执行
- [ ] 部署 OPA Gatekeeper / Kyverno
- [ ] 强制 resource limits
- [ ] 禁止 latest 标签
- [ ] 强制必须的 label
- [ ] 限制镜像来源
## 网络与审计
- [ ] 启用 NetworkPolicy(默认拒绝)
- [ ] 启用 Audit Log 并持久化
- [ ] 监控异常 API 调用
- [ ] 定期进行安全扫描(kube-bench, trivy)
总结
Kubernetes 安全是一个纵深防御体系,单一措施无法提供完整保护。本文覆盖的关键组件协同工作:
- ServiceAccount 提供 Pod 身份标识
- RBAC 基于身份控制 API 访问权限
- PSA 从 Pod 运行时层面限制危险行为
- Secrets 管理 保护敏感数据全生命周期
- OPA Gatekeeper 在准入层强制组织合规策略
- NetworkPolicy + Audit 提供网络层和可观测性保障
生产环境中,建议采用渐进式安全加固策略:先以 warn/audit 模式观察影响,逐步切换到 enforce,确保安全措施不影响业务连续性。同时建立定期安全审计机制,确保权限配置不会随时间推移而劣化。
本文基于 Kubernetes 1.28+ 版本编写,部分功能在旧版本中可能不可用。建议读者结合官方文档验证具体版本特性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)