Skip to main content

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​

ModeWhat it does
ADDrains 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).
DBBi-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/operations claims a batch (auth header X-Agent-Token), the agent executes each against AD, and POST /agent/ad/operations/{id}/complete reports 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:VAR or enc:<…> (AES-256-GCM, vault key NEXUS_AGENT_KEY); never required in plaintext. Broker-issued passwords are decrypted with the shared SECRET_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-log re-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:

  1. The Windows account the agent service runs as — its logon identity (LocalSystem, a domain user, or a gMSA).
  2. 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):

ModeConfigIdentity used against AD
Simple bind (default build)use_kerberos = false, bind_dn + bind_passwordA 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 passwordThe 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 operationRequired right on the OU
Find users (search by mail / UPN)Read user properties
CREATE_ACCOUNTCreate User objects
Password writeback (unicodePwd, over LDAPS)Reset Password (extended right)
Enable / disableWrite userAccountControl
Attribute updatesWrite 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 for unicodePwd writeback).

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.exe or 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.1 is 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.
  • :latest drifts — two hosts pulling :latest a 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).