一、现象或背景
46字符SA Token读出生产数据库密码
在EKS托管集群(Kubernetes 1.27.4)中,payment-service部署在prod-payment命名空间,使用默认ServiceAccount。红队通过测试Pod拿到的kubectl auth can-i权限检查结果发现:
$ kubectl auth can-i list secrets --namespace=prod-payment
yes
$ kubectl get secrets -n prod-payment
payment-db-cred
代价:该Secret包含数据库root用户密码(46字符),且未设置automountServiceAccountToken: false。攻击者通过kubectl get secret payment-db-cred -o jsonpath='{.data.password}' | base64 -d直接获取明文密码。
规模:集群共200+命名空间,60%未配置RBAC白名单,35%存在ServiceAccount权限过度开放问题。
二、原理或根因分析
2.1 ServiceAccount默认绑定机制
Kubernetes默认为每个Pod注入ServiceAccount Token,其权限由所在命名空间的RoleBinding/ClusterRoleBinding决定。关键漏洞点:
- 默认绑定Root权限:未显式配置RBAC时,ServiceAccount自动获得
view权限(rbac.authorization.k8s.ioAPI组) - Secret自动挂载:Pod启动时自动挂载
/var/run/secrets/kubernetes.io/serviceaccount目录,包含Token和CA证书 - 权限继承漏洞:ServiceAccount可访问所有同命名空间的Secret,未做访问控制
2.2 权限扩散路径
graph LR
A[Pod] --> B[ServiceAccount]
B --> C[Secret]
C --> D[Database Credentials]
D --> E[Database Access]
攻击者通过Pod获取ServiceAccount Token后,可访问同命名空间所有Secret,进而获取数据库凭证。
三、具体做法
3.1 配置最小权限RBAC
Step 1: 创建受限Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: prod-payment
name: db-readonly
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
Step 2: 绑定到ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: db-readonly-binding
namespace: prod-payment
subjects:
- kind: ServiceAccount
name: default
namespace: prod-payment
roleRef:
kind: Role
name: db-readonly
apiGroup: rbac.authorization.k8s.io
3.2 禁用默认Token挂载
在Deployment中显式配置:
spec:
serviceAccountName: my-sa
automountServiceAccountToken: false
3.3 使用PodSecurityPolicy限制权限
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted-psp
spec:
privileged: false
seLinuxOptions:
level: "s0:c1234567890"
runAsUser:
rule: MustRunAsNonRoot
fsGroup:
rule: MustRunAs
ranges:
- min: 1000
max: 2000
supplementalGroups:
rule: MustRunAs
ranges:
- min: 1000
max: 2000
四、踩过的坑
- 权限继承陷阱:在
prod-payment命名空间创建Role时,未注意到view权限会自动继承到所有Pod - Secret挂载残留:删除Pod后,/var/run/secrets目录仍会残留旧Token(需手动清理或重启Node)
- RBAC生效延迟:修改RoleBinding后需等待10~30秒,Kubelet才会同步权限配置
- ServiceAccount重名冲突:在多个命名空间使用
defaultServiceAccount会导致权限继承混乱
五、结论与可复用清单
| 项目 | 配置建议 | 验证方法 |
|------|---------|---------|
| RBAC权限 | 创建最小权限RoleBinding | kubectl auth can-i |
| Token挂载 | 设置automountServiceAccountToken: false | kubectl describe pod |
| Secret访问 | 使用readOnly策略 | kubectl get secret |
| 权限继承 | 避免使用view权限 | kubectl get clusterrole |
| 安全审计 | 定期检查RBAC配置 | kubectl get rolebinding -A |
验证命令:
# 检查当前权限
kubectl auth can-i get secrets --namespace=prod-payment
# 查看ServiceAccount配置
kubectl get serviceaccount default -n prod-payment -o jsonpath='{.secrets}'
# 检查RBAC绑定
kubectl get rolebinding -n prod-payment
生产环境建议:
- 所有ServiceAccount必须显式配置RBAC
- 禁用默认Token挂载(
automountServiceAccountToken: false) - 对Secret访问实施
readOnly策略 - 定期审计RBAC配置(建议每周执行一次)
监控指标:
kube_node_status_capacity中的secrets数量kube_pod_status_phase的Running状态持续时间kube_pod_container_status_restarts_total的异常增长
六、Secret加密与动态密钥管理
在Kubernetes生态中,Secret作为敏感数据载体存在天然安全风险,其存储和传输过程若缺乏加密防护,将形成新的攻击面。传统RBAC策略虽能控制访问权限,但无法解决Secret本身泄露问题。本节聚焦Secret加密机制与动态密钥管理方案,从底层数据保护角度构建安全防线。
6.1 Kubernetes Secret的存储加密机制
Kubernetes内置的Secret存储采用base64编码,本质上是明文存储。其加密特性仅体现在传输层(TLS)和持久化存储介质的物理安全层面。若集群节点被攻破,攻击者可通过以下路径获取Secret数据:
# 通过Pod文件系统访问
kubectl exec -it <pod-name> -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
# 通过etcd直接查询
etcdctl get /registry/secrets/default/payment-db-cred
解决方案:
- 启用etcd TLS加密(
--etcd-enforce-ssl) - 配置持久化存储加密(如Ceph RBD、AWS EBS加密)
- 使用KMS(Key Management Service)对Secret进行加密
6.2 Secret加密代理(Secret Encrypt at Rest)
Kubernetes 1.13引入Secret Encrypt at Rest功能,通过加密代理实现Secret数据的透明加密存储。典型架构如下:
+-------------------+ +-------------------+ +-------------------+
| Application Pod | | Secret Encryptor | | Persistent Volume |
| (使用Secret) | | (加密代理) | | (加密存储) |
+-------------------+ +-------------------+ +-------------------+
实现步骤:
- 配置加密代理(如vault-agent-injector)注入环境变量
- 在PersistentVolume中启用加密参数:
spec:
volumeEncryptionConfig:
provider: "vault"
vault:
server: "https://vault.example.com"
auth:
role: "k8s-secrets"
- 使用
kubectl create secret命令创建加密Secret:
kubectl create secret generic payment-db-cred \
--from-file=password=$(openssl rand -base64 46) \
--type=encryption/vault
验证方法:
# 检查加密Secret
kubectl get secret payment-db-cred -o jsonpath='{.data.password}'
# 验证存储层加密
kubectl get pv <pv-name> -o jsonpath='{.spec.csi.volumeHandle}'
6.3 动态密钥轮换与Secret自动更新
传统Secret管理存在两大缺陷:1)密钥长期静止导致攻击面扩大;2)手动更新流程易引发操作失误。通过集成密钥管理服务(KMS)实现动态密钥轮换,可有效解决该问题。
Vault集成方案:
- 配置Vault Secrets Engine:
vault secrets enable -path=secrets kv-v2
vault write secrets/config/defaults lease=3600
- 创建数据库凭证Secret:
vault kv put secrets/db/payment-db password=$(openssl rand -base64 46)
- 配置Kubernetes Secret Sync:
apiVersion: k8s.vault.podman.io/v1
kind: SecretSync
metadata:
name: payment-db-sync
spec:
vault:
server: "https://vault.example.com"
auth:
token: "s.Y5z9Kd8h4F9j7nG3..."
secrets:
- name: payment-db-cred
path: secrets/db/payment-db
- 实现自动更新:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
replicas: 2
selector:
matchLabels:
app: payment
template:
metadata:
labels:
app: payment
spec:
containers:
- name: payment
image: payment-app
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: payment-db-cred
key: password
volumeMounts:
- name: vault-token
mountPath: /var/run/secrets/vault
volumes:
- name: vault-token
secret:
secretName: vault-token
监控指标:
vault_secrets_leases的密钥轮换频率kube_secret_age的Secret更新周期vault_secrets_access的异常访问记录
6.4 Secret访问审计与告警
启用审计日志后,可通过以下方式追踪Secret访问行为:
# 查看Secret访问日志
kubectl get audit-logs -n kube-system
# 配置审计策略
apiVersion: audit.k8s.io/v1beta1
kind: AuditPolicy
rules:
- level: Metadata
- omitTimestamp: true
告警规则示例:
- alert: SecretAccess
expr:
kube_secret_access_total{namespace="prod-payment"} > 0
for: 5m
labels:
severity: warning
annotations:
summary: "prod-payment命名空间检测到Secret访问"
description: "最近5分钟内发生Secret访问行为,需核查访问来源"
通过上述措施,可构建从存储加密到动态管理的全链路Secret防护体系,使Secret数据在生命周期内始终处于加密状态,有效防止因权限配置疏漏导致的敏感信息泄露。