本文系统性地剖析 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 相比其他授权模式的核心优势:

  1. 声明式管理:通过 Kubernetes 原生资源(Role/ClusterRole/Binding)管理权限,无需重启 apiserver
  2. 细粒度控制:可精确到 API Group、Resource、Verb、甚至具体 resourceName
  3. namespace 隔离:天然支持多租户场景
  4. 可审计:所有权限配置均为 API 对象,可通过 kubectl 查询和版本管理
  5. 社区生态:几乎所有 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

这意味着:

  1. 任何有 Secret GET 权限的用户/SA 都能获取明文
  2. etcd 中存储的也是 base64 编码(除非启用加密)
  3. 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 无法强制要求所有资源带有 teamcost-center label

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 安全是一个纵深防御体系,单一措施无法提供完整保护。本文覆盖的关键组件协同工作:

  1. ServiceAccount 提供 Pod 身份标识
  2. RBAC 基于身份控制 API 访问权限
  3. PSA 从 Pod 运行时层面限制危险行为
  4. Secrets 管理 保护敏感数据全生命周期
  5. OPA Gatekeeper 在准入层强制组织合规策略
  6. NetworkPolicy + Audit 提供网络层和可观测性保障

生产环境中,建议采用渐进式安全加固策略:先以 warn/audit 模式观察影响,逐步切换到 enforce,确保安全措施不影响业务连续性。同时建立定期安全审计机制,确保权限配置不会随时间推移而劣化。


本文基于 Kubernetes 1.28+ 版本编写,部分功能在旧版本中可能不可用。建议读者结合官方文档验证具体版本特性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