Post-Tyranny-Tech-Infrastru.../SECURITY-NOTE-tokens.md
Pieter e04efa1cb1 feat: Move Hetzner API token to SOPS encrypted secrets
Resolves #20

Changes:
- Add hcloud_token to secrets/shared.sops.yaml (encrypted with Age)
- Create scripts/load-secrets-env.sh to automatically load token from SOPS
- Update all management scripts to auto-load token if not set
- Remove plaintext tokens from tofu/terraform.tfvars
- Update documentation in README.md, scripts/README.md, and SECURITY-NOTE-tokens.md

Benefits:
 Token encrypted at rest
 Can be safely backed up to cloud storage
 Consistent with other secrets management
 Automatic loading - no manual token management needed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-18 18:17:15 +01:00

3.3 KiB

Security Note: Hetzner API Token Placement

Date: 2026-01-17 (Updated: 2026-01-18) Severity: INFORMATIONAL Status: IMPROVED - Now using SOPS encryption

RESOLVED (2026-01-18)

The Hetzner Cloud API token has been moved to SOPS-encrypted storage:

  • Token now stored in secrets/shared.sops.yaml (encrypted with Age)
  • Automatically loaded by all scripts via scripts/load-secrets-env.sh
  • Removed from tofu/terraform.tfvars
  • All management scripts updated

Previous Situation (Before 2026-01-18)

The Hetzner Cloud API token was previously stored in:

  • tofu/terraform.tfvars (gitignored, NOT committed)

Assessment

Current Setup is SAFE:

  • tofu/terraform.tfvars is properly gitignored (line 15 in .gitignore: tofu/*.tfvars)
  • Token has NOT been committed to git history
  • File is local-only

⚠️ However, Best Practice Would Be:

  • Store token in secrets/shared.sops.yaml (encrypted with SOPS)
  • Reference it from terraform.tfvars as a variable
  • Keep terraform.tfvars minimal (only client configs)

Option 1: Keep Current Approach (Acceptable)

Pros:

  • Simple
  • Works with OpenTofu's native variable system
  • Already gitignored
  • Easy to use

Cons:

  • Token stored in plaintext on disk
  • Not encrypted at rest
  • Can't be safely backed up to cloud storage

Option 2: Move to SOPS (More Secure)

Pros:

  • Token encrypted at rest
  • Can be safely backed up
  • Consistent with other secrets
  • Better security posture

Cons:

  • Slightly more complex workflow
  • Need to decrypt before running tofu

Implementation (if desired):

  1. Add token to shared.sops.yaml:
SOPS_AGE_KEY_FILE=keys/age-key.txt sops secrets/shared.sops.yaml
# Add: hcloud_token: <your-token>
  1. Update terraform.tfvars to be minimal:
# No sensitive data here
# Token loaded from environment variable

clients = {
  # ... client configs only ...
}
  1. Update deployment scripts to load token:
# Before running tofu:
export TF_VAR_hcloud_token=$(sops -d secrets/shared.sops.yaml | yq .hcloud_token)
tofu apply

How It Works Now

All management scripts automatically load the token from SOPS:

# Scripts automatically load token from SOPS
./scripts/deploy-client.sh newclient
./scripts/rebuild-client.sh newclient
./scripts/destroy-client.sh newclient

# Manual loading (if needed)
source scripts/load-secrets-env.sh
# Exports: HCLOUD_TOKEN, TF_VAR_hcloud_token, TF_VAR_hetznerdns_token

Benefits Achieved

Token encrypted at rest with Age encryption Can be safely backed up to cloud storage Consistent with other secrets management Better security posture overall Automatic loading - no manual token management needed

Verification

Confirmed terraform.tfvars is NOT in git:

$ git ls-files | grep terraform.tfvars
tofu/terraform.tfvars.example  # Only the example is tracked ✓

Confirmed .gitignore is properly configured:

tofu/*.tfvars                   # Ignores all tfvars ✓
!tofu/terraform.tfvars.example  # Except the example ✓