概述

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 就会自动使用它。问题在于:

  1. 多个 Pod 共享同一个 default SA,无法做细粒度权限控制
  2. default SA 的 token 会自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/,应用代码可以直接访问 K8s API
  3. 一旦某个 Pod 被攻破,攻击者可以用 default SA 的 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必须 falseprivileged 容器等于宿主机 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: {}

关键安全配置详解

配置默认值生产建议说明
privilegedfalse必须 falseprivileged 容器等于宿主机 root
allowPrivilegeEscalationtrue必须 false防止 setuid 提权
runAsNonRoot未设置必须 true禁止 root 运行
runAsUser未设置指定非0 UID明确运行用户
readOnlyRootFilesystemfalse建议 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: {}

常见需要可写目录的场景:

路径方案
/tmpemptyDir + medium: Memory (tmpfs)
/var/logemptyDir 或 PVC
/app/cacheemptyDir
/app/uploadsPVC(需要持久化)

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

  • 没有使用 default SA 运行工作负载
  • 每个工作负载有独立的 ServiceAccount
  • SA 的 token 默认不自动挂载(automountServiceAccountToken: false
  • 没有 cluster-admin 权限的 SA
  • 定期审计 RBAC 绑定,清理不再需要的权限
  • CI/CD 使用的 SA 权限限制在部署命名空间内

Pod Security

  • 生产命名空间配置 restricted PSS
  • 所有 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、用户)只授予完成其功能所需的最小权限。

实施路径建议:

  1. 第一步:为所有工作负载创建独立 SA,关闭默认 token 挂载。这是成本最低、收益最高的安全措施。
  2. 第二步:在命名空间上配置 PSS,先从 warn + audit 模式开始,收集违规清单后逐个修复。
  3. 第三步:配置 NetworkPolicy,先 default-deny-all 再逐步放行。这一步需要确认 CNI 支持。
  4. 第四步:开启审计日志,建立安全事件的监控和告警能力。
  5. 第五步:定期安全审计,使用 kubectl auth can-i --list 检查权限蔓延,清理冗余权限。

安全没有银弹,但每一层防护都在提高攻击者的成本。多层防御(Defense in Depth)的核心思想是:不依赖单一安全机制,RBAC、PSS、NetworkPolicy、审计日志各司其职,共同构成纵深防御体系。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Kubernetes 安全文档 — Kubernetes 官方,参考了Kubernetes 安全文档相关内容