5.9 KiB
Athenix Secrets System Design
Overview
The Athenix secrets management system integrates ragenix (agenix) with automatic host discovery based on the repository's fleet inventory structure. It provides a seamless workflow for managing encrypted secrets across all systems.
Architecture
Auto-Discovery Module (sw/secrets.nix)
Purpose: Automatically load and configure secrets at system deployment time.
Features:
- Discovers
.ageencrypted files fromsecrets/directories - Loads global secrets from
secrets/global/on ALL systems - Loads host-specific secrets from
secrets/{hostname}/on matching hosts - Auto-configures decryption keys based on
.pubfiles in directories - Supports custom secret configuration via
default.nixin each directory
Key Behaviors:
- Secrets are decrypted to
/run/agenix/{name}at boot - Identity paths include: system SSH keys + global keys + host-specific keys
- Host-specific secrets override global secrets with the same name
Dynamic Recipients Configuration (secrets/secrets.nix)
Purpose: Generate ragenix recipient configuration from directory structure.
Features:
- Automatically discovers hosts from
secrets/subdirectories - Reads age public keys from
.age.pubfiles (converted from SSH keys) - Generates recipient lists based on secret location:
secrets/global/*.age→ ALL hosts + adminssecrets/{hostname}/*.age→ that host + global keys + admins
- Supports admin keys in
secrets/admins/for secret editing
Key Behaviors:
- No manual recipient list maintenance required
- Adding a new host = create directory + add .pub key + run
update-age-keys.sh - Works with ragenix CLI:
ragenix -e,ragenix -r
Workflow
Adding a New Host
-
Capture SSH host key:
# From the running system cat /etc/ssh/ssh_host_ed25519_key.pub > secrets/new-host/ssh_host_ed25519_key.pub -
Convert to age format:
cd secrets/ ./update-age-keys.sh -
Re-key existing secrets (if needed):
ragenix -r
Creating a New Secret
-
Choose location:
secrets/global/→ all systems can decryptsecrets/{hostname}/→ only that host can decrypt
-
Create/edit secret:
ragenix -e secrets/global/my-secret.age -
Recipients are auto-determined from
secrets.nix:- Global secrets: all host keys + admin keys
- Host-specific: that host + global keys + admin keys
Cross-Host Secret Management
Any Athenix host can manage secrets for other hosts because:
- All public keys are in the repository (
*.age.pubfiles) secrets/secrets.nixauto-generates recipient lists- Hosts decrypt using their own private keys (not shared)
Example: From nix-builder, create a secret for usda-dash:
ragenix -e secrets/usda-dash/database-password.age
# Encrypted for usda-dash's public key + admins
# usda-dash will decrypt using its private key at /etc/ssh/ssh_host_ed25519_key
Directory Structure
secrets/
├── secrets.nix # Auto-generated recipient config
├── update-age-keys.sh # Helper to convert SSH → age keys
├── README.md # User documentation
├── DESIGN.md # This file
│
├── global/ # Secrets for ALL hosts
│ ├── *.pub # SSH public keys
│ ├── *.age.pub # Age public keys (generated)
│ ├── *.age # Encrypted secrets
│ └── default.nix # Optional: custom secret config
│
├── {hostname}/ # Host-specific secrets
│ ├── *.pub
│ ├── *.age.pub
│ ├── *.age
│ └── default.nix
│
└── admins/ # Admin keys for editing
└── *.age.pub
Security Model
- Public keys in git: Safe to commit (only public keys,
.age.puband.pub) - Private keys on hosts: Never leave the system (
/etc/ssh/ssh_host_*_key,/etc/age/identity.key) - Encrypted secrets in git: Safe to commit (
.agefiles) - Decrypted secrets: Only in memory/tmpfs (
/run/agenix/*)
Integration Points
With NixOS Configuration
# Access decrypted secrets in any NixOS module
config.age.secrets.my-secret.path # => /run/agenix/my-secret
# Example usage
services.myapp.passwordFile = config.age.secrets.database-password.path;
With Inventory System
The system automatically matches secrets/{hostname}/ to hostnames from inventory.nix. No manual configuration needed.
With External Modules
External user/system modules can reference secrets:
# In external module
{ config, ... }:
{
programs.git.extraConfig.credential.helper =
"store --file ${config.age.secrets.git-credentials.path}";
}
Advantages
- Zero manual recipient management: Just add directories and keys
- Cross-host secret creation: Any host can manage secrets for others
- Automatic host discovery: Syncs with inventory structure
- Flexible permission model: Global vs host-specific + custom configs
- Version controlled: All public data in git, auditable history
- Secure by default: Private keys never shared, secrets encrypted at rest
Limitations
- Requires age key conversion: SSH keys must be converted to age format (automated by script)
- Bootstrap chicken-egg: Need initial host key before encrypting secrets (capture from first boot or generate locally)
- No secret rotation automation: Must manually re-key with
ragenix -r - Git history contains old encrypted versions: Rotating keys doesn't remove old ciphertexts from history
Future Enhancements
- Auto-run
update-age-keys.shin pre-commit hook - Integrate with inventory.nix to auto-generate host directories
- Support for multiple identity types per host
- Automated secret rotation scheduling
- Integration with hardware security modules (YubiKey, etc.)