agenix 是一个小巧便捷的 Nix 库,用于使用常见的公钥-私钥 SSH 密钥对安全地管理和部署机密:
你可以在源机器上使用多个公共 SSH 密钥加密一个机密(密码、访问令牌等),
然后将该加密的机密部署到任何拥有与这些公钥之一对应的私有 SSH 密钥的目标机器上。
该项目包含两个部分:
agenix 命令行应用程序(CLI),用于将机密加密为可复制到 Nix 存储的安全 .age 文件。agenix NixOS 模块,方便地
.age 文件)添加到 Nix 存储中,以便可以像任何其他 Nix 包一样使用 nixos-rebuild 或类似工具部署。/run/agenix/...)以供使用。Nix 存储中的所有文件都可以被任何系统用户读取,因此不适合包含明文机密。许多现有工具(如 NixOps deployment.keys)与 nixos-rebuild 分开部署机密,这使得部署、缓存和审计更加困难。带外机密管理也不太可重现。
agenix 通过使用您预先存在的 SSH 密钥基础设施和 age 将机密加密到 Nix 存储中来解决这些问题。机密在 NixOS 系统激活期间使用 SSH 主机私钥解密。
ssh-keyscan 获取系统公钥首先将其添加到 niv:
$ niv add ryantm/agenix
然后在 configuration.nix 的 imports 列表中添加以下内容:
{ imports = [ "${(import ./nix/sources.nix).agenix}/modules/age.nix" ]; }
要安装 agenix 二进制文件:
</details> <details> <summary>{ environment.systemPackages = [ (pkgs.callPackage "${(import ./nix/sources.nix).agenix}/pkgs/agenix.nix" {}) ]; }
以 root 身份运行:
$ sudo nix-channel --add https://github.com/ryantm/agenix/archive/main.tar.gz agenix $ sudo nix-channel --update
然后在 configuration.nix 的 imports 列表中添加以下内容:
{ imports = [ <agenix/modules/age.nix> ]; }
要安装 agenix 二进制文件:
</details> <details> <summary>{ environment.systemPackages = [ (pkgs.callPackage <agenix/pkgs/agenix.nix> {}) ]; }
在您的 configuration.nix 中添加以下内容:
{ imports = [ "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/modules/age.nix" ]; }
或者使用固定版本:
{ imports = let # 将此替换为实际的提交 ID 或标签 commit = "298b235f664f925b433614dc33380f0662adfc3f"; in [ "${builtins.fetchTarball { url = "https://github.com/ryantm/agenix/archive/${commit}.tar.gz"; # 从 nix build 输出更新哈希值 sha256 = ""; }}/modules/age.nix" ]; }
要安装 agenix 二进制文件:
</details> <details> <summary>{ environment.systemPackages = [ (pkgs.callPackage "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/pkgs/agenix.nix" {}) ]; }
{ inputs.agenix.url = "github:ryantm/agenix"; # 可选,模块不需要 #inputs.agenix.inputs.nixpkgs.follows = "nixpkgs"; # 可选,选择不下载 darwin 依赖项(在 Linux 上节省一些资源) #inputs.agenix.inputs.darwin.follows = ""; outputs = { self, nixpkgs, agenix }: { # 将 `yourhostname` 更改为您的实际主机名 nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem { # 更改为您的系统: system = "x86_64-linux"; modules = [ ./configuration.nix agenix.nixosModules.default ]; }; }; }
您可以临时运行 CLI 工具而无需安装:
nix run github:ryantm/agenix -- --help
但您也可以将其永久添加到 NixOS 模块中 (将系统 "x86_64-linux" 替换为您的系统):
{ environment.systemPackages = [ agenix.packages.x86_64-linux.default ]; }
例如,在您的 flake.nix 文件中:
</details>{ inputs.agenix.url = "github:ryantm/agenix"; # ... outputs = { self, nixpkgs, agenix }: { # 将 `yourhostname` 更改为您的实际主机名 nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ # ... { environment.systemPackages = [ agenix.packages.${system}.default ]; } ]; }; }; }
您要部署机密的系统应该已经存在并运行 sshd,以便它在 /etc/ssh/ 中生成了 SSH 主机密钥。
创建一个目录来存储机密和 secrets.nix 文件,用于列出机密及其公钥:
$ mkdir secrets $ cd secrets $ touch secrets.nix
这个 secrets.nix 文件不会导入到您的 NixOS 配置中。
它只用于 agenix CLI 工具(示例如下)以了解用于加密的公钥。
将公钥添加到您的 secrets.nix 文件中:
let user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH"; user2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILI6jSq53F/3hEmSs+oq9L4TwOo1PrDMAgcA1uo1CCV/"; users = [ user1 user2 ];
system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE"; system2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKzxQgondgEYcLpcPdJLrTdNgZ2gznOHCAxMdaceTUT1"; systems = [ system1 system2 ]; in { "secret1.age".publicKeys = [ user1 system1 ]; "secret2.age".publicKeys = users ++ systems; }
这些是稍后能够使用其对应私钥解密 `.age` 文件的用户和系统。
你可以从以下方式获取公钥:
* 通常在本地计算机的 `~/.ssh` 目录下,例如 `~/.ssh/id_ed25519.pub`。
* 从运行中的目标机器使用 `ssh-keyscan`:
```ShellSession
$ ssh-keyscan <ip-地址>
... ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKzxQgondgEYcLpcPdJLrTdNgZ2gznOHCAxMdaceTUT1
...
创建一个秘密文件:
$ agenix -e secret1.age
它将在你的 $EDITOR 环境变量配置的应用程序中打开一个临时文件。
当你保存该文件时,其内容将使用 secrets.nix 文件中提到的所有公钥进行加密。
将秘密添加到 NixOS 模块配置中:
{ age.secrets.secret1.file = ../secrets/secret1.age; }
当 age.secrets 属性集包含一个秘密时,agenix NixOS 模块稍后会自动解密并将该秘密挂载到默认路径 /run/agenix/secret1 下。
这里 secret1.age 文件成为你的 NixOS 部署的一部分,即移动到 Nix store 中。
在你的配置中引用秘密的挂载路径:
{ users.users.user1 = { isNormalUser = true; passwordFile = config.age.secrets.secret1.path; }; }
你可以在其他配置中已经引用(稍后)未加密秘密的挂载路径。
所以默认情况下 config.age.secrets.secret1.path 将包含路径 /run/agenix/secret1。
像往常一样使用 nixos-rebuild 或其他部署工具。
secret1.age 文件将像任何其他 Nix 包一样被复制到目标机器。
然后它将被解密并按照先前描述的方式挂载。
编辑秘密文件:
$ agenix -e secret1.age
它假设你的 SSH 私钥在 ~/.ssh/ 中。
为了解密并打开一个 .age 文件进行编辑,你需要用于加密该文件的公钥之一对应的私钥。你可以使用 -i 明确传递你想使用的私钥,例如
$ agenix -e secret1.age -i ~/.ssh/id_ed25519
age 模块参考age.secretsage.secrets 是秘密的属性集。你总是需要使用这个配置选项。默认为 {}。
age.secrets.<name>.fileage.secrets.<name>.file 是此秘密的加密 .age 文件的路径。这是唯一必需的秘密选项。
示例:
{ age.secrets.monitrc.file = ../secrets/monitrc.age; }
age.secrets.<name>.pathage.secrets.<name>.path 是秘密解密后的路径。默认为 /run/agenix/<name> (config.age.secretsDir/<name>)。
定义不同路径的示例:
{ age.secrets.monitrc = { file = ../secrets/monitrc.age; path = "/etc/monitrc"; }; }
对于许多服务,你不需要设置这个。相反,在你的配置中使用 config.age.secrets.<name>.path 引用解密路径。
引用路径的示例:
{ users.users.ryantm = { isNormalUser = true; passwordFile = config.age.secrets.passwordfile-ryantm.path; }; }
{ # 不要这样做! config.password = builtins.readFile config.age.secrets.secret1.path; }
这可能会导致明文被放置到全局可读的 Nix store 中。相反,让你的服务在运行时读取明文路径。
age.secrets.<name>.modeage.secrets.<name>.mode 是解密后的秘密的权限模式,格式为 chmod 可以理解的格式。通常,你只需要与 age.secrets.<name>.owner 和 age.secrets.<name>.group 结合使用。
示例:
{ age.secrets.nginx-htpasswd = { file = ../secrets/nginx.htpasswd.age; mode = "770"; owner = "nginx"; group = "nginx"; }; }
age.secrets.<name>.ownerage.secrets.<name>.owner 是解密文件所有者的用户名。通常,你只需要与 age.secrets.<name>.mode 和 age.secrets.<name>.group 结合使用。
示例:
{ age.secrets.nginx-htpasswd = { file = ../secrets/nginx.htpasswd.age; mode = "770"; owner = "nginx"; group = "nginx"; }; }
age.secrets.<name>.groupage.secrets.<name>.group 是解密文件的组名。通常,你只需要与 age.secrets.<name>.owner 和 age.secrets.<name>.mode 结合使用。
示例:
{ age.secrets.nginx-htpasswd = { file = ../secrets/nginx.htpasswd.age; mode = "770"; owner = "nginx"; group = "nginx"; }; }
age.secrets.<name>.symlinkage.secrets.<name>.symlink 是一个布尔值。如果为 true(默认值),秘密将被符号链接到 age.secrets.<name>.path。如果为 false,秘密将被复制到 age.secrets.<name>.path。通常,你希望保持为 true,因为它可以安全地清理不再使用的秘密。(符号链接仍然存在,但它将是断开的。)如果为 false,你需要负责在停止使用秘密后自行清理。
一些程序不喜欢跟随符号链接(例如 Java 程序如 Elasticsearch)。
示例:
{ age.secrets."elasticsearch.conf" = { file = ../secrets/elasticsearch.conf.age; symlink = false; }; }
age.secrets.<name>.nameage.secrets.<name>.name 是解密后文件的名称字符串。默认为属性路径中的 <name>,但如果你希望文件名与属性名不同,可以单独设置。
一个秘密名称与其属性路径不同的示例:
{ age.secrets.monit = { name = "monitrc"; file = ../secrets/monitrc.age; }; }
age.ageBinage.ageBin 是 age 二进制文件路径的字符串。通常,你不需要更改这个。默认为 age/bin/age。
覆盖 age.ageBin 的示例:
{pkgs, ...}:{ age.ageBin = "${pkgs.age}/bin/age"; }
age.identityPathsage.identityPaths 是一个尝试用于解密秘密的接收者密钥路径列表。默认情况下,它是 config.services.openssh.hostKeys 中的 rsa 和 ed25519 密钥,在 NixOS 上你通常不需要更改这个。列表项应该是字符串("/path/to/id_rsa""),而不是 nix 路径(../path/to/id_rsa),因为后者会将你的私钥复制到 nix store 中,这正是 agenix设计用来避免的情况。在运行时,至少一个文件路径必须存在并能够解密相关的秘密。覆盖age.identityPaths` 的示例:
{ age.identityPaths = [ "/var/lib/persistent/ssh_host_ed25519_key" ]; }
age.secretsDirage.secretsDir 是默认情况下秘密被符号链接到的目录。通常情况下,你不需要更改这个设置。默认值为 /run/agenix。
覆盖 age.secretsDir 的示例:
{ age.secretsDir = "/run/keys"; }
age.secretsMountPointage.secretsMountPoint 是在秘密被符号链接之前创建秘密世代的目录。通常情况下,你不需要更改这个设置。默认值为 /run/agenix.d。
覆盖 age.secretsMountPoint 的示例:
{ age.secretsMountPoint = "/run/secret-generations"; }
agenix - 编辑和重新加密 age 秘密文件
agenix -e 文件 [-i 私钥]
agenix -r [-i 私钥]
选项:
-h, --help 显示帮助
-e, --edit 文件 使用 $EDITOR 编辑文件
-r, --rekey 使用指定的接收者重新加密所有秘密
-d, --decrypt 文件 将文件解密到标准输出
-i, --identity 解密时使用的身份
-v, --verbose 详细输出
文件 一个 age 加密的文件
私钥 用于解密文件的 SSH 私钥路径
EDITOR 环境变量,用于指定编辑文件时使用的编辑器
如果标准输入不是交互式的,EDITOR 将被设置为 "cp /dev/stdin"
RULES 环境变量,包含指定接收者公钥的 Nix 文件路径。
默认为 './secrets.nix'
如果你更改了 secrets.nix 中的公钥,你应该重新加密你的秘密:
$ agenix --rekey
要重新加密一个秘密,你必须能够解密它。由于 age 加密算法中的随机性,即使身份没有改变,文件在重新加密时也总是会发生变化。(这最终可以通过从 age 文件中读取身份来改进。)
agenix 命令行界面默认使用 age 作为其 age 实现,你可以使用 Flakes 来使用 rage 实现,如下所示:
{pkgs,agenix,...}:{ environment.systemPackages = [ (agenix.packages.x86_64-linux.default.override { ageBin = "${pkgs.rage}/bin/rage"; }) ]; }
支持和开发讨论可以在 GitHub 上进行,也可以通过 Matrix 进行。
本项目尚未经过安全专业人员的审核。
不熟悉 age 的人可能会惊讶地发现秘密没有经过身份验证。这意味着每个拥有秘密文件写入权限的攻击者都可以修改秘密,因为公钥是公开的。这乍看之下似乎不是问题,因为更改配置本身就可以轻易暴露秘密。然而,审查配置更改比审查随机秘密(例如,4096 位 RSA 密钥)更容易。这个问题可以通过使用消息认证码(MAC)来解决,就像其他实现(如 GPG 或 sops)那样,但为了简单起见,age 中省略了这一功能。
此外,你应该只加密那些在未来被解密时你能够使其失效的秘密,并准备定期轮换它们,因为 age 在 2024 年 6 月 19 日之前不是后量子安全的。因此,如果威胁行为者可以访问你加密的密钥(例如通过在公共仓库中使用),他们可以利用现在收集,以后解密的策略来存储你的密钥,以便日后解密,包括发现重大漏洞导致秘密暴露的情况。详情请参见 https://github.com/FiloSottile/age/issues/578。
nix fmt 来格式化 nix 代码你可以使用以下命令运行测试:
nix flake check
你可以以交互模式运行集成测试,如下所示:
nix run .#checks.x86_64-linux.integration.driverInteractive
启动后,输入 run_tests() 来运行测试。
本项目基于 Mic92 创建的 sops-nix。感谢 Mic92 的灵感和建议。


免费创建高清无水印Sora视频
Vora是一个免费创建高清无水印Sora视频的AI工具


最适合小白的AI自动化工作流平台
无需编码,轻松生成可复用、可变现的AI自动化工作流

大模型驱动的Excel数据处理工具
基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。


AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


AI论文写作指导平台
AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。


AI一键生成PPT,就用博思AIPPT!
博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同 时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。


AI赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。


企业专属的AI法律顾问
iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。


稳定高效的流量提升解决方案,助力品牌曝光
稳定高效的流量提升解决方案,助力品牌曝光


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号