fix: Convert ssh keys to age keys
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
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
This commit is contained in:
174
secrets/DESIGN.md
Normal file
174
secrets/DESIGN.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 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**:
|
||||
```bash
|
||||
# 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**:
|
||||
```bash
|
||||
cd secrets/
|
||||
./update-age-keys.sh
|
||||
```
|
||||
|
||||
3. **Re-key existing secrets** (if needed):
|
||||
```bash
|
||||
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**:
|
||||
```bash
|
||||
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`:
|
||||
```bash
|
||||
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
|
||||
|
||||
```nix
|
||||
# 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:
|
||||
```nix
|
||||
# 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.)
|
||||
Reference in New Issue
Block a user