Secrets Management
All secrets are managed using a hybrid approach combining 1Password as the primary source of truth, and Agenix for the initial deployment bootstrap. This ensures no sensitive secrets are exposed in the git repository while maintaining a fully automated deployment.
The Hybrid Workflow
Section titled “The Hybrid Workflow”- Agenix for the Bootstrap Token: We use Agenix to securely encrypt a single file in the repository:
1password-token.age. This file contains the 1Password Service Account Token. NixOS decrypts this file at boot using the host’s private SSH key (/etc/ssh/ssh_host_ed25519_key). - 1Password for Everything Else: Once the
1password-tokenis available on the machine, NixOS systemd services (likecloudflared) use the 1Password CLI (op) to fetch the rest of the required credentials dynamically at runtime.
SSH Key Architecture
Section titled “SSH Key Architecture”The homelab uses two separate SSH keys stored in 1Password under the Homelab vault:
| Key | 1Password Item | Purpose | Comment |
|---|---|---|---|
| Host Identity | Homelab SSH | Server host key (/etc/ssh/ssh_host_ed25519_key). Used by agenix to decrypt 1password-token.age at boot. | root@nixos |
| User Auth | Homelab SSH - javiersc | Personal SSH key for authenticating to the server. Deployed by ssh-key-setup service. | — |
How it works:
- The
ssh-key-setuponeshot service runs beforesshd.serviceon every boot. - It fetches the public and private key from the
Homelab SSH - javiersc1Password item using the Service Account token. - It writes them to
~/.ssh/(id_ed25519andauthorized_keys) with correct permissions and ownership. - SSH is configured with
PasswordAuthentication = false— key-only authentication. - The SSH proxy (
homelab.proxies.ssh) exposes SSH via Cloudflare tunnel.
Client setup (any OS):
- Export the private key from the
Homelab SSH - javiersc1Password item to~/.ssh/id_ed25519. - Connect:
ssh javiersc@nixos.localorssh ssh-home.javiersc.com(via Cloudflare).
Works on Windows, macOS, and Linux.
Restricted Access (Least Privilege)
Section titled “Restricted Access (Least Privilege)”To maintain security, the infrastructure only has access to the specific secrets it needs:
- Dedicated Vault: A specific vault (e.g.,
Homelab) contains only the items required for this infrastructure (Cloudflare tokens, etc.). - Service Account: A 1Password Service Account is created and granted “Read-Only” access exclusively to that dedicated vault.
- Token-based Auth: The server uses the Service Account Token (injected via Agenix) to authenticate. If the machine is ever compromised, your personal vaults and other sensitive data remain completely isolated and inaccessible.
Integration with systemd Services
Section titled “Integration with systemd Services”To inject secrets securely, we use a unified set of NixOS helpers defined in the infrastructure repository. This follows security best practices:
- Unified Helper Flow:
homelab.mkSecretService: Creates a dedicatedoneshotservice (e.g.,litellm-secrets) to fetch credentials. It runs asroot, fetches secrets with automatic retries (innerretry_op12x + outer systemd 3x restart), writes them to a private RAM-based directory (/run/<service>/env), and sets restrictive permissions (0700).- Companion Config Service: When a service needs structured config files (JSON/YAML) with embedded secrets, a second
oneshotservice (e.g.,litellm-config) sources/run/<service>/envand renders the config files usingenvsubst. Secrets are interpolated via quoted heredocs (<<'EOF') to prevent shell injection. homelab.serviceConfig: Used on the main service only (not the secrets/config oneshots) to set upRuntimeDirectory,0700permissions, and load the 1Password token via systemdLoadCredential.
- Resilience & Security:
- Automatic Retries: All 1Password fetching includes
retry_opwithflock-based concurrency control (12 attempts, exponential backoff) plus systemd-levelRestart=on-failure(3 restarts). Services depending on secrets useafter/requireschains so they only start after secrets are available. - Safe Heredocs: Config file templates use quoted heredocs (
<<'EOF') piped throughenvsubst. This prevents shell command injection from secret values containing$(), backticks, or other metacharacters. - RAM-based storage: Secrets never touch the disk; they live in
tmpfs(/run) and are automatically wiped on service stop or reboot. - Leak Prevention: Secrets are passed via
EnvironmentFileor file-based reading, preventing them from appearing insystemctl statusor/proc. Nopkgs.writeTextor Nix store artifacts for secrets.
- Automatic Retries: All 1Password fetching includes
- DynamicUser Support: For services that use systemd
DynamicUser(where the user ID changes on every start), we use an inlineExecStartPrehook viahomelab.fetchSecretFileto fetch secrets directly within the service context.
Manual Usage
Section titled “Manual Usage”You can still interact with secrets manually using the 1Password CLI:
# Authenticate (if not using a Service Account token locally)op signin
# Read a specific secretop read "op://Homelab/Cloudflare/api-token"