feat(docker-watcher): phase 2 - crypto & config seed loader

AES-256-GCM encryption for credential storage, YAML seed config
parser with validation, and transactional import into SQLite.
Credentials (registry tokens, NPM password) encrypted before storage.
This commit is contained in:
2026-03-27 21:01:16 +03:00
parent d63c831d15
commit cdf21682d6
10 changed files with 602 additions and 19 deletions
@@ -1,6 +1,6 @@
# Phase 2: Crypto & Config Seed Loader
**Status:** ⬜ Not Started
**Status:** ✅ Complete
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
@@ -9,14 +9,14 @@ Implement AES-256 encryption for credential storage and the YAML seed config par
## Tasks
- [ ] Task 1: Implement AES-256-GCM encrypt/decrypt functions using Go stdlib `crypto/aes` + `crypto/cipher`
- [ ] Task 2: Key derivation from ENCRYPTION_KEY env var (SHA-256 hash to get 32 bytes)
- [ ] Task 3: Define YAML config structs matching the seed format from PLAN.md
- [ ] Task 4: Implement YAML parser — read and validate seed file
- [ ] Task 5: Implement seed importer — checks if DB is empty, if so imports YAML into SQLite via store CRUD
- [ ] Task 6: Encrypt credential fields (registry tokens, NPM password) during import
- [ ] Task 7: Create `docker-watcher.example.yaml` with documented example config
- [ ] Task 8: Wire seed import into `cmd/server/main.go` startup sequence
- [x] Task 1: Implement AES-256-GCM encrypt/decrypt functions using Go stdlib `crypto/aes` + `crypto/cipher`
- [x] Task 2: Key derivation from ENCRYPTION_KEY env var (SHA-256 hash to get 32 bytes)
- [x] Task 3: Define YAML config structs matching the seed format from PLAN.md
- [x] Task 4: Implement YAML parser — read and validate seed file
- [x] Task 5: Implement seed importer — checks if DB is empty, if so imports YAML into SQLite via store CRUD
- [x] Task 6: Encrypt credential fields (registry tokens, NPM password) during import
- [x] Task 7: Create `docker-watcher.example.yaml` with documented example config
- [x] Task 8: Wire seed import into `cmd/server/main.go` startup sequence
## Files to Modify/Create
- `internal/crypto/crypto.go` — AES-256-GCM encrypt/decrypt
@@ -40,11 +40,22 @@ Implement AES-256 encryption for credential storage and the YAML seed config par
- The example YAML should have placeholder values, not real credentials
## Review Checklist
- [ ] All tasks completed
- [ ] Crypto uses secure practices (random nonce, GCM, no ECB)
- [ ] No hardcoded keys or secrets
- [ ] YAML parsing validates required fields
- [ ] Import is transactional
- [x] All tasks completed
- [x] Crypto uses secure practices (random nonce, GCM, no ECB)
- [x] No hardcoded keys or secrets
- [x] YAML parsing validates required fields
- [x] Import is transactional
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
- `crypto.Encrypt(key, plaintext)` and `crypto.Decrypt(key, ciphertextHex)` handle AES-256-GCM encryption; ciphertext is hex-encoded with prepended nonce
- `crypto.KeyFromEnv()` derives a `[32]byte` key from the `ENCRYPTION_KEY` env var via SHA-256
- `crypto.EncryptIfNotEmpty(key, value)` is a convenience wrapper that passes through empty strings unchanged
- `config.ImportSeed(db, seedPath)` is the single entry point for seed import — called from `main.go` at startup
- Import is idempotent: skipped if the DB already has projects or registries
- Import is transactional: all inserts happen within a single SQLite transaction (rollback on any failure)
- Registry `token` and settings `npm_password` are now stored encrypted in SQLite — later phases that read these fields must decrypt with `crypto.Decrypt(key, value)`
- `store.DB()` method was added to expose the underlying `*sql.DB` for transaction use
- Seed file path is configurable via `SEED_FILE` env var (default: `./docker-watcher.yaml`)
- YAML validation ensures: `global.domain` is required, every project needs `image`, project registry references must exist, stages need `tag_pattern`
- `go.sum` still does not exist — run `go mod tidy` when Go toolchain is available