Add agenix secrets management
Phase 3: Encrypted secrets - Add secrets module with agenix integration - Create secrets/secrets.nix template for key definitions - Installer generates SSH key if missing - Installer creates personalized secrets.nix with user's key - Full documentation in docs/SECRETS.md Features: - Secrets encrypted with age using SSH keys - Decrypted automatically at system activation - Safe to commit .age files to git - Support for WiFi passwords, API keys, service credentials Usage: agenix -e secrets/my-secret.age age.secrets.my-secret.file = ./secrets/my-secret.age; Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
16
README.md
16
README.md
@@ -29,6 +29,7 @@ Download the ISO from the releases page and boot from it for a fresh installatio
|
|||||||
- **Classical Theme** - Earthy, vintage aesthetic inspired by historical paintings
|
- **Classical Theme** - Earthy, vintage aesthetic inspired by historical paintings
|
||||||
- **Bootloader Choice** - systemd-boot (default) or Limine (prettier, more features)
|
- **Bootloader Choice** - systemd-boot (default) or Limine (prettier, more features)
|
||||||
- **Plymouth** - Optional boot splash screen
|
- **Plymouth** - Optional boot splash screen
|
||||||
|
- **Secrets Management** - Encrypted secrets with agenix (WiFi passwords, API keys, etc.)
|
||||||
|
|
||||||
## Keybindings
|
## Keybindings
|
||||||
|
|
||||||
@@ -85,6 +86,21 @@ extraModules = [
|
|||||||
|
|
||||||
Copy your wallpapers to `~/.config/nomarchy/wallpapers/` and they'll be used for the random rotation.
|
Copy your wallpapers to `~/.config/nomarchy/wallpapers/` and they'll be used for the random rotation.
|
||||||
|
|
||||||
|
## Secrets Management
|
||||||
|
|
||||||
|
Nomarchy uses [agenix](https://github.com/ryantm/agenix) for encrypted secrets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a secret
|
||||||
|
cd ~/.config/nomarchy
|
||||||
|
agenix -e secrets/wifi-password.age
|
||||||
|
|
||||||
|
# Use in your config
|
||||||
|
age.secrets.wifi-password.file = ./secrets/wifi-password.age;
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/SECRETS.md](docs/SECRETS.md) for full documentation.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
149
docs/SECRETS.md
Normal file
149
docs/SECRETS.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Secrets Management with agenix
|
||||||
|
|
||||||
|
Nomarchy uses [agenix](https://github.com/ryantm/agenix) for managing encrypted secrets. Secrets are encrypted with age using SSH public keys and decrypted at system activation.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Secrets are encrypted** with your SSH public key(s)
|
||||||
|
2. **Stored in git** as `.age` files (safe to commit)
|
||||||
|
3. **Decrypted at boot** using the host's SSH private key
|
||||||
|
4. **Available at runtime** in `/run/agenix/`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Get Your SSH Public Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Get the Host's SSH Public Key
|
||||||
|
|
||||||
|
After first boot:
|
||||||
|
```bash
|
||||||
|
ssh-keyscan localhost 2>/dev/null | grep ed25519
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from your hardware-configuration.nix's host:
|
||||||
|
```bash
|
||||||
|
ssh-keyscan <hostname> 2>/dev/null | grep ed25519
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Edit secrets/secrets.nix
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let
|
||||||
|
user = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...";
|
||||||
|
host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...";
|
||||||
|
allKeys = [ user host ];
|
||||||
|
in {
|
||||||
|
"wifi-home.age".publicKeys = allKeys;
|
||||||
|
"github-token.age".publicKeys = allKeys;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create a Secret
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/.config/nomarchy
|
||||||
|
agenix -e secrets/wifi-home.age
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens your editor. Enter the secret content, save, and exit.
|
||||||
|
|
||||||
|
### 5. Use the Secret in Your Config
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# In your custom module
|
||||||
|
{ config, ... }: {
|
||||||
|
age.secrets.wifi-home = {
|
||||||
|
file = ./secrets/wifi-home.age;
|
||||||
|
owner = "root";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Use the decrypted secret
|
||||||
|
networking.wireless.networks."MyNetwork".pskRaw =
|
||||||
|
"file:${config.age.secrets.wifi-home.path}";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### WiFi Passwords
|
||||||
|
|
||||||
|
```nix
|
||||||
|
age.secrets.wifi-password.file = ./secrets/wifi.age;
|
||||||
|
|
||||||
|
# For NetworkManager (wpa_supplicant)
|
||||||
|
networking.wireless.networks."NetworkName".psk =
|
||||||
|
builtins.readFile config.age.secrets.wifi-password.path;
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Keys
|
||||||
|
|
||||||
|
```nix
|
||||||
|
age.secrets.github-token = {
|
||||||
|
file = ./secrets/github-token.age;
|
||||||
|
owner = "youruser";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
|
||||||
|
# In your shell config
|
||||||
|
home.sessionVariables.GITHUB_TOKEN =
|
||||||
|
"$(cat ${config.age.secrets.github-token.path})";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Credentials
|
||||||
|
|
||||||
|
```nix
|
||||||
|
age.secrets.syncthing-key = {
|
||||||
|
file = ./secrets/syncthing-key.age;
|
||||||
|
owner = "youruser";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.syncthing.key = config.age.secrets.syncthing-key.path;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Re-keying Secrets
|
||||||
|
|
||||||
|
If you change hosts or keys, re-encrypt all secrets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/.config/nomarchy
|
||||||
|
agenix --rekey
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- **Never commit unencrypted secrets**
|
||||||
|
- Keep your SSH private key secure
|
||||||
|
- The host's SSH key is generated during install
|
||||||
|
- Secrets are decrypted to a tmpfs (`/run/agenix/`)
|
||||||
|
- Consider using separate keys for different security levels
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "No identity found"
|
||||||
|
Ensure `/etc/ssh/ssh_host_ed25519_key` exists and is readable by root.
|
||||||
|
|
||||||
|
### "Failed to decrypt"
|
||||||
|
Check that the host's public key is in `secrets.nix` and re-encrypt:
|
||||||
|
```bash
|
||||||
|
agenix --rekey
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Secret file not found"
|
||||||
|
Make sure the `.age` file exists and the path in your config is correct.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.config/nomarchy/
|
||||||
|
├── secrets/
|
||||||
|
│ ├── secrets.nix # Key definitions
|
||||||
|
│ ├── wifi.age # Encrypted WiFi password
|
||||||
|
│ └── github.age # Encrypted API key
|
||||||
|
└── flake.nix
|
||||||
|
```
|
||||||
@@ -271,6 +271,53 @@ download_wallpapers() {
|
|||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setup_secrets() {
|
||||||
|
local secrets_dir="$HOME/.config/nomarchy/secrets"
|
||||||
|
mkdir -p "$secrets_dir"
|
||||||
|
|
||||||
|
echo -e "${BLUE}Setting up secrets management...${NC}"
|
||||||
|
|
||||||
|
# Check for SSH key
|
||||||
|
if [ ! -f "$HOME/.ssh/id_ed25519.pub" ]; then
|
||||||
|
echo -e "${YELLOW}No SSH key found. Generating one...${NC}"
|
||||||
|
ssh-keygen -t ed25519 -f "$HOME/.ssh/id_ed25519" -N "" -C "${username}@${hostname_input}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local user_key
|
||||||
|
user_key=$(cat "$HOME/.ssh/id_ed25519.pub")
|
||||||
|
|
||||||
|
# Create secrets.nix template
|
||||||
|
cat > "$secrets_dir/secrets.nix" << EOF
|
||||||
|
# Nomarchy Secrets Configuration
|
||||||
|
# Generated on $(date)
|
||||||
|
#
|
||||||
|
# To add a secret:
|
||||||
|
# 1. Add it to this file with the keys below
|
||||||
|
# 2. Run: agenix -e secrets/<name>.age
|
||||||
|
# 3. Reference in your NixOS config
|
||||||
|
|
||||||
|
let
|
||||||
|
# Your SSH public key (for encrypting)
|
||||||
|
user = "${user_key}";
|
||||||
|
|
||||||
|
# Host SSH key - add after first boot:
|
||||||
|
# Run: ssh-keyscan localhost 2>/dev/null | grep ed25519
|
||||||
|
# host = "ssh-ed25519 AAAA...";
|
||||||
|
|
||||||
|
allKeys = [ user ];
|
||||||
|
# After adding host key: allKeys = [ user host ];
|
||||||
|
in {
|
||||||
|
# Example secrets (uncomment to use):
|
||||||
|
# "wifi-password.age".publicKeys = allKeys;
|
||||||
|
# "api-key.age".publicKeys = allKeys;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo -e "${GREEN}Secrets directory created at ${secrets_dir}${NC}"
|
||||||
|
echo -e "${YELLOW}After first boot, add your host's SSH key to secrets.nix${NC}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
show_summary() {
|
show_summary() {
|
||||||
echo -e "${BOLD}Configuration Summary${NC}"
|
echo -e "${BOLD}Configuration Summary${NC}"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
@@ -347,6 +394,7 @@ main() {
|
|||||||
generate_config
|
generate_config
|
||||||
setup_flake
|
setup_flake
|
||||||
download_wallpapers
|
download_wallpapers
|
||||||
|
setup_secrets
|
||||||
apply_config
|
apply_config
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -6,5 +6,6 @@
|
|||||||
./services
|
./services
|
||||||
./programs
|
./programs
|
||||||
./performance
|
./performance
|
||||||
|
./secrets
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
42
modules/secrets/default.nix
Normal file
42
modules/secrets/default.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Secrets management with agenix
|
||||||
|
# Secrets are encrypted with age and decrypted at system activation
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
nomarchyConfig,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Check if secrets directory exists and has secrets
|
||||||
|
secretsPath = ../../secrets;
|
||||||
|
hasSecrets = builtins.pathExists secretsPath;
|
||||||
|
in {
|
||||||
|
# Import agenix module
|
||||||
|
imports = lib.optionals hasSecrets [
|
||||||
|
inputs.agenix.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
|
config = lib.mkIf hasSecrets {
|
||||||
|
# Install agenix CLI for managing secrets
|
||||||
|
environment.systemPackages = [
|
||||||
|
inputs.agenix.packages.${pkgs.system}.default
|
||||||
|
];
|
||||||
|
|
||||||
|
# Configure age to use SSH host keys for decryption
|
||||||
|
age.identityPaths = [
|
||||||
|
"/etc/ssh/ssh_host_ed25519_key"
|
||||||
|
"/etc/ssh/ssh_host_rsa_key"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Example secrets configuration (users override in their config)
|
||||||
|
# age.secrets = {
|
||||||
|
# wifi-password = {
|
||||||
|
# file = ../../secrets/wifi-password.age;
|
||||||
|
# owner = "root";
|
||||||
|
# group = "root";
|
||||||
|
# mode = "0400";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
};
|
||||||
|
}
|
||||||
41
secrets/secrets.nix
Normal file
41
secrets/secrets.nix
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Secrets configuration for agenix
|
||||||
|
#
|
||||||
|
# This file defines which public keys can decrypt each secret.
|
||||||
|
# Secrets are encrypted with `agenix -e <secret>.age`
|
||||||
|
#
|
||||||
|
# To set up:
|
||||||
|
# 1. Get your user's SSH public key: cat ~/.ssh/id_ed25519.pub
|
||||||
|
# 2. Get the host's SSH public key: ssh-keyscan localhost 2>/dev/null | grep ed25519
|
||||||
|
# 3. Add keys below and run: agenix -e <secret>.age
|
||||||
|
|
||||||
|
let
|
||||||
|
# User SSH public keys (for encrypting secrets on your machine)
|
||||||
|
# Example: user = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host";
|
||||||
|
|
||||||
|
# Host SSH public keys (for decrypting on target machines)
|
||||||
|
# Get with: ssh-keyscan <hostname> | grep ed25519
|
||||||
|
# Example: host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...";
|
||||||
|
|
||||||
|
# Define your keys here:
|
||||||
|
# user = "ssh-ed25519 AAAAC3...";
|
||||||
|
# host = "ssh-ed25519 AAAAC3...";
|
||||||
|
|
||||||
|
# For testing/example, use an empty list (secrets won't be encryptable)
|
||||||
|
allKeys = [
|
||||||
|
# user
|
||||||
|
# host
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
# Example secrets - uncomment and add keys above to use:
|
||||||
|
|
||||||
|
# WiFi password for specific network
|
||||||
|
# "wifi-home.age".publicKeys = allKeys;
|
||||||
|
|
||||||
|
# API keys
|
||||||
|
# "github-token.age".publicKeys = allKeys;
|
||||||
|
# "openai-api-key.age".publicKeys = allKeys;
|
||||||
|
|
||||||
|
# Application secrets
|
||||||
|
# "syncthing-key.age".publicKeys = allKeys;
|
||||||
|
# "mullvad-account.age".publicKeys = allKeys;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user