gsadmin 901692ead7
Release / release (push) Successful in 10m12s
Release / msi (push) Successful in 1m28s
Merge pull request 'Release: backup restore, portable secret key, multi-arch image, CSRF, About dialog' (#14) from development into main
Merge development into main (release)
2026-09-04 00:56:16 +00:00
2026-04-15 14:23:32 -04:00
2026-04-15 14:23:32 -04:00

OrchestrAD

OrchestrAD is a modern, rule-based automation platform for Active Directory. It enables dynamic group membership, object lifecycle orchestration, and policy-driven directory operations through a clean UI, REST API, and powerful scheduling engine.


🚀 Overview

OrchestrAD replaces static scripts and JSON-based tooling with a centralized, database-backed system for managing directory automation.

It allows administrators to define rules that evaluate Users, Computers, and Groups, then automatically perform actions such as:

  • Adding/removing group memberships
  • Moving objects to Organizational Units (OUs)
  • Creating groups dynamically
  • Nesting groups
  • Enforcing directory structure and policy consistency

All configuration is managed through the UI or API — no manual file editing required.


📸 Screenshots

Dashboard Rules
Dashboard Rules
Credentials Sign in
Credentials Sign in

The entire UI is served by the single backend binary (embedded Next.js static export) — no separate web server to run.


Key Features

🔁 Rule-Based Automation

  • Visual rule builder with condition groups and logical operators
  • Supports multiple object types (Users, Computers, Groups)
  • Rich condition support (equals, regex, LDAP, comparisons, etc.)
  • Multiple actions per rule with ordered execution

🧠 Dynamic Directory Orchestration

  • Add/remove group membership with diff-based execution
  • Move objects to OUs with dynamic path generation
  • Auto-create groups and OUs when missing
  • Support for nested group relationships

⏱️ Scheduling Engine

  • Easy schedules (e.g., every 5 minutes)
  • Advanced 6-field cron expressions
  • Per-rule execution control and concurrency handling

🔐 Secure Authentication & Access

  • Local authentication (Argon2-secured)
  • OIDC support (via NextAuth/Auth.js)
  • API key system (separate from user auth)
  • Role-based access control (RBAC)

🔑 Credential Management

  • Reusable, encrypted credential objects
  • Secure storage using AEAD encryption
  • Credential testing and validation

📊 Observability & Auditing

  • Centralized logging with rotation and retention
  • Human-readable logs with structured context
  • Full audit trail for all actions and changes
  • Rule execution history and summaries

💾 Backup & Restore

  • Automatic database backups with retention
  • Manual backup and restore support
  • Safe restore with validation and rollback protection

📦 Cross-Platform Runtime

  • Single Go binary

  • Runs as:

    • foreground process
    • system service
    • Docker container
  • Supports Windows, macOS, and Linux (amd64 + arm64)


🧱 Architecture

  • Backend: Go (Chi, sqlc, SQLite, robfig/cron)
  • Frontend: Next.js + Material UI + Tailwind (Spike Template)
  • Database: SQLite (WAL mode, migrations enabled)
  • Auth: NextAuth/Auth.js (UI sessions) + backend RBAC/API keys
  • Directory Integration: LDAP/LDAPS via go-ldap

🔐 Authentication Model

OrchestrAD separates authentication concerns:

  • Browser Sessions

    • Managed via NextAuth/Auth.js
    • Supports OIDC and local login
  • API Access

    • Managed via API keys
    • Keys can expire or persist indefinitely
    • Shown only once at creation
  • Backend Authorization

    • All security decisions enforced server-side
    • RBAC controls access to resources and actions

🔌 API & Automation

Everything the UI does is available over the REST API, so rules can be created and run programmatically.

  • Interactive docs (Swagger UI): https://<host>:18090/api/docs
  • OpenAPI 3 spec: https://<host>:18090/api/openapi.json
  • Compact route list: https://<host>:18090/api/routes

The spec is generated from the live router, so it always reflects the endpoints the running build actually serves. Authenticate at POST /api/v1/auth/login, then send the returned token as Authorization: Bearer <token>.

Access to the docs

The documentation endpoints are not public — they require the same authentication as the rest of the API:

  • From the dashboard: sign in, then use the </> icon in the header or Administration → API Docs in the menu. Login sets a session cookie scoped to /api/docs, so the Swagger UI opens straight away in a new tab. Opening /api/docs while signed out redirects to the login page and returns you there afterwards.
  • From a client: send Authorization: Bearer <token> or X-API-Key: <key> to /api/openapi.json or /api/routes.

The docs cookie is HttpOnly and path-scoped to /api/docs, so it is never sent to /api/v1/* and cannot be used to make API calls. Swagger's Try it out still needs a token or API key.

Discovering routes

Both /api/routes and /api/openapi.json accept the same filters, so a client can ask for just the part of the API it cares about:

Query Meaning
?method=post only POST operations
?method=get,post GET or POST
?path=rules paths containing rules
?method=post&path=connections POST operations on connection routes

/api/routes returns a flat list — method, path, summary, tag, whether the route is public, and allowed, which is false when a read-scoped API key cannot invoke it:

$headers = New-Object 'System.Collections.Generic.Dictionary[String,String]'
$headers.Add('X-API-Key', $apiKey)

$routes = Invoke-RestMethod -Method 'Get' `
    -Uri 'https://localhost:18090/api/routes?method=post&path=rules' `
    -Headers $headers -SkipCertificateCheck

$routes.data.routes | Format-Table -Property 'method', 'path', 'allowed', 'summary'

The same filters work on the Swagger UI itself (/api/docs?path=rules), which loads a spec narrowed to those operations.

Create a rule from PowerShell

A ready-to-run example lives at docs/examples/Create-OrchestrADRule.ps1. It builds the headers and body as strongly-typed dictionaries, serializes the body with ConvertTo-Json, and uses full cmdlet names (no aliases):

# Authenticate
$loginBody = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
$loginBody.Add('username', 'admin')
$loginBody.Add('password', 'admin')
$login = Invoke-RestMethod -Method 'Post' -Uri 'https://localhost:18090/api/v1/auth/login' `
    -ContentType 'application/json' -Body ($loginBody | ConvertTo-Json) -SkipCertificateCheck

$headers = New-Object 'System.Collections.Generic.Dictionary[String,String]'
$headers.Add('Authorization', "Bearer $($login.data.token)")

# Build the rule: users where department = Sales -> synced into a target group
$condition = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
$condition.Add('attributeName', 'department'); $condition.Add('operator', 'Equals'); $condition.Add('comparisonValue', 'Sales')
$conditions = New-Object 'System.Collections.Generic.List[Object]'; $conditions.Add($condition)
$group = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
$group.Add('joinOperator', 'AND'); $group.Add('conditions', $conditions)
$conditionGroups = New-Object 'System.Collections.Generic.List[Object]'; $conditionGroups.Add($group)

$actionConfig = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
$actionConfig.Add('targetGroupDn', 'CN=Sales,OU=Groups,DC=corp,DC=com')
$actionConfig.Add('syncMode', 'FullSync'); $actionConfig.Add('createIfMissing', $true)
$action = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
$action.Add('actionType', 'SyncGroupMembership'); $action.Add('configurationJson', ($actionConfig | ConvertTo-Json -Compress))
$actions = New-Object 'System.Collections.Generic.List[Object]'; $actions.Add($action)

$ruleBody = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
$ruleBody.Add('name', 'Sales Team'); $ruleBody.Add('adConnectionId', '<connection-id>')
$ruleBody.Add('objectType', 'User'); $ruleBody.Add('executionMode', 'Apply'); $ruleBody.Add('groupJoinOperator', 'AND')
$ruleBody.Add('conditionGroups', $conditionGroups); $ruleBody.Add('actions', $actions)

Invoke-RestMethod -Method 'Post' -Uri 'https://localhost:18090/api/v1/rules' `
    -Headers $headers -ContentType 'application/json' -Body ($ruleBody | ConvertTo-Json -Depth 10) -SkipCertificateCheck

Tip: GET /api/v1/rules/metadata returns the valid object types, operators, action types, and sync modes for building rule bodies.


🧾 Logging & Retention

Standard log format:

[TimestampUTC] - [Component] - [Level] - Message

Log rotation is automatic (max size, backups, and age; compressed):

Setting Env var Default
Max file size (MB) ORCHESTRAD_LOG_MAX_SIZE_MB 5
Rotated copies kept ORCHESTRAD_LOG_MAX_BACKUPS 3
Max age (days) ORCHESTRAD_LOG_MAX_AGE_DAYS 30

Database maintenance keeps history from growing forever — old rule runs and audit events are pruned and the database is compacted (VACUUM) on a schedule:

Setting Env var Default
Rule-run history retained (days) ORCHESTRAD_RUN_RETENTION_DAYS 90
Audit history retained (days) ORCHESTRAD_AUDIT_RETENTION_DAYS 180
Maintenance interval (hours) ORCHESTRAD_MAINTENANCE_INTERVAL_HOURS 24
Run VACUUM ORCHESTRAD_MAINTENANCE_VACUUM true

Error logs:

[TimestampUTC] - [Component] - [Level] - [File:Line:Column] - Message

Logs are designed to be:

  • Clear and human-readable
  • Operationally useful
  • Free of unnecessary noise

⚙️ Configuration

Configuration is managed via:

  • Environment variables
  • Secure secrets
  • Database-backed runtime configuration

Supports:

  • Export and import of configuration (JSON)
  • Schema versioning
  • Credential portability with key validation

📥 Installation

Windows (MSI)

Download OrchestrAD-<version>-x64.msi from the latest release and run it. The installer:

  • Installs to C:\Program Files\OrchestrAD
  • Registers and starts the OrchestrAD Windows service (auto-start)
  • Records the install directory and version under HKLM\Software\Grace Solutions\OrchestrAD
  • On upgrade, updates only the binaries — the database (…\OrchestrAD\data) is preserved
  • On uninstall, stops and removes the service and deletes the program files (the data directory is left in place)

Standalone binary (Windows / macOS / Linux)

Download the archive for your platform from the release (…-<os>-<arch>.tar.gz / .zip), extract the single orchestrad binary, and run it. See Running & Service Management below.

Docker

docker pull prod.git.gracesolution.info/gsadmin/orchestrad:latest
docker run -d --name orchestrad -p 18090:18090 \
  -e ORCHESTRAD_SECRET_KEY=<32+ byte secret> \
  -v orchestrad-data:/data \
  prod.git.gracesolution.info/gsadmin/orchestrad:latest

Or use the bundled docker-compose.yml. Images are tagged latest and the unified commit date yyyy.MM.dd.HHmm.


▶️ Running & Service Management

The single binary is both the app and its own service manager (Windows service, systemd/upstart/sysv, or launchd).

orchestrad <command>

  run           Run in the foreground (also the service entry point; used by Docker)
  initialize    Install and start the service (idempotent)
  remove        Stop and remove the service (idempotent)
  install       Alias for initialize
  uninstall     Alias for remove
  start         Start the installed service
  stop          Stop the installed service
  init          Initialize the database and apply migrations
  migrate       Apply database migrations
  backup        Create a manual backup
  restore --file <path>   Restore the database from a backup
  doctor        Validate configuration and system health
  version       Show version information

initialize and remove are idempotent, so they are safe to re-run (and are what the MSI uses under the hood). On first start the default admin account is admin / admin, and a password change is forced at first login.

Key environment variables (see the CLI help for the full list):

Variable Default Purpose
ORCHESTRAD_DATA_PATH ./data (next to the binary for the service) Database, logs, backups
ORCHESTRAD_SECRET_KEY insecure dev key AEAD key for credential encryption (set in production)
ORCHESTRAD_SECRET_KEY_FILE Read the secret key from a file instead (for Docker/K8s secrets)
ORCHESTRAD_HOST / ORCHESTRAD_PORT 0.0.0.0 / 18090 HTTP bind address
ORCHESTRAD_LOG_LEVEL info debug / info / warn / error

⚠️ The secret key must stay the same for the life of the installation. Stored credential passwords are encrypted with it, so a changed or lost key leaves them intact but unreadable, and every directory bind fails. Back it up somewhere durable.

On startup OrchestrAD verifies that the stored credentials decrypt with the current key and logs a clear error naming the affected credentials if they do not; orchestrad doctor runs the same check on demand. If the original key is genuinely gone, re-enter the affected passwords to re-encrypt them under the new key.


📦 Build & Versioning

  • Version format: yyyy.MM.dd.HHmm (derived from the HEAD commit date in CI)
  • Pure Go — SQLite via modernc.org/sqlite, so CGO_ENABLED=0 and every target cross-compiles from one machine with no C toolchain
  • Multi-platform: Windows, macOS, Linux (amd64 + arm64)
  • Windows binaries embed the application icon and version metadata from /resources/icons (via goversioninfo)

Local build (Windows host, builds the frontend and stages it into the binary):

./scripts/build.ps1 -All        # or -Windows / -MacOS / -Linux

Output lands under /binaries/<os>/<arch>/.


🐳 Docker

OrchestrAD ships as a single small (~60 MB) image built by a multi-stage Dockerfile that compiles the Next.js UI, embeds it, and builds the static Go binary:

  • Runs in foreground mode by default; graceful shutdown on SIGTERM
  • /data holds the database, logs, and backups (bind-mount or named volume)
  • HEALTHCHECK probes /health (answers both GET and HEAD)

🚀 CI/CD

A single Gitea Actions workflow (.gitea/workflows/release.yml) runs only on merge to main (docs-only merges are skipped — no per-commit or per-PR CI):

  1. release job (ubuntu-host): runs the Go test suite as a gate, builds the frontend, cross-compiles all six binaries, builds and pushes the container image (latest + version), and creates the Gitea release with the binaries + checksums attached.
  2. msi job (windows-host): wraps the Windows binary into the MSI with WiX and attaches it to the release.

Required repository secret

Secret Purpose
REGISTRY_PASSWORD A Gitea personal access token with write:package scope, used to push images to the container registry (with REGISTRY_HOST + REGISTRY_USERNAME).

REGISTRY_HOST / REGISTRY_USERNAME point at the container registry (the Gitea instance itself by default). The release itself is created with the auto-injected Actions token, so no other secrets are needed.


📌 Use Cases

  • Dynamic group membership automation
  • Directory cleanup and normalization
  • Organizational policy enforcement
  • Zero-touch user and device placement
  • Identity lifecycle orchestration

S
Description
OrchestrAD is a rule-driven Active Directory automation platform that enables dynamic group management, object orchestration, and policy-based directory operations through a modern UI, API, and scheduling engine.
Readme GPL-3.0 101 MiB
2026-09-04 01:06:19 +00:00
Languages
Go 57.6%
TypeScript 40.1%
MDX 1.4%
PowerShell 0.6%
Dockerfile 0.3%