The read-based loop split multiline values on newlines, causing only the first line to be written. Use jq -j to write each key's value directly to files, preserving multiline content. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
vault-fetch
A helper script for fetching secrets from OpenBao/Vault and writing them to the filesystem.
Features
- AppRole Authentication: Uses role_id and secret_id from
/var/lib/vault/approle/ - Individual Secret Files: Writes each secret key as a separate file for easy consumption
- Caching: Maintains a cache of secrets for fallback when Vault is unreachable
- Graceful Degradation: Falls back to cached secrets if Vault authentication fails
- Secure Permissions: Sets 600 permissions on all secret files
Usage
vault-fetch <secret-path> <output-directory> [cache-directory]
Examples
# Fetch Grafana admin secrets
vault-fetch hosts/monitoring01/grafana-admin /run/secrets/grafana /var/lib/vault/cache/grafana
# Use default cache location
vault-fetch hosts/monitoring01/grafana-admin /run/secrets/grafana
How It Works
- Read Credentials: Loads
role_idandsecret_idfrom/var/lib/vault/approle/ - Authenticate: Calls
POST /v1/auth/approle/loginto get a Vault token - Fetch Secret: Retrieves secret from
GET /v1/secret/data/{path} - Extract Keys: Parses JSON response and extracts individual secret keys
- Write Files: Creates one file per secret key in output directory
- Update Cache: Copies secrets to cache directory for fallback
- Set Permissions: Ensures all files have 600 permissions (owner read/write only)
Error Handling
If Vault is unreachable or authentication fails:
- Script logs a warning to stderr
- Falls back to cached secrets from previous successful fetch
- Exits with error code 1 if no cache is available
Environment Variables
VAULT_ADDR: Vault server address (default:https://vault01.home.2rjus.net:8200)VAULT_SKIP_VERIFY: Skip TLS verification (default:1)
Integration with NixOS
This tool is designed to be called from systemd service ExecStartPre hooks via the vault.secrets NixOS module:
vault.secrets.grafana-admin = {
secretPath = "hosts/monitoring01/grafana-admin";
};
# Service automatically gets secrets fetched before start
systemd.services.grafana.serviceConfig = {
EnvironmentFile = "/run/secrets/grafana-admin/password";
};
Requirements
curl: For Vault API callsjq: For JSON parsingcoreutils: For file operations
Security Considerations
- AppRole credentials stored at
/var/lib/vault/approle/should be root-owned with 600 permissions - Tokens are ephemeral and not stored - fresh authentication on each fetch
- Secrets written to tmpfs (
/run/secrets/) are lost on reboot - Cache directory persists across reboots for service availability
- All secret files have restrictive permissions (600)