Files
tiny-forge/plans/docker-watcher-core/phase-2-crypto-config.md
T
alexei.dolgolyov cdf21682d6 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.
2026-03-27 21:01:16 +03:00

3.4 KiB

Phase 2: Crypto & Config Seed Loader

Status: Complete Parent plan: PLAN.md Domain: backend

Objective

Implement AES-256 encryption for credential storage and the YAML seed config parser that imports into SQLite on first launch.

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

Files to Modify/Create

  • internal/crypto/crypto.go — AES-256-GCM encrypt/decrypt
  • internal/config/config.go — YAML structs and parser
  • internal/config/seed.go — seed import logic (YAML → SQLite)
  • docker-watcher.example.yaml — example seed config
  • cmd/server/main.go — add seed import to startup

Acceptance Criteria

  • Encrypt then decrypt round-trips correctly
  • Different plaintexts produce different ciphertexts (random nonce)
  • YAML parsing handles all fields from the seed format
  • Seed import creates projects, stages, registries, and settings in SQLite
  • Credentials are encrypted before storage
  • Import is idempotent — skipped if DB already has data

Notes

  • ENCRYPTION_KEY is the only secret env var — everything else is encrypted in SQLite
  • Use GCM mode for authenticated encryption (integrity + confidentiality)
  • Seed import should be transactional — all or nothing
  • 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

Handoff to Next 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