配置和秘密看起来都像键值对,但待遇必须不同
Kubernetes 用 ConfigMap 保存普通配置,用 Secret 保存敏感数据。二者都能以环境变量或文件挂入 Pod,但安全语义完全不同。
本项目遵循一条清晰边界:
| |
不要因为 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
配置声明:
| |
不可变配置的优点是:正在运行的 Operator 不会在没有发布动作的情况下突然改变平台行为。
例如 StorageClass、Gateway selector 或资源上限若被原地修改,可能导致不同时间创建的 Pool 使用不同基线,也可能让控制器在运行中产生难以追踪的漂移。
正确的变更流程是:
- 创建新的版本化 ConfigMap,例如
subpool-operator-config-v3; - 修改 Deployment 的
--operator-config-name; - 触发并观察受控滚动更新;
- 验证新 Operator 使用新配置;
- 在确认无引用后,再按变更流程处理旧版本。
这类似发布应用版本:配置也应该有身份、有审计、有回滚路径。
Secret 以文件方式投影
runtime Pod 把每 Pool Secret 挂到:
| |
Volume 的 items 只列出需要的 key,而不是无差别暴露 Secret 全部内容。这叫最小数据暴露。
初始化脚本读取 CPA 所需 key;cpa-manager 则通过文件路径环境变量读取管理 key,例如:
| |
这里环境变量里只有文件路径,没有 secret value。
为什么不把 Secret value 直接写进环境变量
环境变量使用简单,但敏感值更容易出现在:
- 进程诊断信息;
- 错误日志;
- 调试转储;
- 应用打印全部环境变量的输出;
- 某些监控或支持工具中。
文件挂载也不是绝对安全,但更容易控制路径、权限和读取时机。应用若支持 *_FILE 形式,本项目优先使用它。
Secret 生命周期与所有权校验
Operator 只在 subpool-credentials 不存在时生成凭据。已存在时,它不会自动覆盖或轮换,而是检查:
- Secret 是否由当前 SubPool 控制;
- 必需 key 是否都存在;
- 每个 key 的值是否非空。
所有权不匹配时报告 CredentialConflict,形状不符合时报告 CredentialInvalid。
这避免了一个危险行为:Operator 因临时读取失败或资源漂移,重新生成 key,导致已经写入 PVC 的 CPA 配置与新 Secret 不一致。
轮换凭据需要专门协议:先让生产者和消费者同时接受新旧值,再切换并清理旧值。当前 v1 没有实现这套协议,所以“不要自动轮换”是更安全的选择。
Shared Mihomo Secret 为什么不进 Git
mihomo-gateway-providers 包含上游代理定义,可能涉及订阅 URL、密码和节点凭据。仓库只声明 Mihomo Deployment 会引用这个 Secret,不包含 Secret resource 或真实值。
安全流程可以这样创建或更新:
| |
这里依然要注意本地 shell 历史、CI 日志和命令输出。不要执行会把完整 Secret YAML 打印到共享日志的诊断命令。
读取 Secret 的权限就是读取明文的权限
拥有下面权限的人,通常就能取得 Secret value:
- 对 Secret 的
get、list或watch; - 对能挂载该 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
运行手册提供的命令会把凭据打印到当前终端:
| |
执行它意味着你已经决定让明文出现在屏幕和当前进程管道中。不要把输出重定向到普通日志,不要截图发到公开工单,使用完后按组织流程妥善清理。
诊断时看结构,不看 value
检查 Secret 是否存在:
| |
检查 key 名称时,优先使用只输出键名或元数据的方式,并避免任何 .data.* value。对于本项目,Conditions 已能报告 CredentialConflict 与 CredentialInvalid,通常不需要在普通排障中读取实际秘密。
配置管理的核心不是“把值塞进 Pod”,而是明确谁生产、谁读取、何时更新、如何审计、泄露后如何处置。ConfigMap 和 Secret 只是承载这些规则的 Kubernetes 对象。