Kubernetes Secret 管理

某公司安全团队进行了一次红队演练。攻击者的第一步是获取集群中某个应用的配置文件。他们没有直接攻击应用,而是通过 Kubernetes API 获取了应用的 Deployment 配置,其中包含了一个数据库凭证。

这个凭证来自 Kubernetes Secret。Secret 中存储的凭证 Base64 编码后写入 YAML,看起来像是加密的,但 Base64 解码只需要一行命令。

这是一个关于 Secret 安全的典型误解:Kubernetes Secret 默认不是加密存储的。理解这一点是安全运维的基础。

Kubernetes Secret 的用途

Secret 用于存储敏感信息,如密码、OAuth 令牌、SSH 密钥、TLS 证书等。

典型使用场景

  • 数据库连接凭证
  • API 密钥和令牌
  • TLS 证书和私钥
  • Docker Registry 认证信息
  • SSH 公钥/私钥

Secret 与 ConfigMap 的区别

特性SecretConfigMap
用途敏感数据非敏感配置
编码Base64(可配置加密)Base64 或明文
默认权限只能被授权 Pod 读取任意 Pod 可读
审计Secret 访问会被记录无特殊审计

Secret 的类型

Kubernetes 内置了几种 Secret 类型,提供不同的验证和转换逻辑。

Opaque(通用 Secret)

默认类型,用于存储任意键值对。

Opaque
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
data:
  username: dXNlcm5hbWU=
  password: cGFzc3dvcmQ=

TLS Secret

用于存储 TLS 证书和私钥。

TLS
apiVersion: v1
kind: Secret
metadata:
  name: tls-cert
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: <certificate>
  tls.key: <private-key>

Docker Registry Secret

用于拉取私有镜像仓库的镜像。

Docker
apiVersion: v1
kind: Secret
metadata:
  name: docker-registry-secret
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: <base64-encoded-config>

Service Account Token Secret

用于绑定服务账号令牌。

Service
apiVersion: v1
kind: Secret
metadata:
  name: sa-token
  namespace: production
type: kubernetes.io/service-account-token
data:
  token: <jwt-token>
  namespace: <namespace>

Secret 的存储安全

etcd 加密

默认情况下,etcd 存储 Secret 时使用明文(Base64 编码后存储)。启用 etcd 加密后,Secret 会被加密存储。

etcd
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      # 使用 KMS 插件加密(生产推荐)
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      # 最后的提供者作为默认
      - identity: {}

加密密钥管理

静态密钥:最简单,但不推荐用于生产环境。

静态密钥配置
providers:
  - aescbc:
      keys:
        - name: static-key
          secret: c2VjcmV0LWtleS0zMi1ieXRlcw==

KMS 插件:生产环境推荐,将密钥存储在云提供商的 KMS 服务中。

云服务商KMS 服务插件
AWSAWS KMSaws-encryption-provider
GCPCloud KMSgcp-kms-provider
AzureAzure Key Vaultakv-provider
本地HashiCorp Vaultvault-plugin

启用 etcd 加密的步骤

启用
# 1. 生成加密密钥
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)

# 2. 创建加密配置文件
cat > /tmp/encryption-config.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: $ENCRYPTION_KEY
      - identity: {}
EOF

# 3. 修改 API Server 配置
# 编辑 /etc/kubernetes/manifests/kube-apiserver.yaml
# 添加 --encryption-provider-config 参数

# 4. 重启 API Server 后,重新加密所有 Secret
kubectl get secrets --all-namespaces -o json | \
  kubectl replace -f -
加密生效顺序

启用 etcd 加密后,只有新创建的 Secret 会被加密存储。现有 Secret 需要显式重新加密才能被加密。

Secret 的最佳实践

不将 Secret 放在容器镜像中

错误做法

危险
FROM node:18

# 危险:将凭证直接写入镜像
COPY config /app/config
ENV API_KEY=secret-key-in-image

CMD ["node", "app.js"]

正确做法

正确
FROM node:18

COPY package*.json /app/
RUN npm ci --only=production

COPY src /app/src
WORKDIR /app
USER node

CMD ["node", "src/index.js"]

Pod 中引用 Secret

通过环境变量使用
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:latest
      env:
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: api-credentials
              key: api-key
              optional: false
通过
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: secrets
          mountPath: /secrets
          readOnly: true
  volumes:
    - name: secrets
      secret:
        secretName: api-credentials
        optional: false

使用 External Secrets Operator

External Secrets Operator(ESO)从外部 secret manager(如 AWS Secrets Manager、HashiCorp Vault、GCP Secret Manager)同步 Secret 到 Kubernetes。

ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: password
      remoteRef:
        key: production/database
        property: password

限制 Secret 的访问权限

使用 RBAC 限制谁可以读取 Secret。

