ConfigMap、Secret 与安全配置注入

配置和秘密看起来都像键值对,但待遇必须不同

Kubernetes 用 ConfigMap 保存普通配置,用 Secret 保存敏感数据。二者都能以环境变量或文件挂入 Pod,但安全语义完全不同。

本项目遵循一条清晰边界:

1
2
可公开、可进 Git 的平台参数与脚本 → ConfigMap
密码、API key、代理订阅与节点凭据 → Secret

不要因为 Secret 的 YAML 里常出现 base64,就把 base64 当成加密。base64 只是编码,任何拿到值的人都能还原。

项目中有三类配置来源

Operator Configuration

位于 subpool-system 的 ConfigMap,保存所有 Pool 共享的非敏感平台参数:

  • baseDomain
  • Shared Envoy Gateway 的位置和 selector;
  • Shared Mihomo Service、端口和 selector;
  • Namespace 附加 labels;
  • StorageClass;
  • 单 Pool 最大 CPU、内存和存储包络。

它不保存某个 Pool 的镜像和资源申请,也不保存代理节点和任何密码。

Pool bootstrap ConfigMap

Operator 为每个 Pool 生成一个 ConfigMap,其中包含 initialize.sh。脚本负责在 PVC 第一次使用时创建 CPA 配置。

脚本本身没有秘密,可以作为受管资源和代码进行审计。执行时需要的秘密从 Secret 文件读取。

Secret

项目中有两类 Secret:

  • 每 Pool 的 subpool-credentials:由 Operator 使用 crypto/rand 首次生成;
  • Shared Mihomo 的 mihomo-gateway-providers:由外部安全流程提供,包含 proxies.yaml

前者属于单个 SubPool 生命周期,后者是共享基础设施,不能由某个 SubPool 删除。

为什么 Operator Configuration 使用 immutable ConfigMap

配置声明:

1
immutable: true

不可变配置的优点是:正在运行的 Operator 不会在没有发布动作的情况下突然改变平台行为。

例如 StorageClass、Gateway selector 或资源上限若被原地修改,可能导致不同时间创建的 Pool 使用不同基线,也可能让控制器在运行中产生难以追踪的漂移。

正确的变更流程是:

  1. 创建新的版本化 ConfigMap,例如 subpool-operator-config-v3
  2. 修改 Deployment 的 --operator-config-name
  3. 触发并观察受控滚动更新;
  4. 验证新 Operator 使用新配置;
  5. 在确认无引用后,再按变更流程处理旧版本。

这类似发布应用版本:配置也应该有身份、有审计、有回滚路径。

Secret 以文件方式投影

runtime Pod 把每 Pool Secret 挂到:

1
2
3
/run/secrets/cpa_client_key
/run/secrets/cpa_management_key
/run/secrets/cpamp_admin_key

Volume 的 items 只列出需要的 key,而不是无差别暴露 Secret 全部内容。这叫最小数据暴露。

初始化脚本读取 CPA 所需 key;cpa-manager 则通过文件路径环境变量读取管理 key,例如:

1
2
CPA_MANAGER_ADMIN_KEY_FILE=/run/secrets/cpamp_admin_key
CPA_MANAGEMENT_KEY_FILE=/run/secrets/cpa_management_key

这里环境变量里只有文件路径,没有 secret value。

为什么不把 Secret value 直接写进环境变量

环境变量使用简单,但敏感值更容易出现在:

  • 进程诊断信息;
  • 错误日志;
  • 调试转储;
  • 应用打印全部环境变量的输出;
  • 某些监控或支持工具中。

文件挂载也不是绝对安全,但更容易控制路径、权限和读取时机。应用若支持 *_FILE 形式,本项目优先使用它。

Secret 生命周期与所有权校验

Operator 只在 subpool-credentials 不存在时生成凭据。已存在时,它不会自动覆盖或轮换,而是检查:

  1. Secret 是否由当前 SubPool 控制;
  2. 必需 key 是否都存在;
  3. 每个 key 的值是否非空。

