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>
The OpenAPI spec and Swagger UI were public. Put them behind the same
authentication as the rest of the API, and add a compact route list so a
client can ask "what can I call?" without opening dev tools.
Access:
- /api/openapi.json and /api/routes require a bearer token or API key.
- /api/docs additionally accepts a session cookie set at login, so a
signed-in operator can open the docs in a new tab; an anonymous browser
is redirected to /login?redirect=... and returned afterwards.
- The cookie is HttpOnly and path-scoped to /api/docs, so it is never sent
to /api/v1/* and cannot authenticate an API call (no CSRF surface).
Verified: cookie-only request to /api/v1/rules returns 401.
Discovery: both the spec and GET /api/routes accept ?method=get,post and
?path=<substring> (comma-separated, case-insensitive). The route list
returns method, path, summary, tag, public, and `allowed` — false when a
read-scoped API key cannot invoke that route. /api/docs passes the same
query through to the spec it loads.
UI: a </> icon in the header (both layouts) and an Administration → API
Docs menu entry, opened in a new tab via a new `external` nav-item flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>