Age Key Management Tools
Documentation for the lib module
This directory contains derivation-based tools for managing AGE keys with SOPS in Stackpanel projects.
Overview
The age key management system provides:
- Derivation-based tools: Pure Nix derivations instead of shell scripts
- Automatic key fetching: Integration with 1Password for secure key retrieval
- Local caching: Keys are cached in
.keys/to avoid repeated fetches - SOPS integration: Compatible with
SOPS_AGE_KEY_CMDenvironment variable
Architecture
age-key-tools.nix # Core derivations (fetchAgeKey, ageKeyCmd, etc.)
age-key-cmd.nix # NixOS/home-manager module interface
age-key-cmd.sh # [DEPRECATED] Old shell script - kept for referenceFiles
age-key-tools.nix
Core derivation-based tools:
fetchAgeKey: Fetch age keys from 1PasswordreadAgeKeys: Read cached age keys from diskageKeyCmd: Main command compatible withSOPS_AGE_KEY_CMDsopsWithAgeKey: Wrappedsopscommand with automatic key resolutioncheckAgeKeys: Health check tool to verify keys are available
age-key-cmd.nix
Module interface for declarative configuration:
stackpanel.secrets.age-key-cmd = {
enable = true;
keysDir = ".keys";
onePassword = {
enable = true;
account = "voytravel";
item = "op://voy-508-shared/sops-dev";
};
autoSetup = true; # Auto-configure SOPS_AGE_KEY_CMD
};age-key-cmd.sh [DEPRECATED]
Old shell script implementation. Kept for reference but should not be used in new code. The derivation-based approach in age-key-tools.nix provides:
- Better reproducibility (explicit dependencies)
- Type safety and validation
- Integration with Nix module system
- No relative path issues
Usage
Basic Usage (Module-based)
Enable in your flake.nix or module configuration:
{
imports = [ ./nix/stackpanel/secrets/lib/age-key-cmd.nix ];
stackpanel.secrets.age-key-cmd = {
enable = true;
# Optional: customize keys directory
# keysDir = ".secrets/keys";
};
}This automatically:
- Adds age key tools to your devShell
- Sets
SOPS_AGE_KEY_CMDenvironment variable - Adds
.keys/to.gitignore - Provides helper commands:
age:fetch,age:check
Direct Usage (Package-based)
If you need the tools without the module system:
{
ageKeyTools = pkgs.callPackage ./nix/stackpanel/secrets/lib/age-key-tools.nix {
keysDir = ".keys";
opAccount = "voytravel";
opItem = "op://voy-508-shared/sops-dev";
};
devShells.default = pkgs.mkShell {
packages = [ ageKeyTools.ageKeyCmd ];
shellHook = ''
export SOPS_AGE_KEY_CMD="${ageKeyTools.ageKeyCmd}/bin/age-key-cmd"
'';
};
}Command Line Usage
Fetch keys from 1Password
# Using module helper
age:fetch
# Or directly
fetch-age-keyCheck available keys
# Using module helper
age:check
# Or directly
check-age-keysUse with SOPS
# Automatic (if module is enabled)
sops .stack/secrets/dev/web.sops.yaml
# Manual
SOPS_AGE_KEY_CMD=age-key-cmd sops .stack/secrets/dev/web.sops.yaml
# Using wrapped sops command
${ageKeyTools.sopsWithAgeKey}/bin/sops .stack/secrets/dev/web.sops.yamlConfiguration
Environment Variables
Runtime configuration (overrides build-time defaults):
SOPS_KEYS_DIR: Override keys directory locationOP_ACCOUNT: Override 1Password account nameOP_ITEM: Override 1Password item referenceAGE_KEY_NAME: Name for cached key files (default: "dev")
Build-time Configuration
When calling age-key-tools.nix:
pkgs.callPackage ./age-key-tools.nix {
keysDir = ".keys"; # Where to cache keys
opAccount = "voytravel"; # 1Password account
opItem = "op://vault/item"; # 1Password item reference
}1Password Integration
Item Format
The 1Password item should have:
- username field: AGE public key (
age1...) - password field: AGE private key (
AGE-SECRET-KEY-1...)
Authentication
Ensure you're authenticated with 1Password CLI:
op signinSecurity Notes
- Keys are cached in
.keys/directory (gitignored by default) - Private key files have
600permissions - Keys are only fetched when not found in cache
- Use separate 1Password items for different environments (dev/staging/prod)
How It Works
Key Resolution Flow
1. SOPS calls $SOPS_AGE_KEY_CMD
↓
2. age-key-cmd checks .keys/ directory
↓
3a. Keys found? → Return keys to SOPS
↓
3b. No keys? → Call fetch-age-key
↓
4. fetch-age-key queries 1Password CLI
↓
5. Cache keys to .keys/
↓
6. Return keys to SOPSAdvantages Over Shell Script
Old approach (age-key-cmd.sh):
- Relative paths (
$(dirname $(realpath "$0"))) - Implicit dependencies (relies on system PATH)
- No version pinning
- Hard to test/reproduce
New approach (age-key-tools.nix):
- Absolute Nix store paths
- Explicit dependencies (coreutils, findutils, etc.)
- Reproducible builds
- Module system integration
- Type-safe configuration
Troubleshooting
"No age keys available"
# Check if 1Password CLI is installed and authenticated
op signin
# Try fetching manually
fetch-age-key
# Check what keys exist
check-age-keys"1Password CLI not found"
Install 1Password CLI:
# macOS
brew install 1password-cli
# Or add to your Nix configuration
packages = [ pkgs._1password ];Keys not being used by SOPS
# Verify environment variable is set
echo $SOPS_AGE_KEY_CMD
# Test key command manually
$SOPS_AGE_KEY_CMD
# Should output AGE-SECRET-KEY-1...Permission denied on .keys/
# Reset permissions
chmod 700 .keys
chmod 600 .keys/*.ageMigration Guide
If you're currently using age-key-cmd.sh:
-
Enable the module in your configuration:
stackpanel.secrets.age-key-cmd.enable = true; -
Remove manual SOPS_AGE_KEY_CMD exports from shell scripts
-
Update 1Password references if needed:
stackpanel.secrets.age-key-cmd.onePassword = { account = "your-account"; item = "op://your-vault/your-item"; }; -
Test the new setup:
age:check # Should show your keys or fetch them -
Remove old shell script references once verified
Development
Testing the Tools
# Test key fetching
SOPS_KEYS_DIR=/tmp/test-keys fetch-age-key
# Test key reading
SOPS_KEYS_DIR=/tmp/test-keys age-key-cmd
# Test with SOPS
SOPS_KEYS_DIR=/tmp/test-keys sops .stack/secrets/dev/web.sops.yamlAdding New Key Sources
To add support for other key sources (e.g., AWS Secrets Manager, Vault):
- Add a new fetch function in
age-key-tools.nix - Add module options in
age-key-cmd.nix - Update
ageKeyCmdto try new source
Example:
fetchFromVault = writeShellScriptBin "fetch-from-vault" ''
# Implementation here
'';