Files
athenix/secrets/DESIGN.md
UGA Innovation Factory dd19d1488a
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m42s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 20s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
CI / Build and Publish Documentation (push) Successful in 11s
fix: Convert ssh keys to age keys
2026-01-30 19:41:34 +00:00

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 .age encrypted files from secrets/ 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 .pub files in directories
  • Supports custom secret configuration via default.nix in 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.pub files (converted from SSH keys)
  • Generates recipient lists based on secret location:
    • secrets/global/*.age → ALL hosts + admins
    • secrets/{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

  1. Capture SSH host key:

    # From the running system
    cat /etc/ssh/ssh_host_ed25519_key.pub > secrets/new-host/ssh_host_ed25519_key.pub
    
  2. Convert to age format:

    cd secrets/
    ./update-age-keys.sh
    
  3. Re-key existing secrets (if needed):

    ragenix -r
    

Creating a New Secret

  1. Choose location:

    • secrets/global/ → all systems can decrypt
    • secrets/{hostname}/ → only that host can decrypt
  2. Create/edit secret:

    ragenix -e secrets/global/my-secret.age
    
  3. 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.pub files)
  • secrets/secrets.nix auto-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

  1. Public keys in git: Safe to commit (only public keys, .age.pub and .pub)
  2. Private keys on hosts: Never leave the system (/etc/ssh/ssh_host_*_key, /etc/age/identity.key)
  3. Encrypted secrets in git: Safe to commit (.age files)
  4. 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

  1. Zero manual recipient management: Just add directories and keys
  2. Cross-host secret creation: Any host can manage secrets for others
  3. Automatic host discovery: Syncs with inventory structure
  4. Flexible permission model: Global vs host-specific + custom configs
  5. Version controlled: All public data in git, auditable history
  6. Secure by default: Private keys never shared, secrets encrypted at rest

Limitations

  1. Requires age key conversion: SSH keys must be converted to age format (automated by script)
  2. Bootstrap chicken-egg: Need initial host key before encrypting secrets (capture from first boot or generate locally)
  3. No secret rotation automation: Must manually re-key with ragenix -r
  4. Git history contains old encrypted versions: Rotating keys doesn't remove old ciphertexts from history

Future Enhancements

  • Auto-run update-age-keys.sh in 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.)