homelab/terraform/proxmox-examples/docker-host/STATE_MANAGEMENT.md
Claude 9109712b59
feat: Add SOPS state management and fix Terraform deployment issues
SOPS State Management:
- Implemented Git + SOPS + age encryption for Terraform state files
- Added .gitignore files to prevent committing unencrypted secrets
- Created .sops.yaml.example template for age encryption configuration
- Created helper scripts for automated encryption/decryption workflow:
  - scripts/tf: Wrapper script with auto-encrypt/decrypt
  - scripts/tf-encrypt: Manual encryption of state files
  - scripts/tf-decrypt: Manual decryption of state files
- Added comprehensive STATE_MANAGEMENT.md documentation covering:
  - Installation of age and SOPS
  - Initial setup and key generation
  - Daily workflow examples
  - Security best practices
  - Troubleshooting common issues
  - Multi-user key management
  - Backup strategies

Terraform Deployment Fixes:
- Added snippets_storage variable for cloud-init snippet storage
- Fixed datastore error: "local" does not support snippets
- Updated README with solutions for datastore and SSH issues
- Added troubleshooting for:
  - Enabling snippets on existing storage (pvesm set)
  - Creating dedicated directory storage for snippets
  - SSH authentication setup with ssh-agent
  - Manual cloud-init snippet creation workaround

Files modified:
- terraform/proxmox-examples/docker-host/main.tf
- terraform/proxmox-examples/docker-host/variables.tf
- terraform/proxmox-examples/docker-host/terraform.tfvars.example
- terraform/proxmox-examples/docker-host/README.md

Files added:
- .gitignore (root level)
- terraform/proxmox-examples/docker-host/.gitignore
- terraform/proxmox-examples/docker-host/.sops.yaml.example
- terraform/proxmox-examples/docker-host/STATE_MANAGEMENT.md
- terraform/proxmox-examples/docker-host/scripts/tf
- terraform/proxmox-examples/docker-host/scripts/tf-encrypt
- terraform/proxmox-examples/docker-host/scripts/tf-decrypt
2025-11-11 05:55:07 +00:00

378 lines
9.8 KiB
Markdown

