# Secrets Management with Agenix

This directory contains age-encrypted secrets for Athenix hosts. Secrets are automatically loaded based on directory structure.

## Directory Structure

```
secrets/
├── global/              # Secrets installed on ALL systems
│   ├── default.nix      # Optional: Custom config for global secrets
│   └── example.age      # Decrypted to /run/agenix/example on all hosts
├── nix-builder/         # Secrets only for nix-builder host
│   ├── default.nix      # Optional: Custom config for nix-builder secrets
│   └── ssh_host_ed25519_key.age
└── usda-dash/           # Secrets only for usda-dash host
    └── ssh_host_ed25519_key.age
```

## How It Works

1. **Global secrets** (`./secrets/global/*.age`) are installed on every system
2. **Host-specific secrets** (`./secrets/{hostname}/*.age`) are only installed on matching hosts
3. Only `.age` encrypted files are loaded; `.pub` public keys are ignored
4. Secrets are decrypted at boot to `/run/agenix/{secret-name}` with mode `0400` and owner `root:root`
5. **Custom configurations** can be defined in `default.nix` files within each directory

## Creating Secrets

### 1. Generate Age Keys

For a new host, generate an age identity:
```bash
# On the target system
mkdir -p /etc/age
age-keygen -o /etc/age/identity.key
chmod 600 /etc/age/identity.key
```

Or use SSH host keys (automatically done by Athenix):
```bash
# Get the age public key from SSH host key
nix shell nixpkgs#ssh-to-age -c sh -c 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
```

### 2. Store Public Keys

Save the public key to `secrets/{hostname}/` for reference:
```bash
# Example for nix-builder
echo "age1..." > secrets/nix-builder/identity.pub
```

Or from SSH host key:
```bash
cat /etc/ssh/ssh_host_ed25519_key.pub > secrets/nix-builder/ssh_host_ed25519_key.pub
```

**Then convert SSH keys to age format:**
```bash
cd secrets/
./update-age-keys.sh
```

This creates `.age.pub` files that `secrets.nix` uses for ragenix recipient configuration.

### 3. Encrypt Secrets

Encrypt a secret for specific hosts:
```bash
# For a single host
age -r age1publickey... -o secrets/nix-builder/my-secret.age <<< "secret value"

# For multiple hosts (recipient list)
age -R recipients.txt -o secrets/global/shared-secret.age < plaintext-file

# Using SSH public keys
age -R secrets/nix-builder/ssh_host_ed25519_key.pub \
    -o secrets/nix-builder/ssh_host_key.age < /etc/ssh/ssh_host_ed25519_key
```

### 4. Creating and Editing Secrets

**For new secrets**, use the helper script (automatically determines recipients):

```bash
cd secrets/

# Create a host-specific secret
./create-secret.sh usda-dash/database-url.age <<< "postgresql://..."

# Create a global secret
echo "shared-api-key" | ./create-secret.sh global/api-key.age

# From a file
./create-secret.sh nix-builder/ssh-key.age < ~/.ssh/id_ed25519
```

The script automatically includes the correct recipients:
- **Host-specific**: that host's keys + global keys + admin keys
- **Global**: all host keys + admin keys

**To edit existing secrets**, use `ragenix`:

```bash
# Install ragenix
nix shell github:yaxitech/ragenix

# Edit an existing secret (you must have a decryption key)
ragenix -e secrets/global/existing-secret.age

# Re-key all secrets after adding new hosts
ragenix -r
```

**Why create with `age` first?** Ragenix requires the `.age` file to exist before editing. The `secrets/secrets.nix` configuration auto-discovers recipients from the directory structure, but ragenix doesn't support wildcard patterns for creating new files.

**Recipient management** is automatic:
- **Global secrets** (`secrets/global/*.age`): encrypted for ALL hosts + admins
- **Host secrets** (`secrets/{hostname}/*.age`): encrypted for that host + global keys + admins
- **Admin keys** from `secrets/admins/*.age.pub` allow editing from your workstation

After creating new .age files with `age`, use `ragenix -r` to re-key all secrets with the updated recipient configuration.

