Add SOPS secret management documentation
This commit is contained in:
150
SKILL.md
150
SKILL.md
@@ -122,6 +122,129 @@ DB_DATABASE_NAME=<project>
|
|||||||
- `environments/<hostname>/.env` overrides paths for specific hosts
|
- `environments/<hostname>/.env` overrides paths for specific hosts
|
||||||
- Common overrides: mount paths (`/mnt/user/` vs `/mnt/main/`), ports
|
- Common overrides: mount paths (`/mnt/user/` vs `/mnt/main/`), ports
|
||||||
|
|
||||||
|
## Secret Management with SOPS
|
||||||
|
|
||||||
|
Projects use SOPS with age encryption to securely commit secrets to git. This replaces the manual "secret scrubbing" approach.
|
||||||
|
|
||||||
|
### Directory Structure (with SOPS)
|
||||||
|
|
||||||
|
```
|
||||||
|
/docker/config/<project>/
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── docker-compose.override.yml # (gitignored)
|
||||||
|
├── .env # (gitignored, generated by decrypt)
|
||||||
|
├── .gitignore
|
||||||
|
├── .sops.yaml # SOPS encryption config
|
||||||
|
├── Makefile # encrypt/decrypt targets
|
||||||
|
├── env.sops.yaml # Encrypted env vars (committed)
|
||||||
|
└── environments/
|
||||||
|
└── <hostname>/
|
||||||
|
├── env.sops.yaml # Encrypted, host-specific (committed)
|
||||||
|
└── .env # (gitignored, generated)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Repository
|
||||||
|
|
||||||
|
Public keys are managed centrally at `~/repos/sops-age-keys/`:
|
||||||
|
- `recipients/*.pub` - Public keys for each host
|
||||||
|
- `scripts/init-host.sh` - Generate key for new host
|
||||||
|
- `scripts/add-recipient.sh` - Add host's public key
|
||||||
|
- `templates/Makefile` - Makefile template for projects
|
||||||
|
|
||||||
|
Private keys stay at `~/.config/sops/age/keys.txt` (never committed).
|
||||||
|
|
||||||
|
### .sops.yaml Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: env\.sops\.yaml$
|
||||||
|
unencrypted_regex: "_unencrypted$"
|
||||||
|
age: >-
|
||||||
|
age1abc...,age1def...
|
||||||
|
```
|
||||||
|
|
||||||
|
Get recipient keys from `~/repos/sops-age-keys/recipients/*.pub`.
|
||||||
|
|
||||||
|
### env.sops.yaml Format
|
||||||
|
|
||||||
|
Non-secret values use `_unencrypted` suffix (remain readable in git):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Non-secrets (readable in git)
|
||||||
|
UID_unencrypted: "1000"
|
||||||
|
GID_unencrypted: "1000"
|
||||||
|
UPLOAD_LOCATION_unencrypted: "/mnt/user/pictures"
|
||||||
|
DB_DATA_LOCATION_unencrypted: "/mnt/user/appdata/immich"
|
||||||
|
APP_PORT_unencrypted: "2283"
|
||||||
|
DB_HOSTNAME_unencrypted: "database"
|
||||||
|
DB_USERNAME_unencrypted: "postgres"
|
||||||
|
DB_DATABASE_NAME_unencrypted: "immich"
|
||||||
|
|
||||||
|
# Secrets (encrypted by SOPS)
|
||||||
|
DB_PASSWORD: "actual_secret_value"
|
||||||
|
```
|
||||||
|
|
||||||
|
After encryption, only `DB_PASSWORD` value is encrypted; `_unencrypted` values remain readable.
|
||||||
|
|
||||||
|
### Makefile Targets
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
SOURCE ?= env.sops.yaml
|
||||||
|
TARGET ?= .env
|
||||||
|
|
||||||
|
decrypt:
|
||||||
|
@sops decrypt --output-type dotenv $(SOURCE) | sed 's/_unencrypted=/=/g' > $(TARGET)
|
||||||
|
@echo "Decrypted $(SOURCE) -> $(TARGET)"
|
||||||
|
|
||||||
|
encrypt:
|
||||||
|
@sops encrypt --input-type dotenv --output $(SOURCE) $(TARGET)
|
||||||
|
@echo "Encrypted $(TARGET) -> $(SOURCE)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -f $(TARGET)
|
||||||
|
@echo "Removed $(TARGET)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common SOPS Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Decrypt to .env (for Docker Compose)
|
||||||
|
make decrypt SOURCE=environments/<host>/env.sops.yaml TARGET=environments/<host>/.env
|
||||||
|
|
||||||
|
# Edit secrets directly (decrypts in editor, re-encrypts on save)
|
||||||
|
sops environments/<host>/env.sops.yaml
|
||||||
|
|
||||||
|
# Encrypt after editing .env
|
||||||
|
make encrypt SOURCE=environments/<host>/env.sops.yaml TARGET=environments/<host>/.env
|
||||||
|
|
||||||
|
# Add new host to recipients (after adding to .sops.yaml)
|
||||||
|
sops updatekeys environments/<host>/env.sops.yaml
|
||||||
|
|
||||||
|
# Clean up decrypted file
|
||||||
|
make clean TARGET=environments/<host>/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Starting Services with SOPS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /docker/config/<project>
|
||||||
|
|
||||||
|
# Decrypt secrets
|
||||||
|
make decrypt SOURCE=environments/<host>/env.sops.yaml TARGET=environments/<host>/.env
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker compose --env-file environments/<host>/.env \
|
||||||
|
-f docker-compose.yml \
|
||||||
|
-f environments/<host>/docker-compose.override.<host>.yml up -d
|
||||||
|
|
||||||
|
# Clean up (optional, secrets stay decrypted for restarts)
|
||||||
|
make clean TARGET=environments/<host>/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migrating Existing Projects
|
||||||
|
|
||||||
|
See `~/repos/sops-age-keys/README.md` for full migration guide, or `~/repos/sops-age-keys/docs/design.md` for architecture details.
|
||||||
|
|
||||||
## Common Commands
|
## Common Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -165,34 +288,17 @@ Each project should be version controlled with its own git repository.
|
|||||||
### .gitignore Configuration
|
### .gitignore Configuration
|
||||||
|
|
||||||
```gitignore
|
```gitignore
|
||||||
# Root environment file (may contain active secrets)
|
# Root environment file (generated, may contain secrets)
|
||||||
/.env
|
/.env
|
||||||
|
|
||||||
# Docker compose override (host-specific, not committed)
|
# Docker compose override (host-specific, not committed)
|
||||||
/docker-compose.override.yml
|
/docker-compose.override.yml
|
||||||
|
|
||||||
|
# Per-host decrypted .env files (generated from env.sops.yaml)
|
||||||
|
/environments/*/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Use `/.env` (with leading slash) to only exclude the root `.env` file. Environment files in `environments/<hostname>/` are committed after secret scrubbing.
|
**Note**: All `.env` files are gitignored. Secrets are stored encrypted in `env.sops.yaml` files using SOPS (see "Secret Management with SOPS" section above).
|
||||||
|
|
||||||
### Secret Scrubbing
|
|
||||||
|
|
||||||
Before committing `environments/<hostname>/.env` files, replace secret values:
|
|
||||||
|
|
||||||
| Secret Type | Original | Scrubbed |
|
|
||||||
|-------------|----------|----------|
|
|
||||||
| Passwords | `DB_PASSWORD=actual_password` | `DB_PASSWORD=CHANGE_ME_SECRET` |
|
|
||||||
| API Keys | `API_KEY=sk-abc123...` | `API_KEY=CHANGE_ME_SECRET` |
|
|
||||||
| Tokens | `AUTH_TOKEN=token_value` | `AUTH_TOKEN=CHANGE_ME_SECRET` |
|
|
||||||
|
|
||||||
**Keep in version control** (non-secret, host-specific):
|
|
||||||
- Paths: `DATA_LOCATION`, `UPLOAD_PATH`
|
|
||||||
- Ports: `APP_PORT`, `DB_PORT`
|
|
||||||
- UIDs/GIDs: `UID`, `GID`
|
|
||||||
- URLs: `APP_URL`, `DB_HOSTNAME`
|
|
||||||
- Names: `DB_DATABASE_NAME`, `DB_USERNAME`
|
|
||||||
|
|
||||||
**Exclude or scrub**:
|
|
||||||
- Passwords, API keys, tokens, secrets
|
|
||||||
|
|
||||||
### Creating a Git Repository
|
### Creating a Git Repository
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user