mcpField
A
agenix-secrets

agenix-secrets

github.com/edmundmiller安全8047 次安装今天更新
代码仓库

关于 agenix-secrets

用于创建 age 加密密钥并将其接入 NixOS 模块的工作流技能。内容涵盖接收者公钥、自动配置密钥、服务所有权和常见部署模式。

主要功能

  1. 01将公钥映射到加密文件
  2. 02使用 age 接收者创建密钥
  3. 03自动将密钥接入 NixOS
  4. 04配置密钥所有者和路径
  5. 05说明服务及 Home Assistant 模式

适用场景

  1. 01为主机添加加密密钥
  2. 02向 NixOS 服务提供凭据
  3. 03管理 Home Assistant 的 secrets.yaml 值
  4. 04更新现有加密环境文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/edmundmiller/dotfiles --skill agenix-secrets

安装后在 Claude Code 中输入/agenix-secrets确认已加载。

详细介绍

Agenix 密钥

创建由 age 加密的密钥,并将其接入 NixOS 模块。

仓库密钥布局

hosts/<host>/secrets/
├── secrets.nix          # Public key → .age file mapping (NOT imported into NixOS)
├── my-secret.age        # Encrypted secret file
└── restic/              # Subdirectories supported
    └── repo.age
hosts/shared/secrets/
├── secrets.nix          # Shared cross-host secrets
└── host-keys.nix        # Maps hostname → public key for filtering

自动接入

modules/agenix.nix 会根据 secrets.nix 自动生成 age.secrets:

  • 每个 "name.age" 条目都会变成 age.secrets.<name>(去掉 .age 后缀)
  • 默认 owner = config.user.name(通过 mkDefault 设置,可覆盖)
  • 默认 file 指向 hosts/<host>/secrets/<name>.age
  • 激活时解密到 /run/agenix/<name>

无需设置 age.secrets.<name>.file——仅当默认用户不应拥有该密钥时,才覆盖 owner/group/mode。

工作流:添加新密钥

1. 在 secrets.nix 中添加条目
# hosts/<host>/secrets/secrets.nix
let
  edmundmiller = "ssh-ed25519 AAAAC3...";
  nuc = "ssh-ed25519 AAAAC3...";
in {
  "my-secret.age".publicKeys = [ edmundmiller nuc ];
}

用户密钥和主机密钥都必不可少——用户密钥用于加密和编辑,主机密钥用于部署时解密。

2. 创建加密文件
cd hosts/<host>/secrets

# Pipe content (non-interactive)
printf 'SECRET_VALUE' | age \
  -r "ssh-ed25519 AAAA...user" \
  -r "ssh-ed25519 AAAA...host" \
  -o my-secret.age

# Verify decryption
age -d -i ~/.ssh/id_ed25519 my-secret.age

agenix -e CLI 需要交互式编辑器。对于 Agent 工作流,请直接使用 age,并通过 -r 指定 secrets.nix 中每个接收方的公钥。

3. 在 NixOS 模块中引用
# Simple: file path reference (most common)
services.myapp.environmentFile = config.age.secrets.my-secret.path;

# Override owner when service runs as different user
age.secrets.my-secret = {
  owner = "myapp";
  group = "myapp";
};
4. 部署
# Stage by directory — NOT by filename (staging a .age path directly is blocked by the sandbox)
git add hosts/<host>/secrets/
git commit -m "secrets: add my-secret"
git push && hey nuc

沙盒注意事项: pi 沙盒会阻止显式引用 .age 文件路径的 git add(以及任何 bash 命令)。暂存父目录即可绕过限制——git add hosts/<host>/secrets/ 会暂存目录中的所有更改,而不会直接指定 .age 文件。

模式:通过 !secret 使用 HA secrets.yaml

Home Assistant 的 YAML 支持 !secret key 引用。nixpkgs 的 HA 模块会对生成的 YAML 进行后处理,以移除 ! 标签两侧的引号(sed 会将 '!secret foo' 转换为 !secret foo)。

# In HA module config:
services.home-assistant.config.homeassistant = {
  latitude = "!secret latitude";    # Unquoted by nixpkgs sed post-processor
  longitude = "!secret longitude";
};

# Decrypt with correct owner and symlink into HA config dir:
age.secrets.hass-secrets = {
  owner = "hass";
  group = "hass";
};
systemd.tmpfiles.settings."10-hass-nix-yaml" = {
  "${config.services.home-assistant.configDir}/secrets.yaml" = {
    L.argument = config.age.secrets.hass-secrets.path;
  };
};

.age 文件包含标准格式的 HA secrets.yaml:

latitude: 33.083423
longitude: -96.820367

模式:更新现有的 .age 文件

解密 → 修改 → 重新加密。常见于向现有环境文件添加变量时。

# Decrypt to temp
age -d -i ~/.ssh/id_ed25519 hosts/<host>/secrets/my-env.age > /tmp/my-env.txt

# Modify
echo "NEW_VAR=value" >> /tmp/my-env.txt

# Re-encrypt (overwrites existing .age)
age \
  -r "ssh-ed25519 AAAA...user" \
  -r "ssh-ed25519 AAAA...host" \
  -o hosts/<host>/secrets/my-env.age /tmp/my-env.txt

# Clean up
rm /tmp/my-env.txt

# Verify
age -d -i ~/.ssh/id_ed25519 hosts/<host>/secrets/my-env.age

模式:1Password + Agenix(登录凭据)

对于需要用户名和密码的服务,请同时存入 1Password(供人访问)和 agenix(供机器访问)。

# 1. Generate creds and store in 1Password
PASSWORD=$(op item create \
  --category=login \
  --title="MyService" \
  --vault="Private" \
  --url="http://nuc:8080" \
  --generate-password="32,letters,digits" \
  username="emiller" \
  --format=json | jq -r '.fields[] | select(.id == "password") | .value')

# 2. Create agenix env file
printf 'MYSERVICE_USER=emiller\nMYSERVICE_PASSWORD=%s' "$PASSWORD" | age \
  -r "ssh-ed25519 AAAA...user" \
  -r "ssh-ed25519 AAAA...host" \
  -o hosts/<host>/secrets/myservice-env.age

# 3. Add to secrets.nix, wire environmentFile, set owner (see below)

模式:覆盖所有者的服务环境文件

大多数 NixOS 服务以专用用户身份运行(而非 emiller)。请覆盖密钥的所有者,以便服务能够读取它。

# In host config (e.g., hosts/nuc/default.nix):
modules.services.myservice = {
  enable = true;
  environmentFile = config.age.secrets.myservice-env.path;
};

# Override default owner (emiller) → service user
age.secrets.myservice-env.owner = "myservice";

服务用户名通常与服务名称一致。如不确定,可在目标主机上使用 grep -r "DynamicUser\|User=" /etc/systemd/system/<service>* 检查。

密钥公钥

从 hosts/<host>/secrets/secrets.nix 读取,不要硬编码。该文件定义了 edmundmiller(用户 SSH 密钥)和特定于主机的密钥(例如 nuc)。

常见问题

  • 切勿对密钥路径使用 builtins.readFile——这会将明文泄露到所有用户均可读取的 Nix 存储中
  • agenix -e 在非交互式 Shell 中会失败——请改为直接使用 age -r
  • 忘记在 publicKeys 中添加主机密钥——密钥将无法在目标机器上解密
  • 所有者错误——服务无法读取 /run/agenix/<name>(默认所有者为 config.user.name)
  • 共享密钥需要在 hosts/shared/secrets/secrets.nix 中添加条目,并在 host-keys.nix 中添加主机密钥

评论

支持 Markdown