三个角色:secrets 怎么变成进程环境变量#
sops-install-secrets.service example-app.service
│ │
│ ① 解密写入 │ ② 读取环境变量
▼ ▼
/run/secrets/app_env ──▶ example-app 进程 + 它的 envsubst① sops 写文件:sops-install-secrets 解开 secrets.yaml 后,把 app_env 内容写进 /run/secrets/app_env。通常配置为 root:root、0400;具体以 secret 的 owner/group/mode 为准。Linux sops-nix 默认将秘密放在 ramfs 的 /run/secrets.d 并通过 /run/secrets 链接暴露,useTmpfs 会改变存储方式;它们是运行时产物,重启后重新生成。
② systemd 注入环境变量(关键):
serviceConfig.EnvironmentFile = "/run/secrets/app_env";EnvironmentFile 是 systemd 特性,不是 example-app 的:systemd 启动 service 前读取文件,把 KEY=value 注入进程环境。动作发生在进程诞生之前——example-app 自己不知道有 sops。
③ 进程内消费:example-app 的 ExecStartPre 用 envsubst 从环境变量渲染 config.yaml(${APP_SECRET}、${APP_ENDPOINT}),ExecStart 再读渲染好的配置启动。
解密输出:一个键一个文件#
sops-nix 解密后,每个 secret 生成一个独立文件,不合成。源码 path 默认值 = /run/secrets/<secret名>(neededForUsers 时 /run/secrets-for-users/)。每个 secret 独立声明其输出路径和权限。
定义三个 secret 就得到三个文件:
sops.secrets = {
app_env = { }; # → /run/secrets/app_env
another = { sopsFile = ./secrets/another.env; format = "dotenv"; key = "TOKEN"; }; # → /run/secrets/another
blob = { sopsFile = ./secrets/blob.enc; format = "binary"; }; # → /run/secrets/blob
};format 可以按 secret 设置(yaml/json/dotenv/ini/binary),但它声明的是来源 sopsFile 的解析格式,不是键值内容的格式。同一 YAML 源的 secret 应按 YAML 读取;需要 dotenv 或 binary 源时,指向相应格式的独立加密文件。默认 format 继承 defaultSopsFormat,不是按扩展名自动推断。
存储结构 vs 运行时组织的区别 加密时所有键收进一个 secrets.yaml(存储层);解密时按 secret 名拆成多个独立文件(运行时层)。
format选择加密源的解析方式,key选择要导出的值;YAML 字符串值可以本身包含多行环境文件或配置文本。key = ""表示导出整个解密文件,binary 忽略 key。
useSystemdActivation:两条常规 secret 部署路径#
sops-nix 可通过 NixOS activation script 或 systemd unit 部署普通 secrets:
| activation script | useSystemdActivation = true |
|
|---|---|---|
| 执行入口 | NixOS 激活过程中的 setupSecrets |
sops-install-secrets.service |
| 运行条件 | 受启动与 switch 激活过程、用户创建及挂载可用性影响 | 通过 unit 依赖控制顺序 |
| 常见选择 | 默认值取决于 user 管理方式和模块版本 | 需要挂载依赖或使用 sysusers/userborn 时有用 |
| 检查重点 | 激活时密钥、用户及目录是否可用 | After、RequiresMountsFor 与消费服务依赖 |
不能将所有 NixOS activation script 一概称为 initrd 脚本。启动与激活路径需要依据配置版本和日志核对,不能仅从秘密文件缺失判断发生阶段。当前上游模块为 systemd 路径设置 local-fs.target 等顺序,并根据密钥路径设置 RequiresMountsFor;实际部署以项目锁定 revision 的代码和生成 unit 为准。neededForUsers 使用更早的用户创建前路径,需单独检查,不能依靠普通服务的等待机制补救。
挂载时序:为什么有时序问题#
解密时,运行该阶段的进程必须能够读取密钥。挂载是否可用由启动配置决定,不能仅根据目录名或 Btrfs subvolume 名推断:
| 示例路径 | 可能承载的内容 | 要检查的条件 |
|---|---|---|
/、/etc/ssh/ |
根文件系统与 host key | 根是否持久、host key 是否已经生成或恢复 |
/nix |
Nix store | 启动所需挂载是否已可用 |
/home |
用户身份与配置 | 用户分区是否已挂载,服务是否有访问权限 |
/persist |
持久化密钥与机器状态 | 挂载时序、恢复绑定、解密阶段依赖 |
密钥位于单独挂载点时,先核对生成的 unit 与挂载依赖。需要用户创建前秘密时,要让恢复密钥在该早期阶段可用;普通服务秘密可以使用适当的 systemd 挂载依赖。选择部署路径之后,再决定密钥保存位置。
故障链条示例:密钥路径不可用#
部署 secrets → 尝试读取 /persist/secrets/age-key.txt
→ 文件缺失、挂载未就绪或权限不允许
→ 解密失败,/run/secrets/app_env 未生成
→ 消费服务的 EnvironmentFile 加载失败代表性错误形式(示意,不是此处运行日志):
cannot read keyfile '/persist/secrets/age-key.txt': no such file or directory
Activation script snippet 'setupSecrets' failed先检查存在性、权限和时序,避免打印私钥或解密内容。若使用 systemd 解密路径,消费服务应指向实际的 sops-install-secrets.service,并配置合适的 After 与依赖关系;After 只规定顺序,不主动拉起 unit。Requires 可表达解密服务失败时不启动消费服务的要求。
为什么 sops-nix 可复用 SSH host key#
当前上游模块在启用了 OpenSSH 或 host key 生成时,会默认从 services.openssh.hostKeys 中导入 Ed25519 key(age)或 RSA key(GnuPG),并避开 /run/secrets 中的 key 以防循环依赖。未启用相应功能时,默认列表可以为空;并不是每台 NixOS 机器都有 host key。
| SSH host key | 用户 id_ed25519 | |
|---|---|---|
| 常见保存位置 | /etc/ssh/,也可由系统配置改动 |
用户的 ~/.ssh/ |
| 归属 | 通常为 root 管理的机器身份 | 用户身份 |
| 解密前提 | 已生成/恢复、文件与挂载可用、可读取 | 用户密钥存在、权限与口令条件满足 |
复用 host key 的理由包括机器服务与机器身份生命周期接近、减少独立密钥数量、root 权限可读。但这些是运维选择,不能推出必须使用 host key,也不能保证它跨重装自动保留。复用会扩大密钥泄露的影响范围,专用 age key 则能将 SSH 身份与 secrets 解密能力分离。
配置示例:从独立 age key 过渡到 host key#
sops = {
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
useSystemdActivation = true;
};这里的标准 host key 路径只是通用示例,不包含实际密钥。先把新 recipient 加入密文并验证独立解密,再调整部署来源;旧来源应保留到新链路验证完成。SSH host key 也需要备份和恢复计划,不能因为复用它就省略迁移。
密钥可保存在适合系统设计的持久化目录,前提是该解密阶段可读且依赖正确。不要把 /persist 或 /home 一律判为禁止目录;也不要只因 /etc 位于根路径就认定它一定早期可用。
相关#
- 原理 → sops 原理
- 命令行操作 → sops 使用指南
- host key 的角色 → SSH 认证体系
- 换密钥/迁移操作指南 → sops 迁移指南
- 参考:sops-nix 与 systemd activation 的取舍
服务环境变量是方便的接口,但不适合所有秘密:能检查进程环境或受信任服务运行上下文的人可能读到它。应用支持凭据文件时可优先传路径;明文不得通过 Nix 求值、store 或构建日志暴露。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。