Commit Graph

23 Commits

Author SHA1 Message Date
Alphaeus Mote 63701dd086 feat: real restore, portable secret key, multi-arch image, real CSRF
Addresses the gaps identified in the last audit.

Restore (was a stub returning "not yet implemented"). Every repository shares
one connection pool, so the database cannot be swapped underneath a live
server. Restore is therefore two-phase: RestoreBackup validates the file and
stages it beside the database; db.New applies it before the pool is opened,
which is the only safe moment. The database being replaced is preserved as
<db>.replaced-<timestamp>, and stale -wal/-shm are removed so SQLite cannot
replay the old journal over the restored file. Validation is strict — SQLite
integrity_check plus a schema probe — because applying an unrelated file
would destroy the install. GET/DELETE /api/v1/backups/restore inspect and
cancel a staged restore. The CLI does both phases at once, since it runs
standalone; `orchestrad backup` was also a stub and now works.

Secret key. With nothing configured the key is generated once and persisted
to <data>/secret.key, so restarts reuse it and moving the stack to another
server is a matter of copying the data directory. Upgrades are handled: if a
database already exists the install was silently running on the legacy
built-in default, so that value is adopted and written out rather than
replaced — generating a fresh key there would make every stored credential
undecryptable. The file is owner-only (ACL-restricted on Windows).

Multi-arch image: buildx now emits linux/amd64 + linux/arm64, matching the
architectures the release binaries already covered. The Dockerfile
cross-compiles via TARGETARCH rather than emulating, so arm64 costs little.

CSRF: the middleware previously checked only that a header was *present* and
was never wired up, and /auth/csrf returned "csrf-token-placeholder". Tokens
are now nonce + HMAC-SHA256 signed with the application secret, validated
properly, and the middleware is mounted on /api/v1. Bearer and API-key
requests are not CSRF-reachable and pass through untouched, so this is
transparent to the SPA and to API clients.

Also: the Windows store import drops CRYPT_EXPORTABLE (the store copy is not
the source of truth — <data>/tls holds the key, so portability is unaffected
and a non-exportable server key is the better posture), the PFX password is
written to server.pfx.password beside the bundle so an operator importing it
by hand does not have to hunt for a password they never chose, and the
"renewed" log line now reflects whether a leaf was actually issued instead of
guessing from its age.

Verified live: backup -> stage -> restart applies and preserves the previous
database; secret key generated, adopted, and read back across restarts with
the credential check confirming decryptability; CSRF endpoint issues real
signed tokens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:57:15 -04:00
Alphaeus Mote 5301b47f16 fix(crypto): explain that a decryption failure means the secret key changed
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>
2026-09-03 10:59:43 -04:00
Alphaeus Mote 13b336a77e feat(connections): LDAPS port auto-flip + skip-cert default; keep 3 backups
- Toggling LDAPS in the connection form now auto-sets the port (636 on / 389
  off when it's still the other default) and enables "Allow invalid certs",
  because a non-domain-joined host usually can't validate the DC's LDAPS
  certificate. This fixes the common "existing connection was forcibly
  closed" seen when LDAPS was enabled while the port stayed 389 (dialing TLS
  to the plaintext port). Helper text explains the traffic is still encrypted.
- LDAP client sets tls ServerName to the host so verification succeeds when a
  DC cert IS trusted (ignored under InsecureSkipVerify).
- Backups retention reduced from 10 to 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 22:01:34 -04:00
Alphaeus Mote 1e38b3b99a feat(maintenance): history retention + VACUUM; robust config import
Database maintenance:
- New MaintenanceService prunes rule_runs (+ their action detail) and
  audit_events older than their retention windows, then VACUUMs to reclaim
  space. Runs once at startup and then on an interval, bound to the run
  context. Configurable via ORCHESTRAD_RUN_RETENTION_DAYS (90),
  ORCHESTRAD_AUDIT_RETENTION_DAYS (180),
  ORCHESTRAD_MAINTENANCE_INTERVAL_HOURS (24), ORCHESTRAD_MAINTENANCE_VACUUM.
  This stops the database growing forever. (Log rotation already existed via
  lumberjack: ORCHESTRAD_LOG_MAX_SIZE_MB/_MAX_BACKUPS/_MAX_AGE_DAYS.)

