Sync Agent — hybrid AD & DB connector
The NexusID Sync Agent is a small, self-hosted service (Windows / Linux / macOS, written in Rust) that connects egress↔ingress to the broker and synchronizes your on-prem systems — like Azure AD Connect, but provider-agnostic. It runs where your directory or database lives and makes only outbound connections, so nothing dials into your network.
Two modes
| Mode | What it does |
|---|---|
| AD | Drains the broker's provisioning queue into Active Directory over LDAP: create (disabled), enable (start date), disable (leaver), attribute update, and password writeback (unicodePwd over LDAPS). Binds with an AD service account (simple bind, or Kerberos/GSSAPI). |
| DB | Bi-directional field sync between a database table and the broker. Outbound pushes mapped rows to the ingest webhook (driving Joiner/Mover/Leaver). Writeback applies server-side changes back to the DB. PostgreSQL / MySQL / SQLite. |
How it talks to the broker
- AD mode uses the agent contract:
GET /agent/ad/operationsclaims a batch (auth headerX-Agent-Token), the agent executes each against AD, andPOST /agent/ad/operations/{id}/completereports the result. Pair this with a Directory Integration that has Provision from HR sources enabled (see DataSheets → Provisioning to AD). - DB mode posts mapped rows to an ingest webhook (e.g.
/api/hr-sync/db-agent/webhook) and can poll a change-feed for writeback.
Security
- No inbound exposure — outbound-only to the broker and to your directory/DB.
- Encrypted credentials — the AD service-account password and DB connection string use
env:VARorenc:<…>(AES-256-GCM, vault keyNEXUS_AGENT_KEY); never required in plaintext. Broker-issued passwords are decrypted with the sharedSECRET_ENCRYPTION_KEY. - Tamper-evident log — every action is appended to a local hash-chained event log
(
hash = SHA256(seq ‖ ts ‖ event ‖ detail ‖ prev_hash)).nexus-agent verify-logre-walks the chain and prints the record count and any break, so on-box run history can't be silently edited.
Active Directory account & permissions
On a domain-joined Windows host there are two distinct identities:
- The Windows account the agent service runs as — its logon identity (LocalSystem, a domain user, or a gMSA).
- The AD account the agent binds to the directory with — the identity that actually performs the create / enable / disable / password-write operations.
How they relate depends on the bind mode (set in [ad] of config.toml):
| Mode | Config | Identity used against AD |
|---|---|---|
| Simple bind (default build) | use_kerberos = false, bind_dn + bind_password | A dedicated AD service account (a normal domain user, e.g. CN=svc-nexus,OU=Service Accounts,…). The service-logon account is irrelevant to AD; all writes happen as the bind account. The password is stored encrypted (enc: / env:), never plaintext. |
Kerberos / integrated (--features kerberos build) | use_kerberos = true, no password | The Kerberos identity the service runs as — ideally a gMSA (group Managed Service Account), so AD auto-manages and rotates the password and nothing is stored. |
This is not an Azure-style "service principal." In AD the equivalent is a service account (domain user) or a gMSA. No SPN is required for the agent — LDAPS needs none, and Kerberos mode only needs the run-as account to have a valid ticket.
Least-privilege delegation (not Domain Admin)
Grant the binding account only the rights it needs, scoped to the OU(s) it manages — never at the domain root:
| Agent operation | Required right on the OU |
|---|---|
| Find users (search by mail / UPN) | Read user properties |
CREATE_ACCOUNT | Create User objects |
Password writeback (unicodePwd, over LDAPS) | Reset Password (extended right) |
| Enable / disable | Write userAccountControl |
| Attribute updates | Write the mapped attributes (givenName, sn, displayName, mail, title, department, telephoneNumber, …) |
Quickest setup: in Active Directory Users & Computers, right-click the target OU →
Delegate Control → grant "Create, delete, and manage user accounts" and "Reset user passwords
and force password change at next logon" (or use dsacls for exact attribute rights).
Recommendation (domain-joined)
- Best: build with
--features kerberos, run the Windows service under a gMSA, and delegate the OU rights above to that gMSA — integrated auth, no stored password, automatic rotation. - Simpler (stock binary): a dedicated domain user service account with a strong password
(
enc:/env:), the same OU delegation, and LDAPS on 636 (mandatory forunicodePwdwriteback).
Either way, two non-domain credentials remain separate: the broker X-Agent-Token (AD_AGENT_TOKEN)
and the SECRET_ENCRYPTION_KEY used to decrypt broker-issued initial passwords.
Quick start
Prebuilt binaries for Linux (glibc + static musl), Windows, and macOS (Apple Silicon + Intel) are attached to each GitHub release — verify against the
.sha256. Or build from source:
# 1. Build (portable; add --features kerberos for GSSAPI)
cargo build --release # -> target/release/nexus-agent
# 2. Keys & secrets
nexus-agent gen-key # NEXUS_AGENT_KEY (vault) and the broker key
NEXUS_AGENT_KEY=… nexus-agent encrypt-secret --value 'svc-account-pw' # -> enc:… (paste into config)
# 3. Configure (see config.example.toml) and run
nexus-agent run --config /etc/nexus-agent/config.toml
nexus-agent status --config /etc/nexus-agent/config.toml # mode + audit integrity + counts
Run as a service
- Linux:
dist/nexus-agent.service(systemd) + secrets in/etc/nexus-agent/agent.env. - macOS:
dist/com.nexusid.agent.plist(launchd). - Windows: register with
sc.exeor NSSM; the binary handles graceful stop.
Run in Docker
A multi-arch image (linux/amd64 + linux/arm64) is published to GHCR on each release. Replace
{version} with the release you want (e.g. 0.1.1 — see the releases):
docker pull ghcr.io/adroitts/nexusid-agent:{version}
docker run -d --name nexus-agent --restart unless-stopped \
-v "$PWD/config.toml:/etc/nexus-agent/config.toml:ro" \
-v nexus-agent-data:/var/lib/nexus-agent \
-e NEXUS_AGENT_KEY -e AD_AGENT_TOKEN -e SECRET_ENCRYPTION_KEY \
ghcr.io/adroitts/nexusid-agent:{version}
Why pin {version} instead of :latest? A sync agent writes to your directory, so the running
build should be deliberate and reproducible:
- Reproducible & auditable —
:0.1.1is immutable; everyone (and every host) runs the exact same code, and your change log shows precisely when the agent version changed. - Controlled rollout — upgrade by bumping the pinned tag (and rolling back is just re-pinning the
previous one), rather than silently picking up a new build on the next
docker pull/restart. :latestdrifts — two hosts pulling:latesta week apart can run different code; fine for a quick local try, risky for something with write access to AD.
Mount your config.toml read-only at /etc/nexus-agent/config.toml, point audit_log at
/var/lib/nexus-agent/audit.jsonl (a named volume) so the hash-chained log persists, and supply
env:/enc: secrets via -e (the image runs as a non-root user).
Configuration sketch
[agent]
id = "nexus-agent-dc01"
mode = "ad" # or "db"
poll_interval_secs = 30
audit_log = "/var/lib/nexus-agent/audit.jsonl"
[server]
base_url = "https://idp.example.com"
agent_token = "env:NEXUS_AGENT_TOKEN"
secret_key = "env:SECRET_ENCRYPTION_KEY"
[ad]
url = "ldaps://dc01.corp.example.com:636"
bind_dn = "CN=svc-nexus,OU=Service Accounts,DC=corp,DC=example,DC=com"
bind_password = "enc:AES-256-GCM|local|…"
base_dn = "DC=corp,DC=example,DC=com"
password_writeback = true
Seeing agents in the console
Once an agent is running, Admin → Sync Agents lists every connected agent with a live status dot
(online / offline / error), its mode (AD/DB), host, version, and the last connected,
last heartbeat, and last terminated timestamps. Click View on a row for the per-agent
connection log — CONNECTED / TERMINATED / DISCONNECTED (stale) events, newest first.
How it works: the agent posts POST /agent/heartbeat each cycle (authenticated with X-Agent-Token),
which flips it ONLINE and stamps lastConnectedAt on a fresh connect; on graceful shutdown it posts
POST /agent/disconnect (logged as TERMINATED). If heartbeats stop, a broker sweep
(agent.stale-after-secs, default 180s) marks the agent OFFLINE and logs a stale DISCONNECTED. An
agent that names a Directory Integration also lights up the Online Agents tile on that page.
Source
The agent source is published at
github.com/adroitts/nexusid-agent — see its
README.md for the full build/feature matrix (default, --features kerberos, --no-default-features).