Secrets Management¶
Policy¶
Never Commit Secrets
All credentials must be stored in 1Password. Never commit secrets to the repository.
1Password CLI¶
Installation¶
# Add 1Password apt repository
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main" | \
sudo tee /etc/apt/sources.list.d/1password.list
sudo apt update && sudo apt install 1password-cli
Basic Usage¶
# Retrieve a credential from scandora-automation vault
op item get "opnsense_api_key_owl_production" \
--vault scandora-automation --fields api_key --reveal
# Use in scripts
API_KEY=$(op item get "zerotier_api_token_network_management" \
--vault scandora-automation --fields api_token --reveal)
# List items by vault
op item list --vault scandora-automation
op item list --vault scandora-prd-automation
# List items by tag (across all vaults)
op item list --tags scandora.net
Authentication (Reducing Prompts)¶
For interactive development on luna (macOS):
- Ensure 1Password app is running and unlocked (uses Touch ID/biometric)
- 1Password CLI auto-connects to the running app
- No additional authentication needed for subsequent
opcommands
# Verify CLI is connected to 1Password app
op whoami
# Should show:
# URL: https://my.1password.com
# Email: joseph@scandora.net
# Account: scandoraproject
If not connected:
# Sign in once (will prompt for password and 2FA)
eval $(op signin)
# Subsequent commands use this session
# Session expires after 30 minutes of inactivity
Vault permissions:
The scandora-automation vault is configured for full read access to minimize prompts during development:
- All automation scripts use this vault exclusively
- Credentials are accessed without vault-specific authorization prompts
- Production deployments work identically (no code changes needed)
Current Secrets¶
scandora-automation Vault¶
Infrastructure automation credentials (API keys, tokens):
| Item | Purpose | Fields |
|---|---|---|
| opnsense_api_key_dev_vm | Dev VM API access | api_key, api_secret |
| opnsense_api_key_owl_production | Owl gateway API | api_key, api_secret |
| opnsense_api_key_blue_production | Blue gateway API | api_key, api_secret |
| zerotier_api_token_network_management | ZeroTier Central API | api_token |
| grafana_admin_password | Grafana admin login | password |
| snmp_community_monitoring | SNMP access for Prometheus | password |
| cloudflare_tunnel_token_pluto | Cloudflare Zero Trust | token |
| cloudflare_tunnel_token_dumbo | Cloudflare Zero Trust | token |
scandora.net Vault¶
Cloud infrastructure and database credentials:
| Item | Purpose |
|---|---|
| aws_access_key_terraform_automation | AWS CLI credentials (terraform, in scandora-automation) |
| gcp_service_account_key_scandoraproject_bigquery_admin | BigQuery admin (scandoraproject) |
| gcp_service_account_key_coop_bigquery_admin | BigQuery admin (coop-389306) |
| mysql_rds_scandora_mysql8_admin | Legacy RDS credentials |
| cloudflare_api_token_dns_automation | Public DNS management (in scandora-automation) |
| cloud_sql_scandora_postgres_user_joe | PostgreSQL user (in scandora-automation) |
| gcp_postgres_grafana_password | Grafana database (in scandora-automation) |
1Password Service Account¶
Trusted cloud instances use the scandora-full service account for headless credential access.
Deployed Hosts¶
| Host | Token Location | CLI Version | Status |
|---|---|---|---|
| Pluto | /etc/op-service-account.token |
2.32.0 | ✅ Active |
| Dumbo | /etc/op-service-account.token |
2.30.0 | ✅ Active |
Bogart is Untrusted
Bogart does not have 1Password access. No secrets should be stored there.
Usage on Trusted Hosts¶
# Export token and retrieve credential
export OP_SERVICE_ACCOUNT_TOKEN=$(sudo cat /etc/op-service-account.token)
op item get "aws_access_key_terraform_automation" --vault scandora-automation --fields access_key_id
# One-liner for scripts
API_KEY=$(OP_SERVICE_ACCOUNT_TOKEN=$(sudo cat /etc/op-service-account.token) \
op item get "item_name" --vault scandora-automation --fields api_key)
Vault Required
Service accounts require --vault to be specified explicitly.
Token Management¶
- Token stored in:
1Password Account (Joseph Scandora)→ Security →scandora-full service account token - Vault access:
scandora.netvault only (read-only) - To rotate: Generate new token in 1Password web console, redeploy to hosts
AWS Credentials (luna)¶
The luna workstation uses credential_process to retrieve AWS credentials from 1Password automatically.
Configuration¶
# ~/.aws/config
[default]
region = us-west-2
credential_process = /Users/joe/src/scandora.net/scripts/aws/op-credential-helper.sh
How It Works¶
- AWS CLI calls
op-credential-helper.sh - Script retrieves credentials from 1Password
- Returns JSON in AWS credential_process format
- No credentials stored on disk
See scripts/aws/README.md for details.
GCP Credentials (luna)¶
The luna workstation uses a session-based credential helper that retrieves GCP service account keys from 1Password.
Profiles¶
Multiple GCP projects are supported via profiles:
| Profile | Service Account | Project | Use Case |
|---|---|---|---|
luna (default) |
scandora-owner | scandoraproject | dumbo, Cloud SQL |
coop |
coop-owner | coop-389306 | bogart |
coop-bq |
bigquery-admin | coop-389306 | BigQuery queries |
scandora-bq |
scandora-bigquery-admin | scandoraproject | BigQuery queries |
Configuration¶
Unlike AWS CLI, gcloud requires credentials via a file path (GOOGLE_APPLICATION_CREDENTIALS). The helper writes to a temp file that's automatically cleaned up on shell exit.
Usage¶
# Load default credentials (scandoraproject)
source scripts/gcp/gcp-env.sh
# Load coop credentials (for bogart)
source scripts/gcp/gcp-env.sh coop
# Aliases (add to ~/.bashrc or ~/.zshrc)
alias gcp-auth='source ~/src/scandora.net/scripts/gcp/gcp-env.sh'
alias gcp-coop='source ~/src/scandora.net/scripts/gcp/gcp-env.sh coop'
# Credentials now available
gcloud compute instances list
terraform plan
How It Works¶
gcp-env.sh [PROFILE]callsop-credential-helper.sh --profile PROFILE- Script retrieves service account JSON from 1Password via
op document get - Writes JSON to temp file with 0600 permissions
- Sets
GOOGLE_APPLICATION_CREDENTIALSto temp file path - Registers EXIT trap to clean up file when shell exits
See scripts/gcp/README.md for details.
Adding New Credentials¶
Best Practices¶
- Let 1Password generate credentials - Never generate externally
- Use descriptive names - Include service and host
- Tag with scandora.net - For easy filtering
- Document retrieval command - In repo files
Creating New Item¶
Choose the correct vault:
- scandora-automation: Automation/API credentials (OPNsense, ZeroTier, monitoring, tunnels)
- scandora.net: Cloud infrastructure keys and database passwords
# Create automation credential (use lowercase_with_underscores)
op item create --category="API Credential" \
--title="service_name_description" \
--vault="scandora-automation" \
--tags="scandora.net" \
--generate-password='letters,digits,symbols,32' \
'api_key[password]=<generated>'
# Examples:
# - opnsense_api_key_dev_vm
# - zerotier_api_token_network_management
# - grafana_admin_password
Naming conventions:
- Item names:
lowercase_with_underscores(no spaces) - Field names:
api_key,api_secret,api_token,password(consistent across all items)
Documenting in Repo¶
Reference 1Password items in code comments using the new naming convention:
# Credential: 1Password "opnsense_api_key_owl_production" (scandora-automation vault)
# Retrieve: op item get "opnsense_api_key_owl_production" --vault scandora-automation --fields api_key --reveal
opnsense_api_url: https://192.168.194.10
# Retrieve ZeroTier API token
ZEROTIER_TOKEN=$(op item get "zerotier_api_token_network_management" \
--vault scandora-automation --fields api_token --reveal)
Credential Rotation¶
If a credential was ever in git history:
- Rotate immediately - Generate new credential in 1Password
- Update services - Deploy new credential to affected hosts
- Revoke old credential - If possible (API keys, tokens)
- Audit - Check for unauthorized access
Ansible Integration¶
Passing Secrets¶
Never store secrets in playbooks. Pass at runtime:
# Via extra-vars
TOKEN=$(op item get "Cloudflare Tunnel Token - pluto" --fields credential --reveal)
ansible-playbook playbooks/site.yml -e cloudflared_tunnel_token="$TOKEN"
# Via environment variable
export PDNS_API_KEY=$(op item get "PowerDNS API - Bogart" --fields credential --reveal)
ansible-playbook playbooks/dns.yml
Ansible Vault (Alternative)¶
For secrets that must be in files: