agenix

agenix

NixOS加密秘密管理和部署的轻量级解决方案

agenix是一个轻量级Nix库,通过SSH密钥对实现NixOS系统中加密信息的安全管理和部署。它包含命令行工具和NixOS模块,支持多密钥加密、自动解密和挂载等功能,简化了敏感数据在NixOS环境中的处理流程。

agenixNixOS加密密钥管理部署Github开源项目

agenix - 用于 NixOS 的 age 加密机密

agenix 是一个小巧便捷的 Nix 库,用于使用常见的公钥-私钥 SSH 密钥对安全地管理和部署机密: 你可以在源机器上使用多个公共 SSH 密钥加密一个机密(密码、访问令牌等), 然后将该加密的机密部署到任何拥有与这些公钥之一对应的私有 SSH 密钥的目标机器上。 该项目包含两个部分:

  1. 一个 agenix 命令行应用程序(CLI),用于将机密加密为可复制到 Nix 存储的安全 .age 文件。
  2. 一个 agenix NixOS 模块,方便地
    • 将这些加密的机密(.age 文件)添加到 Nix 存储中,以便可以像任何其他 Nix 包一样使用 nixos-rebuild 或类似工具部署。
    • 在目标机器上使用该机器上的私有 SSH 密钥自动解密
    • 自动将这些解密的机密挂载到一个众所周知的路径(如 /run/agenix/...)以供使用。

目录

问题和解决方案

Nix 存储中的所有文件都可以被任何系统用户读取,因此不适合包含明文机密。许多现有工具(如 NixOps deployment.keys)与 nixos-rebuild 分开部署机密,这使得部署、缓存和审计更加困难。带外机密管理也不太可重现。

agenix 通过使用您预先存在的 SSH 密钥基础设施和 age 将机密加密到 Nix 存储中来解决这些问题。机密在 NixOS 系统激活期间使用 SSH 主机私钥解密。

特性

  • 机密使用 SSH 密钥加密
  • 无需 GPG
  • 代码量很少,便于审计
  • 加密的机密存储在 Nix 存储中,因此不需要单独的分发机制

注意事项

  • 密码保护的 ssh 密钥:由于 age 不支持 ssh-agent,密码保护的 ssh 密钥无法很好地工作。例如,如果你需要重新加密 20 个机密,你将不得不输入 20 次密码。

安装

<details> <summary>

通过 niv 安装

</summary>

首先将其添加到 niv:

$ niv add ryantm/agenix

通过 niv 安装模块

然后在 configuration.niximports 列表中添加以下内容:

{ imports = [ "${(import ./nix/sources.nix).agenix}/modules/age.nix" ]; }

通过 niv 安装 CLI

要安装 agenix 二进制文件:

{ environment.systemPackages = [ (pkgs.callPackage "${(import ./nix/sources.nix).agenix}/pkgs/agenix.nix" {}) ]; }
</details> <details> <summary>

通过 nix-channel 安装

</summary>

以 root 身份运行:

$ sudo nix-channel --add https://github.com/ryantm/agenix/archive/main.tar.gz agenix $ sudo nix-channel --update

通过 nix-channel 安装模块

然后在 configuration.niximports 列表中添加以下内容:

{ imports = [ <agenix/modules/age.nix> ]; }

通过 nix-channel 安装 CLI

要安装 agenix 二进制文件:

{ environment.systemPackages = [ (pkgs.callPackage <agenix/pkgs/agenix.nix> {}) ]; }
</details> <details> <summary>

通过 fetchTarball 安装

</summary>

通过 fetchTarball 安装模块

在您的 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" ]; }

通过 fetchTarball 安装 CLI

要安装 agenix 二进制文件:

{ environment.systemPackages = [ (pkgs.callPackage "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/pkgs/agenix.nix" {}) ]; }
</details> <details> <summary>

通过 Flakes 安装

</summary>

通过 Flakes 安装模块

{ 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 ]; }; }; }

通过 Flakes 安装 CLI

您可以临时运行 CLI 工具而无需安装:

nix run github:ryantm/agenix -- --help

但您也可以将其永久添加到 NixOS 模块中 (将系统 "x86_64-linux" 替换为您的系统):

{ environment.systemPackages = [ agenix.packages.x86_64-linux.default ]; }

例如,在您的 flake.nix 文件中:

{ 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 ]; } ]; }; }; }
</details>

教程

  1. 您要部署机密的系统应该已经存在并运行 sshd,以便它在 /etc/ssh/ 中生成了 SSH 主机密钥。

  2. 创建一个目录来存储机密和 secrets.nix 文件,用于列出机密及其公钥:

    $ mkdir secrets $ cd secrets $ touch secrets.nix

    这个 secrets.nix 文件不会导入到您的 NixOS 配置中。 它只用于 agenix CLI 工具(示例如下)以了解用于加密的公钥。

  3. 将公钥添加到您的 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
  ...
  1. 创建一个秘密文件:

    $ agenix -e secret1.age

    它将在你的 $EDITOR 环境变量配置的应用程序中打开一个临时文件。 当你保存该文件时,其内容将使用 secrets.nix 文件中提到的所有公钥进行加密。

  2. 将秘密添加到 NixOS 模块配置中:

    { age.secrets.secret1.file = ../secrets/secret1.age; }

    age.secrets 属性集包含一个秘密时,agenix NixOS 模块稍后会自动解密并将该秘密挂载到默认路径 /run/agenix/secret1 下。 这里 secret1.age 文件成为你的 NixOS 部署的一部分,即移动到 Nix store 中。

  3. 在你的配置中引用秘密的挂载路径:

    { users.users.user1 = { isNormalUser = true; passwordFile = config.age.secrets.secret1.path; }; }

    你可以在其他配置中已经引用(稍后)未加密秘密的挂载路径。 所以默认情况下 config.age.secrets.secret1.path 将包含路径 /run/agenix/secret1

  4. 像往常一样使用 nixos-rebuild其他部署工具

    secret1.age 文件将像任何其他 Nix 包一样被复制到目标机器。 然后它将被解密并按照先前描述的方式挂载。

  5. 编辑秘密文件:

    $ agenix -e secret1.age

    它假设你的 SSH 私钥在 ~/.ssh/ 中。 为了解密并打开一个 .age 文件进行编辑,你需要用于加密该文件的公钥之一对应的私钥。你可以使用 -i 明确传递你想使用的私钥,例如

    $ agenix -e secret1.age -i ~/.ssh/id_ed25519