所有权不匹配时报告 CredentialConflict,形状不符合时报告 CredentialInvalid

这避免了一个危险行为:Operator 因临时读取失败或资源漂移,重新生成 key,导致已经写入 PVC 的 CPA 配置与新 Secret 不一致。

轮换凭据需要专门协议:先让生产者和消费者同时接受新旧值,再切换并清理旧值。当前 v1 没有实现这套协议,所以“不要自动轮换”是更安全的选择。

Shared Mihomo Secret 为什么不进 Git

mihomo-gateway-providers 包含上游代理定义,可能涉及订阅 URL、密码和节点凭据。仓库只声明 Mihomo Deployment 会引用这个 Secret,不包含 Secret resource 或真实值。

安全流程可以这样创建或更新:

1
2
3
kubectl -n subpool-gateway create secret generic mihomo-gateway-providers \
  --from-file=proxies.yaml=/secure/path/proxies.yaml \
  --dry-run=client -o yaml | kubectl apply -f -

这里依然要注意本地 shell 历史、CI 日志和命令输出。不要执行会把完整 Secret YAML 打印到共享日志的诊断命令。

读取 Secret 的权限就是读取明文的权限

拥有下面权限的人,通常就能取得 Secret value:

  • 对 Secret 的 getlistwatch
  • 对能挂载该 Secret 的 Pod 进行创建或修改;
  • 对现有 Pod 执行 exec
  • 访问运行节点或底层数据存储。

因此仅仅“把数据放进 Secret”还不够,还要限制 RBAC、Pod 创建权限、审计和 etcd 静态加密。

项目的 runtime ServiceAccount 禁止自动挂载 API token,减少容器被攻破后直接调用 API Server 的能力。

ConfigMap 和 Secret 更新后,Pod 会怎样

以 Volume 挂载时,kubelet通常会在一段时间后更新投影文件,但存在重要例外和应用行为差异:

  • 使用 subPath 挂载单文件时通常不会自动收到更新;
  • 应用可能只在启动时读取配置;
  • immutable ConfigMap 根本不能原地更新;
  • Secret 内容变更并不保证应用自动重载。

因此不能只问“对象更新了吗”,还要问“应用何时重新读取”。本项目的 CPA 初始配置写入 PVC 后由应用使用,Operator Configuration 则通过版本化发布和 Operator 重启切换。

不要把秘密带进这些地方

本项目明确禁止敏感值进入:

  • SubPool.spec
  • SubPool.status
  • ConfigMap;
  • Git;
  • Kubernetes Event;
  • 日志和 metrics label;
  • 工单与聊天记录;
  • 长期保留的 shell history。

可以记录 Secret 的名称、Namespace、必需 key 名、对象是否存在和脱敏摘要,但不要记录 value。

安全读取 cpa-manager key

运行手册提供的命令会把凭据打印到当前终端:

1
2
kubectl -n subpool-demo-pool get secret subpool-credentials \
  -o jsonpath='{.data.cpamp_admin_key}' | base64 --decode

执行它意味着你已经决定让明文出现在屏幕和当前进程管道中。不要把输出重定向到普通日志,不要截图发到公开工单,使用完后按组织流程妥善清理。

诊断时看结构,不看 value

检查 Secret 是否存在:

1
kubectl -n subpool-demo-pool get secret subpool-credentials

检查 key 名称时,优先使用只输出键名或元数据的方式,并避免任何 .data.* value。对于本项目,Conditions 已能报告 CredentialConflictCredentialInvalid,通常不需要在普通排障中读取实际秘密。

配置管理的核心不是“把值塞进 Pod”,而是明确谁生产、谁读取、何时更新、如何审计、泄露后如何处置。ConfigMap 和 Secret 只是承载这些规则的 Kubernetes 对象。

使用 Hugo 构建
主题 StackJimmy 设计