# Terraform State Management with SOPS
This project uses [SOPS](https://github.com/getsops/sops) (Secrets OPerationS) with [age](https://github.com/FiloSottile/age) encryption to securely store Terraform state files in Git.
## Why SOPS + age?
**Encrypted at rest** - State files contain sensitive data (IPs, tokens)
**Version controlled** - Track infrastructure changes over time
**No infrastructure required** - No need for S3, PostgreSQL, or other backends
**Perfect for homelabs** - Simple, secure, self-contained
**FOSS** - Fully open source tools
## Prerequisites
### 1. Install age
**Debian/Ubuntu:**
```bash
sudo apt update
sudo apt install age
```
**macOS:**
```bash
brew install age
```
**Manual installation:**
```bash
# Download from https://github.com/FiloSottile/age/releases
wget https://github.com/FiloSottile/age/releases/download/v1.1.1/age-v1.1.1-linux-amd64.tar.gz
tar xzf age-v1.1.1-linux-amd64.tar.gz
sudo mv age/age age/age-keygen /usr/local/bin/
```
### 2. Install SOPS
**Debian/Ubuntu:**
```bash
# Download from https://github.com/getsops/sops/releases
wget https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
sudo mv sops-v3.8.1.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
```
**macOS:**
```bash
brew install sops
```
Verify installation:
```bash
age --version
sops --version
```
## Initial Setup
### 1. Generate Age Encryption Key
```bash
# Create SOPS directory
mkdir -p ~/.sops
# Generate a new age key pair
age-keygen -o ~/.sops/homelab-terraform.txt
# View the key (you'll need the public key)
cat ~/.sops/homelab-terraform.txt
```
Output will look like:
```
# created: 2025-11-11T12:34:56Z
# public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AGE-SECRET-KEY-1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
```
**⚠️ IMPORTANT:**
- The line starting with `AGE-SECRET-KEY-1` is your **private key** - keep it secret!
- The line starting with `age1` is your **public key** - you'll use this in .sops.yaml
- **Backup this file** to a secure location (password manager, encrypted backup, etc.)
- If you lose this key, you **cannot decrypt** your state files!
### 2. Configure SOPS
```bash
cd terraform/proxmox-examples/docker-host
# Copy the example config
cp .sops.yaml.example .sops.yaml
# Edit and replace YOUR_AGE_PUBLIC_KEY_HERE with your public key from step 1
nano .sops.yaml
```
Your `.sops.yaml` should look like:
```yaml
creation_rules:
- path_regex: \.tfstate$
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- path_regex: \.secret$
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- path_regex: terraform\.tfvars$
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
### 3. Set Environment Variable (Optional but Recommended)
```bash
# Add to your ~/.bashrc or ~/.zshrc
echo 'export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt' >> ~/.bashrc
source ~/.bashrc
```
This tells SOPS where to find your private key for decryption.
## Usage
### Option A: Automatic Wrapper Script (Recommended)
Use the `./scripts/tf` wrapper that handles encryption/decryption automatically:
```bash
# Initialize (first time)
./scripts/tf init
# Plan changes
./scripts/tf plan
# Apply changes (automatically encrypts after)
./scripts/tf apply
# Destroy infrastructure (automatically encrypts after)
./scripts/tf destroy
# View state
./scripts/tf show
```
The wrapper script:
1. Decrypts state files before running
2. Runs your terraform/tofu command
3. Encrypts state files after (if state was modified)
### Option B: Manual Encryption/Decryption
If you prefer manual control:
```bash
# 1. Decrypt state files
./scripts/tf-decrypt
# 2. Run terraform commands
tofu init
tofu plan
tofu apply
# 3. Encrypt state files
./scripts/tf-encrypt
# 4. Commit encrypted files to Git
git add *.enc
git commit -m "Update infrastructure"
git push
```
## Workflow Examples
### First Time Setup
```bash
cd terraform/proxmox-examples/docker-host
# 1. Configure your variables
cp terraform.tfvars.example terraform.tfvars
nano terraform.tfvars # Add your API tokens, SSH keys, etc.
# 2. Initialize Terraform
./scripts/tf init
# 3. Plan infrastructure
./scripts/tf plan
# 4. Apply infrastructure
./scripts/tf apply
# 5. Encrypted state files are automatically created
# terraform.tfstate.enc now exists
# 6. Commit encrypted state to Git
git add terraform.tfstate.enc .sops.yaml.example
git commit -m "Add encrypted Terraform state"
git push
```
### Making Infrastructure Changes
```bash
# 1. Decrypt, apply changes, re-encrypt (all automatic)
./scripts/tf apply
# 2. Commit updated encrypted state
git add terraform.tfstate.enc
git commit -m "Update VM configuration"
git push
```
### Cloning on a New Machine
```bash
# 1. Clone the repository
git clone https://github.com/efigueroa/homelab.git
cd homelab/terraform/proxmox-examples/docker-host
# 2. Copy your age private key to the new machine
# (Securely transfer ~/.sops/homelab-terraform.txt)
mkdir -p ~/.sops
# Copy the key file here
# 3. Set up SOPS config
cp .sops.yaml.example .sops.yaml
# Edit with your public key
# 4. Decrypt state
./scripts/tf-decrypt
# 5. Now you can run terraform commands
./scripts/tf plan
```
## Security Best Practices
### DO ✅
- **Backup your age private key** to multiple secure locations
- **Use different keys** for different projects/environments
- **Commit `.sops.yaml.example`** to Git (without your actual key)
- **Commit encrypted `*.enc` files** to Git
- **Use the wrapper script** to avoid forgetting to encrypt
### DON'T ❌
- **Never commit `.sops.yaml`** with your actual key (it's in .gitignore)
- **Never commit unencrypted `.tfstate`** files (they're in .gitignore)
- **Never commit unencrypted `terraform.tfvars`** with secrets
- **Never share your private age key** publicly
- **Don't lose your private key** - you can't decrypt without it!
## File Structure
```
terraform/proxmox-examples/docker-host/
├── .gitignore # Ignores unencrypted files
├── .sops.yaml # Your SOPS config (NOT in Git)
├── .sops.yaml.example # Template (in Git)
├── terraform.tfstate # Unencrypted state (NOT in Git)
├── terraform.tfstate.enc # Encrypted state (in Git) ✅
├── terraform.tfvars # Your config with secrets (NOT in Git)
├── terraform.tfvars.enc # Encrypted config (in Git) ✅
├── terraform.tfvars.example # Template without secrets (in Git)
├── scripts/
│ ├── tf # Wrapper script
│ ├── tf-encrypt # Manual encrypt
│ └── tf-decrypt # Manual decrypt
└── STATE_MANAGEMENT.md # This file
```
## Troubleshooting
### Error: "no key could decrypt the data"
**Cause:** SOPS can't find your private key
**Solution:**
```bash
# Set the key file location
export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt
# Or add to ~/.bashrc permanently
echo 'export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt' >> ~/.bashrc
```
### Error: "YOUR_AGE_PUBLIC_KEY_HERE"
**Cause:** You didn't replace the placeholder in `.sops.yaml`
**Solution:**
```bash
# Edit .sops.yaml and replace with your actual public key
nano .sops.yaml
```
### Error: "failed to get the data key"
**Cause:** The file was encrypted with a different key
**Solution:**
- Ensure you're using the same age key that encrypted the file
- If you lost the original key, you'll need to re-create the state by running `tofu import`
### Accidentally Committed Unencrypted State
**Solution:**
```bash
# Remove from Git history (DANGEROUS - coordinate with team if not solo)
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch terraform.tfstate' \
--prune-empty --tag-name-filter cat -- --all
# Force push (only if solo or coordinated)
git push origin --force --all
```
### Lost Private Key
**Solution:**
- Restore from your backup (you made a backup, right?)
- If truly lost, you'll need to:
1. Manually recreate infrastructure or import existing resources
2. Generate a new age key
3. Re-encrypt everything with the new key
## Advanced: Multiple Keys (Team Access)
If multiple people need access:
```yaml
# .sops.yaml
creation_rules:
- path_regex: \.tfstate$
age: >-
age1person1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
age1person2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
age1person3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Each person's private key can decrypt the files.
## Backup Strategy
### Recommended Backup Locations:
1. **Password Manager** (1Password, Bitwarden, etc.)
```bash
# Copy the contents
cat ~/.sops/homelab-terraform.txt
# Store as a secure note in your password manager
```
2. **Encrypted USB Drive**
```bash
# Copy to encrypted drive
cp ~/.sops/homelab-terraform.txt /media/encrypted-usb/
```
3. **Encrypted Cloud Storage**
```bash
# Encrypt with gpg before uploading
gpg -c ~/.sops/homelab-terraform.txt
# Upload homelab-terraform.txt.gpg to cloud
```
## Resources
- [SOPS Documentation](https://github.com/getsops/sops)
- [age Documentation](https://github.com/FiloSottile/age)
- [Terraform State Security](https://developer.hashicorp.com/terraform/language/state/sensitive-data)
- [OpenTofu Documentation](https://opentofu.org/docs/)
## Questions?
Common questions answered in this document:
- ✅ How do I set up SOPS? → See [Initial Setup](#initial-setup)
- ✅ How do I use it daily? → See [Option A: Automatic Wrapper](#option-a-automatic-wrapper-script-recommended)
- ✅ What if I lose my key? → See [Lost Private Key](#lost-private-key)
- ✅ How do I backup my key? → See [Backup Strategy](#backup-strategy)
- ✅ Can multiple people access? → See [Advanced: Multiple Keys](#advanced-multiple-keys-team-access)