docs(wiki): add comprehensive SOPS secrets guide
Add dedicated guide covering SOPS installation with age/GPG, creating encrypted secrets files, Gordon configuration, environment file integration, container setup, team workflows with key management, and troubleshooting common issues.
- commit
- 5c255ed8715448513bce0ac34cead4ad2e605d31
- author
- brice · · 2026-01-16 06:39 UTC
- committer
- brice · · 2026-01-16 06:44 UTC
- parent
- dd108b6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +430 -1
M README.md +1 -1
| @@ -44,7 +44,7 @@ Full documentation is available at **[gordon.bnema.dev](https://gordon.bnema.dev | |||
| 44 | 44 | ||
| 45 | 45 | ```bash | |
| 46 | 46 | # Install Gordon | |
| 47 | - | curl -fsSL https://gordon.bnema.dev/install | sh | |
| 47 | + | curl -fsSL https://gordon.bnema.dev/install.sh | sh | |
| 48 | 48 | # Or: curl -fsSL https://raw.githubusercontent.com/bnema/gordon/main/install.sh | sh | |
| 49 | 49 | ||
| 50 | 50 | # Start Gordon (generates config on first run) | |
| @@ -44,7 +44,7 @@ Full documentation is available at **[gordon.bnema.dev](https://gordon.bnema.dev | |||||
| 44 | 44 | ||||
| 45 | ```bash | 45 | ```bash | ||
| 46 | # Install Gordon | 46 | # Install Gordon | ||
| 47 | - | curl -fsSL https://gordon.bnema.dev/install | sh | 47 | + | curl -fsSL https://gordon.bnema.dev/install.sh | sh |
| 48 | # Or: curl -fsSL https://raw.githubusercontent.com/bnema/gordon/main/install.sh | sh | 48 | # Or: curl -fsSL https://raw.githubusercontent.com/bnema/gordon/main/install.sh | sh | ||
| 49 | 49 | ||||
| 50 | # Start Gordon (generates config on first run) | 50 | # Start Gordon (generates config on first run) | ||
A wiki/guides/secrets-sops.md +429 -0
| @@ -0,0 +1,429 @@ | |||
| 1 | + | # Using SOPS for Secrets | |
| 2 | + | ||
| 3 | + | Set up Mozilla SOPS as Gordon's secrets backend for encrypted file-based secret storage. | |
| 4 | + | ||
| 5 | + | ## What You'll Learn | |
| 6 | + | ||
| 7 | + | - Installing SOPS and age (or GPG) | |
| 8 | + | - Creating encrypted secrets files | |
| 9 | + | - Configuring Gordon to use SOPS | |
| 10 | + | - Using secrets in environment files | |
| 11 | + | - Team workflows and key rotation | |
| 12 | + | ||
| 13 | + | ## Prerequisites | |
| 14 | + | ||
| 15 | + | - Linux or macOS system | |
| 16 | + | - Basic understanding of encryption concepts | |
| 17 | + | ||
| 18 | + | ## Why SOPS? | |
| 19 | + | ||
| 20 | + | SOPS (Secrets OPerationS) encrypts specific values in YAML/JSON files while keeping keys readable: | |
| 21 | + | ||
| 22 | + | - **Git-friendly**: Encrypted files can be version controlled | |
| 23 | + | - **Multiple backends**: age, GPG, AWS KMS, GCP KMS, Azure Key Vault | |
| 24 | + | - **Selective encryption**: Only values are encrypted, keys remain visible | |
| 25 | + | - **Team support**: Multiple recipients can decrypt the same file | |
| 26 | + | ||
| 27 | + | ## Installation | |
| 28 | + | ||
| 29 | + | ### SOPS | |
| 30 | + | ||
| 31 | + | ```bash | |
| 32 | + | # Ubuntu/Debian | |
| 33 | + | curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64 | |
| 34 | + | sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops | |
| 35 | + | sudo chmod +x /usr/local/bin/sops | |
| 36 | + | ||
| 37 | + | # macOS | |
| 38 | + | brew install sops | |
| 39 | + | ||
| 40 | + | # Arch Linux | |
| 41 | + | sudo pacman -S sops | |
| 42 | + | ``` | |
| 43 | + | ||
| 44 | + | ### age (Recommended) | |
| 45 | + | ||
| 46 | + | age is simpler than GPG and recommended for new setups: | |
| 47 | + | ||
| 48 | + | ```bash | |
| 49 | + | # Ubuntu/Debian | |
| 50 | + | sudo apt install age | |
| 51 | + | ||
| 52 | + | # macOS | |
| 53 | + | brew install age | |
| 54 | + | ||
| 55 | + | # Arch Linux | |
| 56 | + | sudo pacman -S age | |
| 57 | + | ``` | |
| 58 | + | ||
| 59 | + | ### GPG (Alternative) | |
| 60 | + | ||
| 61 | + | ```bash | |
| 62 | + | # Ubuntu/Debian | |
| 63 | + | sudo apt install gnupg | |
| 64 | + | ||
| 65 | + | # macOS | |
| 66 | + | brew install gnupg | |
| 67 | + | ``` | |
| 68 | + | ||
| 69 | + | ## Setup with age | |
| 70 | + | ||
| 71 | + | ### 1. Generate an age Key | |
| 72 | + | ||
| 73 | + | ```bash | |
| 74 | + | # Generate key pair | |
| 75 | + | age-keygen -o ~/.config/sops/age/keys.txt | |
| 76 | + | ||
| 77 | + | # Output shows your public key: | |
| 78 | + | # Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | |
| 79 | + | ``` | |
| 80 | + | ||
| 81 | + | Note your public key - you'll need it for encryption. | |
| 82 | + | ||
| 83 | + | ### 2. Configure SOPS | |
| 84 | + | ||
| 85 | + | Create `.sops.yaml` in your project or home directory: | |
| 86 | + | ||
| 87 | + | ```yaml | |
| 88 | + | creation_rules: | |
| 89 | + | - age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | |
| 90 | + | ``` | |
| 91 | + | ||
| 92 | + | Or for multiple recipients: | |
| 93 | + | ||
| 94 | + | ```yaml | |
| 95 | + | creation_rules: | |
| 96 | + | - age: >- | |
| 97 | + | age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, | |
| 98 | + | age1yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy | |
| 99 | + | ``` | |
| 100 | + | ||
| 101 | + | ### 3. Set age Key Location | |
| 102 | + | ||
| 103 | + | ```bash | |
| 104 | + | # Add to ~/.bashrc or ~/.zshrc | |
| 105 | + | export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt | |
| 106 | + | ``` | |
| 107 | + | ||
| 108 | + | ## Setup with GPG | |
| 109 | + | ||
| 110 | + | ### 1. Generate or Use Existing GPG Key | |
| 111 | + | ||
| 112 | + | ```bash | |
| 113 | + | # Generate new key | |
| 114 | + | gpg --gen-key | |
| 115 | + | ||
| 116 | + | # List keys to get fingerprint | |
| 117 | + | gpg --list-keys | |
| 118 | + | ``` | |
| 119 | + | ||
| 120 | + | ### 2. Configure SOPS | |
| 121 | + | ||
| 122 | + | ```yaml | |
| 123 | + | creation_rules: | |
| 124 | + | - pgp: ABCD1234EFGH5678... # Your GPG fingerprint | |
| 125 | + | ``` | |
| 126 | + | ||
| 127 | + | ## Creating Encrypted Secrets | |
| 128 | + | ||
| 129 | + | ### Create secrets.yaml | |
| 130 | + | ||
| 131 | + | ```bash | |
| 132 | + | # Create and encrypt in one step | |
| 133 | + | sops edit secrets.yaml | |
| 134 | + | ``` | |
| 135 | + | ||
| 136 | + | This opens your editor. Add secrets in YAML format: | |
| 137 | + | ||
| 138 | + | ```yaml | |
| 139 | + | registry: | |
| 140 | + | password_hash: "$2y$10$..." | |
| 141 | + | token_secret: "your-random-32-char-string-here" | |
| 142 | + | database: | |
| 143 | + | password: "db-password-here" | |
| 144 | + | api: | |
| 145 | + | key: "api-key-here" | |
| 146 | + | ``` | |
| 147 | + | ||
| 148 | + | Save and exit. SOPS encrypts the values automatically. | |
| 149 | + | ||
| 150 | + | ### View Encrypted File | |
| 151 | + | ||
| 152 | + | ```bash | |
| 153 | + | cat secrets.yaml | |
| 154 | + | ``` | |
| 155 | + | ||
| 156 | + | Output shows encrypted values: | |
| 157 | + | ||
| 158 | + | ```yaml | |
| 159 | + | registry: | |
| 160 | + | password_hash: ENC[AES256_GCM,data:...,iv:...,tag:...] | |
| 161 | + | token_secret: ENC[AES256_GCM,data:...,iv:...,tag:...] | |
| 162 | + | database: | |
| 163 | + | password: ENC[AES256_GCM,data:...,iv:...,tag:...] | |
| 164 | + | api: | |
| 165 | + | key: ENC[AES256_GCM,data:...,iv:...,tag:...] | |
| 166 | + | sops: | |
| 167 | + | age: | |
| 168 | + | - recipient: age1xxx... | |
| 169 | + | enc: | | |
| 170 | + | -----BEGIN AGE ENCRYPTED FILE----- | |
| 171 | + | ... | |
| 172 | + | -----END AGE ENCRYPTED FILE----- | |
| 173 | + | lastmodified: "2024-01-15T12:00:00Z" | |
| 174 | + | version: 3.9.0 | |
| 175 | + | ``` | |
| 176 | + | ||
| 177 | + | ### Decrypt and View | |
| 178 | + | ||
| 179 | + | ```bash | |
| 180 | + | # View entire file | |
| 181 | + | sops decrypt secrets.yaml | |
| 182 | + | ||
| 183 | + | # Extract specific value | |
| 184 | + | sops decrypt --extract '["registry"]["token_secret"]' secrets.yaml | |
| 185 | + | ``` | |
| 186 | + | ||
| 187 | + | ### Edit Existing File | |
| 188 | + | ||
| 189 | + | ```bash | |
| 190 | + | sops edit secrets.yaml | |
| 191 | + | ``` | |
| 192 | + | ||
| 193 | + | ## Gordon Configuration | |
| 194 | + | ||
| 195 | + | ### Enable SOPS Backend | |
| 196 | + | ||
| 197 | + | ```toml | |
| 198 | + | [secrets] | |
| 199 | + | backend = "sops" | |
| 200 | + | ||
| 201 | + | [registry_auth] | |
| 202 | + | enabled = true | |
| 203 | + | type = "token" | |
| 204 | + | username = "admin" | |
| 205 | + | password_hash = "secrets.yaml:registry.password_hash" | |
| 206 | + | token_secret = "secrets.yaml:registry.token_secret" | |
| 207 | + | ``` | |
| 208 | + | ||
| 209 | + | The path format is `file:key.path` where: | |
| 210 | + | - `file` is the SOPS-encrypted file path | |
| 211 | + | - `key.path` is dot-notation to the value | |
| 212 | + | ||
| 213 | + | ### Using Secrets in Environment Files | |
| 214 | + | ||
| 215 | + | Reference SOPS secrets in your app's environment files: | |
| 216 | + | ||
| 217 | + | ```bash | |
| 218 | + | # ~/.gordon/env/app_mydomain_com.env | |
| 219 | + | DATABASE_URL=postgresql://user:${sops:secrets.yaml:database.password}@postgres:5432/app | |
| 220 | + | API_KEY=${sops:secrets.yaml:api.key} | |
| 221 | + | ``` | |
| 222 | + | ||
| 223 | + | ## File Organization | |
| 224 | + | ||
| 225 | + | Recommended structure: | |
| 226 | + | ||
| 227 | + | ``` | |
| 228 | + | project/ | |
| 229 | + | ├── .sops.yaml # SOPS configuration | |
| 230 | + | ├── secrets.yaml # Encrypted secrets (commit this) | |
| 231 | + | ├── secrets.dev.yaml # Dev environment secrets | |
| 232 | + | ├── secrets.prod.yaml # Production secrets | |
| 233 | + | └── gordon.toml # Gordon config (references secrets) | |
| 234 | + | ``` | |
| 235 | + | ||
| 236 | + | ### Environment-Specific Rules | |
| 237 | + | ||
| 238 | + | ```yaml | |
| 239 | + | # .sops.yaml | |
| 240 | + | creation_rules: | |
| 241 | + | # Dev secrets - developer keys | |
| 242 | + | - path_regex: \.dev\.yaml$ | |
| 243 | + | age: age1devkey... | |
| 244 | + | ||
| 245 | + | # Prod secrets - production keys only | |
| 246 | + | - path_regex: \.prod\.yaml$ | |
| 247 | + | age: age1prodkey... | |
| 248 | + | ||
| 249 | + | # Default | |
| 250 | + | - age: age1defaultkey... | |
| 251 | + | ``` | |
| 252 | + | ||
| 253 | + | ## Running in Containers | |
| 254 | + | ||
| 255 | + | ### Custom Dockerfile | |
| 256 | + | ||
| 257 | + | ```dockerfile | |
| 258 | + | FROM ghcr.io/bnema/gordon:latest | |
| 259 | + | ||
| 260 | + | USER root | |
| 261 | + | RUN apk add --no-cache sops age | |
| 262 | + | USER gordon | |
| 263 | + | ``` | |
| 264 | + | ||
| 265 | + | ### Mount Secrets and Keys | |
| 266 | + | ||
| 267 | + | ```bash | |
| 268 | + | docker run -d \ | |
| 269 | + | --name gordon \ | |
| 270 | + | -p 80:8080 \ | |
| 271 | + | -p 5000:5000 \ | |
| 272 | + | -v /var/run/docker.sock:/var/run/docker.sock \ | |
| 273 | + | -v gordon-data:/data \ | |
| 274 | + | -v $(pwd)/secrets.yaml:/app/secrets.yaml:ro \ | |
| 275 | + | -v $(pwd)/.sops.yaml:/app/.sops.yaml:ro \ | |
| 276 | + | -v ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro \ | |
| 277 | + | -e SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt \ | |
| 278 | + | -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \ | |
| 279 | + | gordon-with-sops | |
| 280 | + | ``` | |
| 281 | + | ||
| 282 | + | ### Docker Compose | |
| 283 | + | ||
| 284 | + | ```yaml | |
| 285 | + | services: | |
| 286 | + | gordon: | |
| 287 | + | build: | |
| 288 | + | context: . | |
| 289 | + | dockerfile: Dockerfile.gordon | |
| 290 | + | container_name: gordon | |
| 291 | + | restart: unless-stopped | |
| 292 | + | ports: | |
| 293 | + | - "80:8080" | |
| 294 | + | - "5000:5000" | |
| 295 | + | volumes: | |
| 296 | + | - /var/run/docker.sock:/var/run/docker.sock | |
| 297 | + | - gordon-data:/data | |
| 298 | + | - ./gordon.toml:/etc/gordon/gordon.toml:ro | |
| 299 | + | - ./secrets.yaml:/app/secrets.yaml:ro | |
| 300 | + | - ./.sops.yaml:/app/.sops.yaml:ro | |
| 301 | + | - ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro | |
| 302 | + | environment: | |
| 303 | + | - SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt | |
| 304 | + | - GORDON_SECRETS_BACKEND=sops | |
| 305 | + | ||
| 306 | + | volumes: | |
| 307 | + | gordon-data: | |
| 308 | + | ``` | |
| 309 | + | ||
| 310 | + | ## Team Workflows | |
| 311 | + | ||
| 312 | + | ### Adding Team Members | |
| 313 | + | ||
| 314 | + | 1. Get their age public key | |
| 315 | + | 2. Add to `.sops.yaml`: | |
| 316 | + | ||
| 317 | + | ```yaml | |
| 318 | + | creation_rules: | |
| 319 | + | - age: >- | |
| 320 | + | age1yourkey..., | |
| 321 | + | age1teammemberkey... | |
| 322 | + | ``` | |
| 323 | + | ||
| 324 | + | 3. Re-encrypt existing files: | |
| 325 | + | ||
| 326 | + | ```bash | |
| 327 | + | sops updatekeys secrets.yaml | |
| 328 | + | ``` | |
| 329 | + | ||
| 330 | + | ### Key Rotation | |
| 331 | + | ||
| 332 | + | 1. Generate new key | |
| 333 | + | 2. Update `.sops.yaml` | |
| 334 | + | 3. Re-encrypt all files: | |
| 335 | + | ||
| 336 | + | ```bash | |
| 337 | + | sops updatekeys secrets.yaml | |
| 338 | + | sops updatekeys secrets.prod.yaml | |
| 339 | + | ``` | |
| 340 | + | ||
| 341 | + | ### Git Workflow | |
| 342 | + | ||
| 343 | + | Encrypted files are safe to commit: | |
| 344 | + | ||
| 345 | + | ```bash | |
| 346 | + | git add .sops.yaml secrets.yaml | |
| 347 | + | git commit -m "Add encrypted secrets" | |
| 348 | + | git push | |
| 349 | + | ``` | |
| 350 | + | ||
| 351 | + | Team members with valid keys can decrypt after pulling. | |
| 352 | + | ||
| 353 | + | ## Troubleshooting | |
| 354 | + | ||
| 355 | + | ### "failed to get the data key" | |
| 356 | + | ||
| 357 | + | Your key isn't in the recipients list or key file is missing: | |
| 358 | + | ||
| 359 | + | ```bash | |
| 360 | + | # Check if age key is set | |
| 361 | + | echo $SOPS_AGE_KEY_FILE | |
| 362 | + | cat $SOPS_AGE_KEY_FILE | |
| 363 | + | ||
| 364 | + | # Check recipients in encrypted file | |
| 365 | + | grep -A5 "age:" secrets.yaml | |
| 366 | + | ``` | |
| 367 | + | ||
| 368 | + | ### "sops: command not found" | |
| 369 | + | ||
| 370 | + | Install SOPS: | |
| 371 | + | ||
| 372 | + | ```bash | |
| 373 | + | # Check if installed | |
| 374 | + | which sops | |
| 375 | + | ||
| 376 | + | # Install if missing (see Installation section) | |
| 377 | + | ``` | |
| 378 | + | ||
| 379 | + | ### "could not decrypt data key" | |
| 380 | + | ||
| 381 | + | Wrong key or corrupted file: | |
| 382 | + | ||
| 383 | + | ```bash | |
| 384 | + | # Verify your public key matches a recipient | |
| 385 | + | age-keygen -y ~/.config/sops/age/keys.txt | |
| 386 | + | # Compare output to recipients in .sops.yaml | |
| 387 | + | ``` | |
| 388 | + | ||
| 389 | + | ### "no matching creation_rules" | |
| 390 | + | ||
| 391 | + | Create or fix `.sops.yaml`: | |
| 392 | + | ||
| 393 | + | ```bash | |
| 394 | + | # Check if .sops.yaml exists | |
| 395 | + | cat .sops.yaml | |
| 396 | + | ||
| 397 | + | # Ensure path_regex matches your file | |
| 398 | + | ``` | |
| 399 | + | ||
| 400 | + | ### Permission Denied in Container | |
| 401 | + | ||
| 402 | + | Ensure key file is readable: | |
| 403 | + | ||
| 404 | + | ```bash | |
| 405 | + | docker exec gordon cat /home/gordon/.config/sops/age/keys.txt | |
| 406 | + | ``` | |
| 407 | + | ||
| 408 | + | Check mount permissions: | |
| 409 | + | ||
| 410 | + | ```bash | |
| 411 | + | ls -la ~/.config/sops/age/keys.txt | |
| 412 | + | # Should be readable by container user | |
| 413 | + | ``` | |
| 414 | + | ||
| 415 | + | ## Security Best Practices | |
| 416 | + | ||
| 417 | + | 1. **Never commit unencrypted secrets**: Always use `sops edit`, never edit decrypted files directly | |
| 418 | + | 2. **Protect age/GPG keys**: Store private keys securely, never commit them | |
| 419 | + | 3. **Use separate keys per environment**: Production keys should be isolated | |
| 420 | + | 4. **Rotate keys regularly**: Update keys and re-encrypt periodically | |
| 421 | + | 5. **Audit access**: Track who has decryption keys for each environment | |
| 422 | + | 6. **Use .sops.yaml**: Enforce encryption rules via configuration | |
| 423 | + | ||
| 424 | + | ## Related | |
| 425 | + | ||
| 426 | + | - [Secrets Configuration](/docs/config/secrets.md) | |
| 427 | + | - [Registry Authentication](/docs/config/registry-auth.md) | |
| 428 | + | - [Running in Containers](/wiki/guides/running-in-container.md) | |
| 429 | + | - [Using Pass for Secrets](./secrets-pass.md) | |
| @@ -0,0 +1,429 @@ | |||||
| 1 | + | # Using SOPS for Secrets | |||
| 2 | + | ||||
| 3 | + | Set up Mozilla SOPS as Gordon's secrets backend for encrypted file-based secret storage. | |||
| 4 | + | ||||
| 5 | + | ## What You'll Learn | |||
| 6 | + | ||||
| 7 | + | - Installing SOPS and age (or GPG) | |||
| 8 | + | - Creating encrypted secrets files | |||
| 9 | + | - Configuring Gordon to use SOPS | |||
| 10 | + | - Using secrets in environment files | |||
| 11 | + | - Team workflows and key rotation | |||
| 12 | + | ||||
| 13 | + | ## Prerequisites | |||
| 14 | + | ||||
| 15 | + | - Linux or macOS system | |||
| 16 | + | - Basic understanding of encryption concepts | |||
| 17 | + | ||||
| 18 | + | ## Why SOPS? | |||
| 19 | + | ||||
| 20 | + | SOPS (Secrets OPerationS) encrypts specific values in YAML/JSON files while keeping keys readable: | |||
| 21 | + | ||||
| 22 | + | - **Git-friendly**: Encrypted files can be version controlled | |||
| 23 | + | - **Multiple backends**: age, GPG, AWS KMS, GCP KMS, Azure Key Vault | |||
| 24 | + | - **Selective encryption**: Only values are encrypted, keys remain visible | |||
| 25 | + | - **Team support**: Multiple recipients can decrypt the same file | |||
| 26 | + | ||||
| 27 | + | ## Installation | |||
| 28 | + | ||||
| 29 | + | ### SOPS | |||
| 30 | + | ||||
| 31 | + | ```bash | |||
| 32 | + | # Ubuntu/Debian | |||
| 33 | + | curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64 | |||
| 34 | + | sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops | |||
| 35 | + | sudo chmod +x /usr/local/bin/sops | |||
| 36 | + | ||||
| 37 | + | # macOS | |||
| 38 | + | brew install sops | |||
| 39 | + | ||||
| 40 | + | # Arch Linux | |||
| 41 | + | sudo pacman -S sops | |||
| 42 | + | ``` | |||
| 43 | + | ||||
| 44 | + | ### age (Recommended) | |||
| 45 | + | ||||
| 46 | + | age is simpler than GPG and recommended for new setups: | |||
| 47 | + | ||||
| 48 | + | ```bash | |||
| 49 | + | # Ubuntu/Debian | |||
| 50 | + | sudo apt install age | |||
| 51 | + | ||||
| 52 | + | # macOS | |||
| 53 | + | brew install age | |||
| 54 | + | ||||
| 55 | + | # Arch Linux | |||
| 56 | + | sudo pacman -S age | |||
| 57 | + | ``` | |||
| 58 | + | ||||
| 59 | + | ### GPG (Alternative) | |||
| 60 | + | ||||
| 61 | + | ```bash | |||
| 62 | + | # Ubuntu/Debian | |||
| 63 | + | sudo apt install gnupg | |||
| 64 | + | ||||
| 65 | + | # macOS | |||
| 66 | + | brew install gnupg | |||
| 67 | + | ``` | |||
| 68 | + | ||||
| 69 | + | ## Setup with age | |||
| 70 | + | ||||
| 71 | + | ### 1. Generate an age Key | |||
| 72 | + | ||||
| 73 | + | ```bash | |||
| 74 | + | # Generate key pair | |||
| 75 | + | age-keygen -o ~/.config/sops/age/keys.txt | |||
| 76 | + | ||||
| 77 | + | # Output shows your public key: | |||
| 78 | + | # Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | |||
| 79 | + | ``` | |||
| 80 | + | ||||
| 81 | + | Note your public key - you'll need it for encryption. | |||
| 82 | + | ||||
| 83 | + | ### 2. Configure SOPS | |||
| 84 | + | ||||
| 85 | + | Create `.sops.yaml` in your project or home directory: | |||
| 86 | + | ||||
| 87 | + | ```yaml | |||
| 88 | + | creation_rules: | |||
| 89 | + | - age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | |||
| 90 | + | ``` | |||
| 91 | + | ||||
| 92 | + | Or for multiple recipients: | |||
| 93 | + | ||||
| 94 | + | ```yaml | |||
| 95 | + | creation_rules: | |||
| 96 | + | - age: >- | |||
| 97 | + | age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, | |||
| 98 | + | age1yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy | |||
| 99 | + | ``` | |||
| 100 | + | ||||
| 101 | + | ### 3. Set age Key Location | |||
| 102 | + | ||||
| 103 | + | ```bash | |||
| 104 | + | # Add to ~/.bashrc or ~/.zshrc | |||
| 105 | + | export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt | |||
| 106 | + | ``` | |||
| 107 | + | ||||
| 108 | + | ## Setup with GPG | |||
| 109 | + | ||||
| 110 | + | ### 1. Generate or Use Existing GPG Key | |||
| 111 | + | ||||
| 112 | + | ```bash | |||
| 113 | + | # Generate new key | |||
| 114 | + | gpg --gen-key | |||
| 115 | + | ||||
| 116 | + | # List keys to get fingerprint | |||
| 117 | + | gpg --list-keys | |||
| 118 | + | ``` | |||
| 119 | + | ||||
| 120 | + | ### 2. Configure SOPS | |||
| 121 | + | ||||
| 122 | + | ```yaml | |||
| 123 | + | creation_rules: | |||
| 124 | + | - pgp: ABCD1234EFGH5678... # Your GPG fingerprint | |||
| 125 | + | ``` | |||
| 126 | + | ||||
| 127 | + | ## Creating Encrypted Secrets | |||
| 128 | + | ||||
| 129 | + | ### Create secrets.yaml | |||
| 130 | + | ||||
| 131 | + | ```bash | |||
| 132 | + | # Create and encrypt in one step | |||
| 133 | + | sops edit secrets.yaml | |||
| 134 | + | ``` | |||
| 135 | + | ||||
| 136 | + | This opens your editor. Add secrets in YAML format: | |||
| 137 | + | ||||
| 138 | + | ```yaml | |||
| 139 | + | registry: | |||
| 140 | + | password_hash: "$2y$10$..." | |||
| 141 | + | token_secret: "your-random-32-char-string-here" | |||
| 142 | + | database: | |||
| 143 | + | password: "db-password-here" | |||
| 144 | + | api: | |||
| 145 | + | key: "api-key-here" | |||
| 146 | + | ``` | |||
| 147 | + | ||||
| 148 | + | Save and exit. SOPS encrypts the values automatically. | |||
| 149 | + | ||||
| 150 | + | ### View Encrypted File | |||
| 151 | + | ||||
| 152 | + | ```bash | |||
| 153 | + | cat secrets.yaml | |||
| 154 | + | ``` | |||
| 155 | + | ||||
| 156 | + | Output shows encrypted values: | |||
| 157 | + | ||||
| 158 | + | ```yaml | |||
| 159 | + | registry: | |||
| 160 | + | password_hash: ENC[AES256_GCM,data:...,iv:...,tag:...] | |||
| 161 | + | token_secret: ENC[AES256_GCM,data:...,iv:...,tag:...] | |||
| 162 | + | database: | |||
| 163 | + | password: ENC[AES256_GCM,data:...,iv:...,tag:...] | |||
| 164 | + | api: | |||
| 165 | + | key: ENC[AES256_GCM,data:...,iv:...,tag:...] | |||
| 166 | + | sops: | |||
| 167 | + | age: | |||
| 168 | + | - recipient: age1xxx... | |||
| 169 | + | enc: | | |||
| 170 | + | -----BEGIN AGE ENCRYPTED FILE----- | |||
| 171 | + | ... | |||
| 172 | + | -----END AGE ENCRYPTED FILE----- | |||
| 173 | + | lastmodified: "2024-01-15T12:00:00Z" | |||
| 174 | + | version: 3.9.0 | |||
| 175 | + | ``` | |||
| 176 | + | ||||
| 177 | + | ### Decrypt and View | |||
| 178 | + | ||||
| 179 | + | ```bash | |||
| 180 | + | # View entire file | |||
| 181 | + | sops decrypt secrets.yaml | |||
| 182 | + | ||||
| 183 | + | # Extract specific value | |||
| 184 | + | sops decrypt --extract '["registry"]["token_secret"]' secrets.yaml | |||
| 185 | + | ``` | |||
| 186 | + | ||||
| 187 | + | ### Edit Existing File | |||
| 188 | + | ||||
| 189 | + | ```bash | |||
| 190 | + | sops edit secrets.yaml | |||
| 191 | + | ``` | |||
| 192 | + | ||||
| 193 | + | ## Gordon Configuration | |||
| 194 | + | ||||
| 195 | + | ### Enable SOPS Backend | |||
| 196 | + | ||||
| 197 | + | ```toml | |||
| 198 | + | [secrets] | |||
| 199 | + | backend = "sops" | |||
| 200 | + | ||||
| 201 | + | [registry_auth] | |||
| 202 | + | enabled = true | |||
| 203 | + | type = "token" | |||
| 204 | + | username = "admin" | |||
| 205 | + | password_hash = "secrets.yaml:registry.password_hash" | |||
| 206 | + | token_secret = "secrets.yaml:registry.token_secret" | |||
| 207 | + | ``` | |||
| 208 | + | ||||
| 209 | + | The path format is `file:key.path` where: | |||
| 210 | + | - `file` is the SOPS-encrypted file path | |||
| 211 | + | - `key.path` is dot-notation to the value | |||
| 212 | + | ||||
| 213 | + | ### Using Secrets in Environment Files | |||
| 214 | + | ||||
| 215 | + | Reference SOPS secrets in your app's environment files: | |||
| 216 | + | ||||
| 217 | + | ```bash | |||
| 218 | + | # ~/.gordon/env/app_mydomain_com.env | |||
| 219 | + | DATABASE_URL=postgresql://user:${sops:secrets.yaml:database.password}@postgres:5432/app | |||
| 220 | + | API_KEY=${sops:secrets.yaml:api.key} | |||
| 221 | + | ``` | |||
| 222 | + | ||||
| 223 | + | ## File Organization | |||
| 224 | + | ||||
| 225 | + | Recommended structure: | |||
| 226 | + | ||||
| 227 | + | ``` | |||
| 228 | + | project/ | |||
| 229 | + | ├── .sops.yaml # SOPS configuration | |||
| 230 | + | ├── secrets.yaml # Encrypted secrets (commit this) | |||
| 231 | + | ├── secrets.dev.yaml # Dev environment secrets | |||
| 232 | + | ├── secrets.prod.yaml # Production secrets | |||
| 233 | + | └── gordon.toml # Gordon config (references secrets) | |||
| 234 | + | ``` | |||
| 235 | + | ||||
| 236 | + | ### Environment-Specific Rules | |||
| 237 | + | ||||
| 238 | + | ```yaml | |||
| 239 | + | # .sops.yaml | |||
| 240 | + | creation_rules: | |||
| 241 | + | # Dev secrets - developer keys | |||
| 242 | + | - path_regex: \.dev\.yaml$ | |||
| 243 | + | age: age1devkey... | |||
| 244 | + | ||||
| 245 | + | # Prod secrets - production keys only | |||
| 246 | + | - path_regex: \.prod\.yaml$ | |||
| 247 | + | age: age1prodkey... | |||
| 248 | + | ||||
| 249 | + | # Default | |||
| 250 | + | - age: age1defaultkey... | |||
| 251 | + | ``` | |||
| 252 | + | ||||
| 253 | + | ## Running in Containers | |||
| 254 | + | ||||
| 255 | + | ### Custom Dockerfile | |||
| 256 | + | ||||
| 257 | + | ```dockerfile | |||
| 258 | + | FROM ghcr.io/bnema/gordon:latest | |||
| 259 | + | ||||
| 260 | + | USER root | |||
| 261 | + | RUN apk add --no-cache sops age | |||
| 262 | + | USER gordon | |||
| 263 | + | ``` | |||
| 264 | + | ||||
| 265 | + | ### Mount Secrets and Keys | |||
| 266 | + | ||||
| 267 | + | ```bash | |||
| 268 | + | docker run -d \ | |||
| 269 | + | --name gordon \ | |||
| 270 | + | -p 80:8080 \ | |||
| 271 | + | -p 5000:5000 \ | |||
| 272 | + | -v /var/run/docker.sock:/var/run/docker.sock \ | |||
| 273 | + | -v gordon-data:/data \ | |||
| 274 | + | -v $(pwd)/secrets.yaml:/app/secrets.yaml:ro \ | |||
| 275 | + | -v $(pwd)/.sops.yaml:/app/.sops.yaml:ro \ | |||
| 276 | + | -v ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro \ | |||
| 277 | + | -e SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt \ | |||
| 278 | + | -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \ | |||
| 279 | + | gordon-with-sops | |||
| 280 | + | ``` | |||
| 281 | + | ||||
| 282 | + | ### Docker Compose | |||
| 283 | + | ||||
| 284 | + | ```yaml | |||
| 285 | + | services: | |||
| 286 | + | gordon: | |||
| 287 | + | build: | |||
| 288 | + | context: . | |||
| 289 | + | dockerfile: Dockerfile.gordon | |||
| 290 | + | container_name: gordon | |||
| 291 | + | restart: unless-stopped | |||
| 292 | + | ports: | |||
| 293 | + | - "80:8080" | |||
| 294 | + | - "5000:5000" | |||
| 295 | + | volumes: | |||
| 296 | + | - /var/run/docker.sock:/var/run/docker.sock | |||
| 297 | + | - gordon-data:/data | |||
| 298 | + | - ./gordon.toml:/etc/gordon/gordon.toml:ro | |||
| 299 | + | - ./secrets.yaml:/app/secrets.yaml:ro | |||
| 300 | + | - ./.sops.yaml:/app/.sops.yaml:ro | |||
| 301 | + | - ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro | |||
| 302 | + | environment: | |||
| 303 | + | - SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt | |||
| 304 | + | - GORDON_SECRETS_BACKEND=sops | |||
| 305 | + | ||||
| 306 | + | volumes: | |||
| 307 | + | gordon-data: | |||
| 308 | + | ``` | |||
| 309 | + | ||||
| 310 | + | ## Team Workflows | |||
| 311 | + | ||||
| 312 | + | ### Adding Team Members | |||
| 313 | + | ||||
| 314 | + | 1. Get their age public key | |||
| 315 | + | 2. Add to `.sops.yaml`: | |||
| 316 | + | ||||
| 317 | + | ```yaml | |||
| 318 | + | creation_rules: | |||
| 319 | + | - age: >- | |||
| 320 | + | age1yourkey..., | |||
| 321 | + | age1teammemberkey... | |||
| 322 | + | ``` | |||
| 323 | + | ||||
| 324 | + | 3. Re-encrypt existing files: | |||
| 325 | + | ||||
| 326 | + | ```bash | |||
| 327 | + | sops updatekeys secrets.yaml | |||
| 328 | + | ``` | |||
| 329 | + | ||||
| 330 | + | ### Key Rotation | |||
| 331 | + | ||||
| 332 | + | 1. Generate new key | |||
| 333 | + | 2. Update `.sops.yaml` | |||
| 334 | + | 3. Re-encrypt all files: | |||
| 335 | + | ||||
| 336 | + | ```bash | |||
| 337 | + | sops updatekeys secrets.yaml | |||
| 338 | + | sops updatekeys secrets.prod.yaml | |||
| 339 | + | ``` | |||
| 340 | + | ||||
| 341 | + | ### Git Workflow | |||
| 342 | + | ||||
| 343 | + | Encrypted files are safe to commit: | |||
| 344 | + | ||||
| 345 | + | ```bash | |||
| 346 | + | git add .sops.yaml secrets.yaml | |||
| 347 | + | git commit -m "Add encrypted secrets" | |||
| 348 | + | git push | |||
| 349 | + | ``` | |||
| 350 | + | ||||
| 351 | + | Team members with valid keys can decrypt after pulling. | |||
| 352 | + | ||||
| 353 | + | ## Troubleshooting | |||
| 354 | + | ||||
| 355 | + | ### "failed to get the data key" | |||
| 356 | + | ||||
| 357 | + | Your key isn't in the recipients list or key file is missing: | |||
| 358 | + | ||||
| 359 | + | ```bash | |||
| 360 | + | # Check if age key is set | |||
| 361 | + | echo $SOPS_AGE_KEY_FILE | |||
| 362 | + | cat $SOPS_AGE_KEY_FILE | |||
| 363 | + | ||||
| 364 | + | # Check recipients in encrypted file | |||
| 365 | + | grep -A5 "age:" secrets.yaml | |||
| 366 | + | ``` | |||
| 367 | + | ||||
| 368 | + | ### "sops: command not found" | |||
| 369 | + | ||||
| 370 | + | Install SOPS: | |||
| 371 | + | ||||
| 372 | + | ```bash | |||
| 373 | + | # Check if installed | |||
| 374 | + | which sops | |||
| 375 | + | ||||
| 376 | + | # Install if missing (see Installation section) | |||
| 377 | + | ``` | |||
| 378 | + | ||||
| 379 | + | ### "could not decrypt data key" | |||
| 380 | + | ||||
| 381 | + | Wrong key or corrupted file: | |||
| 382 | + | ||||
| 383 | + | ```bash | |||
| 384 | + | # Verify your public key matches a recipient | |||
| 385 | + | age-keygen -y ~/.config/sops/age/keys.txt | |||
| 386 | + | # Compare output to recipients in .sops.yaml | |||
| 387 | + | ``` | |||
| 388 | + | ||||
| 389 | + | ### "no matching creation_rules" | |||
| 390 | + | ||||
| 391 | + | Create or fix `.sops.yaml`: | |||
| 392 | + | ||||
| 393 | + | ```bash | |||
| 394 | + | # Check if .sops.yaml exists | |||
| 395 | + | cat .sops.yaml | |||
| 396 | + | ||||
| 397 | + | # Ensure path_regex matches your file | |||
| 398 | + | ``` | |||
| 399 | + | ||||
| 400 | + | ### Permission Denied in Container | |||
| 401 | + | ||||
| 402 | + | Ensure key file is readable: | |||
| 403 | + | ||||
| 404 | + | ```bash | |||
| 405 | + | docker exec gordon cat /home/gordon/.config/sops/age/keys.txt | |||
| 406 | + | ``` | |||
| 407 | + | ||||
| 408 | + | Check mount permissions: | |||
| 409 | + | ||||
| 410 | + | ```bash | |||
| 411 | + | ls -la ~/.config/sops/age/keys.txt | |||
| 412 | + | # Should be readable by container user | |||
| 413 | + | ``` | |||
| 414 | + | ||||
| 415 | + | ## Security Best Practices | |||
| 416 | + | ||||
| 417 | + | 1. **Never commit unencrypted secrets**: Always use `sops edit`, never edit decrypted files directly | |||
| 418 | + | 2. **Protect age/GPG keys**: Store private keys securely, never commit them | |||
| 419 | + | 3. **Use separate keys per environment**: Production keys should be isolated | |||
| 420 | + | 4. **Rotate keys regularly**: Update keys and re-encrypt periodically | |||
| 421 | + | 5. **Audit access**: Track who has decryption keys for each environment | |||
| 422 | + | 6. **Use .sops.yaml**: Enforce encryption rules via configuration | |||
| 423 | + | ||||
| 424 | + | ## Related | |||
| 425 | + | ||||
| 426 | + | - [Secrets Configuration](/docs/config/secrets.md) | |||
| 427 | + | - [Registry Authentication](/docs/config/registry-auth.md) | |||
| 428 | + | - [Running in Containers](/wiki/guides/running-in-container.md) | |||
| 429 | + | - [Using Pass for Secrets](./secrets-pass.md) | |||