Project Icon

agenix

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

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

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 次密码。

安装

通过 niv 安装

首先将其添加到 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" {}) ];
}

通过 nix-channel 安装

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

通过 fetchTarball 安装

通过 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" {}) ];
}

通过 Flakes 安装

通过 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 ];
        }
      ];
    };
  };
}

教程

  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 的灵感和建议。

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

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

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号