限制
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]
    # 只允许读取标记了特定标签的 Secret
    resourceNames: ["db-credentials", "api-keys"]
RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: secret-reader-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: my-app-sa
roleRef:
  kind: Role
  name: secret-reader

Vault 与 K8s Secret 的集成

HashiCorp Vault 是企业常用的 Secret 管理解决方案。

Vault Kubernetes 认证

Vault 支持通过 Kubernetes ServiceAccount 进行身份认证:

配置
# 启用 Kubernetes 认证
vault auth enable kubernetes

# 配置 Kubernetes 认证
vault write auth/kubernetes/config \
  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  kubernetes_host="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT_HTTPS" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

Vault 动态 Secret

Vault 可以为数据库、AWS 等生成动态 Secret,避免静态凭证共享。

创建数据库动态
# 启用数据库引擎
vault secrets enable database

# 配置数据库连接
vault write database/config/my-postgresql \
  plugin_name=postgresql-database-plugin \
  host=db.example.com \
  port=5432 \
  username=admin \
  password=admin_password

# 创建角色
vault write database/roles/myapp-role \
  db_name=my-postgresql \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.core.VaultSys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DatabaseCredentialService {
    
    @Autowired
    private VaultOperations vault;
    
    public DatabaseCredentials getCredentials() {
        // 从 Vault 获取动态凭证
        Map<String, Object> secret = vault.read("database/creds/myapp-role");
        
        return new DatabaseCredentials(
            (String) secret.get("username"),
            (String) secret.get("password"),
            (String) secret.get("lease_id")
        );
    }
    
    public void closeCredentials(String leaseId) {
        // 释放动态凭证
        vault.opsForLease().revoke(leaseId);
    }
}

Secret 的轮转策略

静态 Secret 长期有效会增加泄露风险。需要建立轮转机制。

手动轮转流程

Secret
# 1. 创建新 Secret
kubectl create secret generic api-key-v2 \
  --from-literal=api-key=new-secret-key \
  --namespace=production

# 2. 更新应用配置使用新 Secret
kubectl patch deployment myapp \
  --patch '{"spec":{"template":{"spec":{"containers":[{"name":"app","env":[{"name":"API_KEY","valueFrom":{"secretKeyRef":{"name":"api-key-v2","key":"api-key"}}}]}]}}}}}' \
  --namespace=production

# 3. 验证应用正常工作
kubectl rollout status deployment/myapp --namespace=production

# 4. 删除旧 Secret
kubectl delete secret api-key-v1 --namespace=production

自动轮转策略

使用 External Secrets Operator + Vault 可以实现 Secret 的自动轮转:

自动轮转配置
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  vault:
    server: "https://vault.example.com"
    path: "secret"
    auth:
      kubernetes:
        mountPath: kubernetes
        role: "myapp-role"
Secret 轮转建议

动态 Secret(如 Vault 动态凭证)天然支持自动轮转。对于静态 Secret,建议:1)设置 Secret 的最大生命周期;2)使用外部 Secret manager 管理生命周期;3)在 CI/CD 流水线中嵌入 Secret 轮转逻辑。

总结与延伸思考

Kubernetes Secret 的安全性取决于正确的配置和使用方式。默认情况下,Secret 只是 Base64 编码,不是加密。生产环境必须:

  1. 启用 etcd 加密
  2. 使用 RBAC 限制访问
  3. 启用审计日志
  4. 考虑使用 Vault 或 ESO 管理 Secret

对于���高安全要求的场景,可以考虑使用 Sealed Secrets,将加密的 Secret 存储在 Git 中,只有集群内的 Sealed Secrets Controller 可以解密。

思考题

问题 1:为什么 Kubernetes Secret 的 Base64 编码不等于加密?

参考答案

Base64 编码是一种可逆转换,任何人都可以用 echo "base64-string" | base64 -d 解码。加密需要密钥才能解密,即使知道密文也无法恢复明文。Kubernetes Secret 默认使用 Base64 编码是为了在 API 中传输二进制数据(如证书),而不是提供安全保护。真正的保护来自:1)etcd 加密存储;2)RBAC 限制 API 访问;3)传输层 TLS 加密。

问题 2:如果需要在 Git 中存储 Secret 配置文件(如 deployment.yaml),应该如何处理?

参考答案

有几种方案:1)使用 Sealed Secrets:Sealed Secrets Controller 解密后创建普通 Secret,只有 Controller 能解密,Git 中存储的是加密数据;2)使用 SOPS:Mozilla 的加密工具,支持 AWS KMS、GCP KMS 等加密 Git 中的敏感文件;3)使用 External Secrets Operator:将 Secret 管理外部化,Git 只存储 ExternalSecret 资源定义;4)Git-crypt:Git 插件,在 Git 层面加密敏感文件。无论哪种方案,都不应该将明文 Secret 提交到 Git。