To add admin keys for editing secrets:
```bash
# Generate personal age key
age-keygen -o ~/.config/age/personal.key

# Extract public key and add to secrets
grep "public key:" ~/.config/age/personal.key | cut -d: -f2 | tr -d ' ' > secrets/admins/your-name.age.pub
```

## Using Secrets in Configuration

Secrets are automatically loaded. Reference them in your NixOS configuration:

```nix
# Example: Using a secret for a service
services.myservice = {
  enable = true;
  passwordFile = config.age.secrets.my-password.path;  # /run/agenix/my-password
};

# Example: Setting up SSH host key from secret
services.openssh = {
  hostKeys = [{
    path = config.age.secrets.ssh_host_ed25519_key.path;
    type = "ed25519";
  }];
};
```

## Custom Secret Configuration

For secrets needing custom permissions, use `athenix.sw.secrets.extraSecrets`:

```nix
# In inventory.nix or host config
athenix.sw.secrets.extraSecrets = {
  "nginx-cert" = {
    file = ./secrets/custom/cert.age;
    mode = "0440";
    owner = "nginx";
    group = "nginx";
  };
};
```

### Using default.nix in Secret Directories

Alternatively, create a `default.nix` file in the secret directory to configure all secrets in that directory:

```nix
# secrets/global/default.nix
{
  "example" = {
    mode = "0440";        # Custom file mode (default: "0400")
    owner = "nginx";      # Custom owner (default: "root")
    group = "nginx";      # Custom group (default: "root")
    path = "/run/secrets/example";  # Custom path (default: /run/agenix/{name})
  };
  
  "api-key" = {
    mode = "0400";
    owner = "myservice";
    group = "myservice";
  };
}
```

The `default.nix` file should return an attribute set where:
- **Keys** are secret names (without the `.age` extension)
- **Values** are configuration objects with optional fields:
  - `mode` - File permissions (string, e.g., `"0440"`)
  - `owner` - File owner (string, e.g., `"nginx"`)
  - `group` - File group (string, e.g., `"nginx"`)
  - `path` - Custom installation path (string, e.g., `"/custom/path"`)
  
Secrets not listed in `default.nix` will use default settings.

## Security Best Practices

1. **Never commit unencrypted secrets** - Only `.age` and `.pub` files belong in this directory
2. **Use host-specific secrets** when possible - Limit exposure by using hostname directories
3. **Rotate secrets regularly** - Re-encrypt with new keys periodically
4. **Backup age identity keys** - Store `/etc/age/identity.key` securely offline
5. **Use SSH keys** - Leverage existing SSH host keys for age encryption when possible
6. **Pin to commits** - When using external secrets modules, always use `rev = "commit-hash"`

## Converting SSH Keys to Age Format

```bash
# Convert SSH public key to age public key
nix shell nixpkgs#ssh-to-age -c ssh-to-age < secrets/nix-builder/ssh_host_ed25519_key.pub

# Convert SSH private key to age identity (for editing secrets)
nix shell nixpkgs#ssh-to-age -c ssh-to-age -private-key -i ~/.ssh/id_ed25519
```

## Disabling Automatic Secrets

To disable automatic secret loading:

```nix
# In inventory.nix or host config
athenix.sw.secrets.enable = false;
```

## Troubleshooting

### Secret not found
- Ensure the `.age` file exists in `secrets/global/` or `secrets/{hostname}/`
- Check `hostname` matches directory name: `echo $HOSTNAME` on the target system
- Run `nix flake check` to verify secrets are discovered

### Permission denied
- Verify secret permissions in `/run/agenix/`
- Check if custom permissions are needed (use `extraSecrets`)
- Ensure the service user/group has access to the secret file

### Age decrypt failed
- Verify the host's age identity exists: `ls -l /etc/age/identity.key`
- Check that the secret was encrypted with the host's public key
- Confirm SSH host key hasn't changed (would change derived age key)

## References

- [ragenix GitHub](https://github.com/yaxitech/ragenix)
- [agenix upstream](https://github.com/ryantm/agenix)
- [age encryption tool](https://age-encryption.org/)