参考

age 模块参考

age.secrets

age.secrets 是秘密的属性集。你总是需要使用这个配置选项。默认为 {}

age.secrets.<name>.file

age.secrets.<name>.file 是此秘密的加密 .age 文件的路径。这是唯一必需的秘密选项。

示例:

{ age.secrets.monitrc.file = ../secrets/monitrc.age; }

age.secrets.<name>.path

age.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; }; }
builtins.readFile 反模式
{ # 不要这样做! config.password = builtins.readFile config.age.secrets.secret1.path; }

这可能会导致明文被放置到全局可读的 Nix store 中。相反,让你的服务在运行时读取明文路径。

age.secrets.<name>.mode

age.secrets.<name>.mode 是解密后的秘密的权限模式,格式为 chmod 可以理解的格式。通常,你只需要与 age.secrets.<name>.ownerage.secrets.<name>.group 结合使用。

示例:

{ age.secrets.nginx-htpasswd = { file = ../secrets/nginx.htpasswd.age; mode = "770"; owner = "nginx"; group = "nginx"; }; }

age.secrets.<name>.owner

age.secrets.<name>.owner 是解密文件所有者的用户名。通常,你只需要与 age.secrets.<name>.modeage.secrets.<name>.group 结合使用。

示例:

{ age.secrets.nginx-htpasswd = { file = ../secrets/nginx.htpasswd.age; mode = "770"; owner = "nginx"; group = "nginx"; }; }

age.secrets.<name>.group

age.secrets.<name>.group 是解密文件的组名。通常,你只需要与 age.secrets.<name>.ownerage.secrets.<name>.mode 结合使用。

示例:

{ age.secrets.nginx-htpasswd = { file = ../secrets/nginx.htpasswd.age; mode = "770"; owner = "nginx"; group = "nginx"; }; }

age.secrets.<name>.symlink

age.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>.name

age.secrets.<name>.name 是解密后文件的名称字符串。默认为属性路径中的 <name>,但如果你希望文件名与属性名不同,可以单独设置。

一个秘密名称与其属性路径不同的示例:

{ age.secrets.monit = { name = "monitrc"; file = ../secrets/monitrc.age; }; }

age.ageBin

age.ageBinage 二进制文件路径的字符串。通常,你不需要更改这个。默认为 age/bin/age

覆盖 age.ageBin 的示例:

{pkgs, ...}:{ age.ageBin = "${pkgs.age}/bin/age"; }

age.identityPaths

age.identityPaths 是一个尝试用于解密秘密的接收者密钥路径列表。默认情况下,它是 config.services.openssh.hostKeys 中的 rsaed25519 密钥,在 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.secretsDir

age.secretsDir 是默认情况下秘密被符号链接到的目录。通常情况下,你不需要更改这个设置。默认值为 /run/agenix

覆盖 age.secretsDir 的示例:

{ age.secretsDir = "/run/keys"; }

age.secretsMountPoint

age.secretsMountPoint 是在秘密被符号链接之前创建秘密世代的目录。通常情况下,你不需要更改这个设置。默认值为 /run/agenix.d

覆盖 age.secretsMountPoint 的示例:

{ age.secretsMountPoint = "/run/secret-generations"; }

agenix 命令行界面参考

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 文件中读取身份来改进。)

覆盖 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 中省略了这一功能。

此外,你应该只加密那些在未来被解密时你能够使其失效的秘密,并准备定期轮换它们,因为 age2024 年 6 月 19 日之前不是后量子安全的。因此,如果威胁行为者可以访问你加密的密钥(例如通过在公共仓库中使用),他们可以利用现在收集,以后解密的策略来存储你的密钥,以便日后解密,包括发现重大漏洞导致秘密暴露的情况。详情请参见 https://github.com/FiloSottile/age/issues/578。

贡献

  • 主分支受保护,不允许直接推送
  • 所有更改必须通过 GitHub PR 审核并获得至少一个批准
  • PR 标题和提交消息应该至少以以下类别之一为前缀:
    • contrib - 改进项目开发的事项
    • doc - 文档
    • feature - 新功能
    • fix - 错误修复
  • 请为新功能更新或制作集成测试
  • 使用 nix fmt 来格式化 nix 代码

测试

你可以使用以下命令运行测试:

nix flake check

你可以以交互模式运行集成测试,如下所示:

nix run .#checks.x86_64-linux.integration.driverInteractive

启动后,输入 run_tests() 来运行测试。

致谢

本项目基于 Mic92 创建的 sops-nix。感谢 Mic92 的灵感和建议。

编辑推荐精选

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

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

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

下拉加载更多