Config import:
- Import now accepts either the wrapped {payload,dryRun} shape or a bare
  exported config object, so a file downloaded from Export re-imports
  directly (dryRun via ?dryRun=true) — useful for automation.

Test covers retention pruning with FK-cascaded action rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 18:25:14 -04:00
Alphaeus Mote 1f77473d8f feat(server): trust local proxies by default, seed built-in schedules
Proxy / base-URL:
- ORCHESTRAD_TRUSTED_PROXIES now defaults to "local", trusting reverse
  proxies in loopback + RFC1918 + link-local/ULA ranges out of the box, so
  X-Forwarded-* (client IP, scheme, host) is honored behind an edge proxy
  without extra config. New keywords: local/private, all/any, none.
- OIDC redirect URI derivation now uses the trust-gated request base URL
  instead of reading X-Forwarded-Proto directly, and audit client IP now
  trusts the middleware-rewritten RemoteAddr rather than the raw (spoofable)
  X-Forwarded-For header. Both honor forwarded values only from trusted
  peers.

Schedules:
- Seed eight built-in schedules on startup (every 5/15/30 min, hourly,
  every 6/12h, daily, weekly), idempotent by name, so operators have
  ready-made cadences in the Schedules page and the rule editor's schedule
  dropdown without hand-building one.

Test covers the trusted-proxy keyword expansion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 17:14:49 -04:00
Alphaeus Mote e74cb454c8 feat(activity): add action-ledger intelligence and drill-in feed
Turn the rule_run_actions ledger into operator intelligence. A new
ActivityService rolls the ledger up by time window (24h / 7d / all-time)
into syncs, operations and removals with success/failure counts and the
number of distinct objects affected, plus all-time totals per action type
and a most-active-rules ranking. A companion filtered, paginated feed
answers the operational questions directly: what action ran, what
happened, to which object, triggered by whom, and when.

Backend:
- ActivityService.Summary() and ListActions(filter) over the joined
  rule_run_actions / rule_runs / rules tables, categorising each action
  type as Sync (AddToGroup/AddGroupToGroup), Removal
  (RemoveFromGroupIfNoLongerMatched) or Operation (everything else).
- GET /api/v1/activity/summary and GET /api/v1/activity (category,
  actionType, status, ruleId, search, pagination).
- Table-driven test seeding a run with mixed action types/statuses and
  asserting window roll-ups, distinct-object counts, top rules, and the
  category/status feed filters.

Frontend:
- New Activity page: window summary cards, by-action-type and
  most-active-rules tables, and a drill-in feed with category/result/
  object filters and a detail dialog that pretty-prints the action's
  details JSON and error.
