OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 099

sops-nix 集成:运行时、时序与 SSH host key

把加密源文件、运行时秘密文件与服务依赖连接起来。

所在专题SOPS 加密配置与密钥生命周期 ↗
约 11 分钟阅读开始阅读

三个角色: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 位于根路径就认定它一定早期可用。

相关#


服务环境变量是方便的接口,但不适合所有秘密:能检查进程环境或受信任服务运行上下文的人可能读到它。应用支持凭据文件时可优先传路径;明文不得通过 Nix 求值、store 或构建日志暴露。

参考与延伸

  1. SOPS 官方文档 ↗
  2. SOPS CLI 源码 ↗
  3. SOPS 配置源码 ↗
  4. sops-nix 官方说明 ↗
  5. sops-nix 模块 ↗
  6. sops-nix 模板模块 ↗
  7. ssh-to-age 官方说明 ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

SOPS 加密配置与密钥生命周期

  1. 01sops 原理:混合加密与接收者
  2. 02sops-nix 集成:运行时、时序与 SSH host key
  3. 03sops 使用指南:命令行操作
  4. 04sops 配置模板与运行时渲染
  5. 05sops 密钥迁移与轮换
CONTINUE READING / 下一篇sops 使用指南:命令行操作

编辑加密文件,检查状态并管理接收者与数据密钥。