Stored credential secrets are encrypted with a key derived from
ORCHESTRAD_SECRET_KEY. When that value changes, the secrets are intact but
unreadable, and the only symptom was an opaque "decryption failed" surfacing
deep inside an unrelated operation:
"preview failed: building LDAP client: failed to decrypt credential:
decryption failed"
Nothing pointed at the real cause, so the error is now self-diagnosing:
- ErrDecryptionFailed states that the data was encrypted under a different
ORCHESTRAD_SECRET_KEY (or is corrupted). GCM auth failure on a well-formed
ciphertext is overwhelmingly a wrong-key case.
- The three credential decrypt sites name the credential, so the operator
knows which password to restore or re-enter.
- New services.CheckSecretKey verifies every stored secret against the
current key. It runs at startup (LogSecretKeyCheck) and in `doctor`, so a
mismatched key is reported once, loudly, at the moment it is first used
rather than during the next rule run. A correctly-sized but *different*
key passed doctor's existing length check and still broke every bind.
Not fatal: the server still starts, since an operator may be mid-migration
or may intend to re-enter the secrets.
Verified on the demo instance: starting with a wrong key logs
"1 of 1 stored credential secret(s) CANNOT be decrypted ... [OrchestrAD]",
and the rule preview error now names both the credential and the key.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guards the Argon2id parsing path against a regression where the encoded
hash (which contains dollar-delimited base64) was fed through fmt.Sscanf
and silently rejected valid passwords. Tests cover round-trip with a
variety of passwords including unicode, rejection of wrong passwords,
the six-segment encoded shape, and malformed-hash cases.
The encoded hash format produced by HashPassword contains base64-encoded
salt and hash segments separated by dollar signs. fmt.Sscanf's %s verb
does not stop at dollar signs, so VerifyPassword was silently failing to
parse any valid hash, rejecting all correct passwords with a 401.
Switch to strings.Split on the six-segment encoded layout and validate
the prefix, version, parameter header, and base64 segments explicitly.
Phase 1 foundations:
- Go backend with Chi router framework
- SQLite database with WAL mode and foreign keys
- Database migrations for users, roles, credentials, AD connections, schedules, rules, and audit
- CLI commands: init, run, install, uninstall, start, stop, migrate, backup, restore, doctor
- Configuration loading from environment variables
- Centralized logging with file rotation (lumberjack)
- Crypto package for Argon2id password hashing and AES-GCM encryption
- Auth service with session management
- Audit service for event logging
- Scheduler with 6-field cron support
- REST API routes scaffolded for all major resources
- CORS support with localhost defaults for development
- Docker support with Dockerfile and docker-compose.yml
- Multi-platform build script (PowerShell)
- Project structure per design specification
Version format: yyyy.MM.dd.HHmm
All PKs are UUIDv4, all timestamps UTC