- Wired into the vertical sidebar and horizontal navbar under Automation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 14:09:04 -04:00
Alphaeus Mote a4698ee9f9 feat(tls): certificate modes (auto/provided/windows-store) + config API + richer SANs
Add runtime TLS modes resolved via settings (UI wins over env): auto
(self-managed), provided (bring-your-own upload), and windows-store (Windows
cert store, enumeration/serving scaffolded). New /api/v1/tls endpoints: status,
mode, certificate upload, and windows-store list; changes reload the live
manager without a restart, falling back to the self-managed cert if a configured
source is unavailable. Auto SANs now cover the hostname (CN), localhost, every
non-loopback IP, and host.<suffix> + the suffix for each detected DNS suffix
(Windows registry / resolv.conf). Bring-your-own is validated and unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:21:00 -04:00
Alphaeus Mote 300866d5cf feat(tls): auto-managed HTTPS with renewal; disable for edge termination
Add internal/tlsmgr: on startup, load-or-generate the CA, issue/reuse the leaf,
export the chain under <data>/tls, and hot-swap the cert on renewal via
GetCertificate. The server listens HTTPS when a manager is present. TLS is on by
default for the standalone binary/service (tls.enabled setting or
ORCHESTRAD_TLS_ENABLED, UI wins over env); the container image defaults it off so
it runs plain HTTP behind a TLS-terminating proxy. Startup logs a clickable URL.
Verified end to end: HTTPS serves, plain HTTP rejected, disable serves HTTP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:06:42 -04:00
Alphaeus Mote 491b5abe83 feat(cli): idempotent initialize/remove service commands
Add initialize (install + start) and remove (stop + uninstall) that are safe to
re-run: initialize skips reinstall/restart when already up, remove treats a
not-installed service as done. install/uninstall become aliases. Uses the
service status to branch. Verified idempotent on Windows (repeat runs exit 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 08:55:33 -04:00
Alphaeus Mote 0debf9ab33 feat(cli): real cross-platform service management
Implement install/uninstall/start/stop via kardianos/service (Windows SCM,
systemd/upstart/sysv, launchd). The run command now goes through service.Run so
foreground/container and service execution share one startup path; install also
starts the service and uninstall stops then removes it. Route CLI and main
command output/errors through the centralized logger. Verified end to end as a
real Windows service (install -> RUNNING -> uninstall).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 08:49:15 -04:00
GraceSolutions 336d60926b feat(auth): seed initial admin user from env on first run
When the users table is empty on startup (typical first boot into a
fresh data directory), create a single Admin-role user called admin
so the operator has something to log in with before an IdP is wired.

Password source precedence:
  1. ORCHESTRAD_BOOTSTRAP_PASSWORD (or _FILE) if set
  2. A 24-byte hex string generated at random and printed once to
     stdout and to the structured log

The generated-password path logs a bright one-time banner with the
username and password, intended to be captured from the console on the
very first run and then forgotten. No password is ever written to a
file on disk by this flow.

Wired from cmd/run via cli.go so that every run invocation checks the
seed state after migrations have been applied.
2026-04-23 16:40:25 -04:00
GraceSolutions b05858b40d feat(config): add configuration export/import API
Introduce ConfigService and /api/v1/config endpoints for system portability.

- Export: emits a versioned JSON document covering credentials (encrypted secrets preserved), connections, schedules, rules (with nested condition groups, conditions, and actions), and non-sensitive app settings.
- Import: validates the format version and upserts each entity by ID. Supports a dryRun mode that plans the import without writing, and emits per-entity warnings for secrets that need re-entry.
- Audit: Export and Import actions emit ConfigChange audit events with entity counts.
- Wiring: add ConfigService to server.Dependencies and instantiate it in cli.RunForeground.
2026-04-23 13:28:51 -04:00
GraceSolutions 409ae42bc7 feat(dashboard): add aggregated dashboard summary endpoint
New DashboardService aggregates entity counts (rules, connections, credentials, schedules, users, api keys), rolling rule-run statistics (last 24h and 7d), the 10 most recent rule runs, and per-connection health based on last_tested_utc/last_test_result. Exposed via GET /api/v1/dashboard/summary and wired through server.Dependencies + cli.RunForeground.
2026-04-23 13:22:04 -04:00
GraceSolutions 99a0589a2c feat(settings): add CRUD API for the app_settings key-value store
New SettingsService provides List/Get/Upsert/Delete over the app_settings table. SettingsHandler exposes GET /api/v1/settings, GET/PUT/DELETE /api/v1/settings/{key} and redacts sensitive values in responses. Changes are recorded as ConfigChange audit events. The service is wired into server.Dependencies and constructed in cli.RunForeground.
2026-04-23 13:19:18 -04:00
GraceSolutions 8cb09a7086 fix(cli): instantiate audit.Service and include it in server.Dependencies
Previously the audit service was never constructed, so every emitAudit call across the handlers was a silent no-op. Build an audit.Service in RunForeground, add it to the Dependencies struct, and log a ServiceStart event on startup so the audit trail is bootstrapped.
2026-04-23 13:16:25 -04:00
GraceSolutions 29a8a0b503 feat(backups): add database backup and restore endpoints
- New BackupsHandler exposing list, create, and restore over
  /api/v1/backups, backed by services.BackupService.
- Create records who triggered the backup (username from context or
  'api') and defaults the backup type to 'manual'.
- Restore resolves the {id} path parameter against the list of available
  backups so clients can reference backups by filename, or pass an
  explicit filePath in the body.
- Inject BackupService via server.Dependencies and initialize it in
  cli.RunForeground under <DataPath>/backups with retention 10.
2026-04-23 12:37:31 -04:00
GraceSolutions 76469eb401 feat(api-keys): add API key administration endpoints
- New APIKeysHandler exposing list, create, revoke, and delete over
  /api/v1/api-keys, backed by services.APIKeyService.
- Create returns the full plaintext key once; list/get responses only
  surface the prefix and metadata.
- When userId is omitted, the handler falls back to the authenticated
  principal pulled from context so callers can self-service tokens.
- Inject APIKeyService via server.Dependencies and initialize it in
  cli.RunForeground.
2026-04-23 12:36:06 -04:00
GraceSolutions 8b3cf48bba feat(users): add user administration CRUD endpoints
- Add UserRepository.List with pagination and non-deleted filtering.
- New UsersHandler with List/Get/Create/Update/Delete; Create hashes
  passwords via crypto.HashPassword and Update re-hashes on rotation.
- Responses omit password material; username conflict returns 409.
- Inject UserRepo via server.Dependencies and wire /api/v1/users routes.
2026-04-23 12:34:19 -04:00
GraceSolutions a4473ba77e feat(api): wire live auth and credentials handlers
- Extend server.Dependencies with AuthService and CredService, and
  initialize them in cli.RunForeground using the derived AES key.
- Replace the handleNotImplemented stubs on /auth and /credentials with
  the existing AuthHandler and CredentialsHandler routes.
- Guard /auth/me behind api.AuthMiddleware so it only resolves when a
  valid session token is presented.
2026-04-23 12:32:13 -04:00
GraceSolutions 1c50828b9c feat(schedules): add full CRUD endpoints for schedule resource
- New SchedulesHandler backed by the existing ScheduleRepository, with
  request/response DTOs matching the schedules model (kind, easy/cron,
  timezone mode).
- Register List/Get/Create/Update/Delete routes on /api/v1/schedules.
- Extend server.Dependencies with ScheduleRepo and wire it from
  cli.RunForeground.
2026-04-23 12:29:19 -04:00
GraceSolutions c117d6d546 feat(rules): complete CRUD endpoints for rules resource
- Extend RuleRepository with List/Update/SoftDelete/UpdateEnabled and
  upsert helpers for condition groups, conditions, and actions.
- Extend RuleService with Create/GetByID/List/Update/Delete/Enable/Disable.
- Add List/Get/Create/Update/Delete/Enable/Disable handlers on RulesHandler
  with request/response DTOs that decouple the API from storage models.
- Register the full CRUD route set on /api/v1/rules.
- Inject RuleService into server.Dependencies from cli.RunForeground.
2026-04-23 12:26:50 -04:00
GraceSolutions 2376daf91c feat(startup): wire runner, engine, and scheduler into foreground mode
Derives a 32-byte AES key from the configured secret via SHA-256, constructs
the ConnectionService, Runner, and Engine, and starts the Scheduler so
enabled rules fire automatically. Injects the shared services into the HTTP
server via a new Dependencies struct so API handlers can reuse them.
2026-04-23 12:14:50 -04:00
GraceSolutions 09a09d4a68 feat: Initial backend scaffold - Go service, database, CLI, API structure
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
2026-04-19 10:07:21 -04:00