Browse docs
Docs / Admin & IT / LDAP Directory Sync

LDAP Directory Sync

If your users live in on-premises Active Directory, this is how their accounts and groups reach Swarmfile. It is AD-specific - the agent reads attributes like objectGUID, objectSid, sAMAccountName, and uSNChanged, and generic RFC-2307 directories (entryUUID/uid) aren't supported today. Swarmfile's control plane can't open raw LDAP connections, so a small customer-hosted agent runs inside your network, reads AD, and pushes normalized changes to Swarmfile over HTTPS. Nothing needs inbound access from Swarmfile to your directory.

If your identity provider is cloud-based (Entra ID, Okta), you don't need this agent - those push SCIM directly to Swarmfile. See Identity for SSO and SCIM setup.

What you need#

  • A Pro plan or above (directory sync is a Pro entitlement).
  • An org owner to mint the sync token under Settings → Directory Sync.
  • A read-only service account in AD with permission to read users and groups. The agent never writes to your directory.
  • A host inside your network to run the agent - Docker is the supported path - with line-of-sight to a domain controller and outbound HTTPS to your hub.

How it behaves#

  • Delta sync every 5 minutes by default (SYNC_INTERVAL_SECONDS): scans only entries whose uSNChanged is newer than the persisted cursor. Cheap, catches user joiner/mover/leaver churn. Group membership rides along with the full sync, not the delta.
  • Full sync daily by default (SYNC_FULL_INTERVAL_SECONDS): rescans users and groups. This is how deletions propagate - delta scans can't see a deleted object, so any principal missing from a full scan is disabled (user_disable / group_disable).
  • Crash-safe: the cursor is written atomically and a partial batch leaves it unchanged, so the next cycle replays the same window; ingest is idempotent on (source, externalId), so replays don't duplicate anyone.
  • Disables, not deletes: an account that disappears from AD is disabled in Swarmfile, preserving its history and audit trail.
  • Server limits: the hub accepts at most 500 operations per push (batches are clamped to 1-500; default 100) and 1,000 group members per group. The sync token carries its own request-rate ceiling.

Set it up#

  1. Mint the sync token. As an owner: Settings → Directory Sync → New sync token. Copy the plaintext (swarmfile_scim_...) - it is shown once, and it authorizes principal changes for the org, so treat it like an API key.

  2. Create the AD service account with read access to the users and groups OUs you'll sync.

  3. Run the agent. Production-safe Docker Swarm example with secrets:

    printf 'changeme' | docker secret create ldap_bind_password -
    printf 'swarmfile_scim_xxxxxxxx' | docker secret create swarmfile_sync_token -
    
    docker service create --name swarmfile-sync \
      --secret ldap_bind_password \
      --secret swarmfile_sync_token \
      --mount type=volume,source=swarmfile-sync-cursor,target=/var/lib/swarmfile-sync \
      -e SWARMFILE_HUB_URL=https://hub.swarmfile.com \
      -e SWARMFILE_ORG_ID=<your-org-uuid> \
      -e SWARMFILE_SYNC_TOKEN_FILE=/run/secrets/swarmfile_sync_token \
      -e LDAP_URL=ldaps://dc01.acme.corp:636 \
      -e LDAP_BIND_DN='CN=svc_swarmfile,OU=ServiceAccounts,DC=acme,DC=corp' \
      -e LDAP_BIND_PASSWORD_FILE=/run/secrets/ldap_bind_password \
      -e LDAP_USERS_BASE_DN='OU=Users,DC=acme,DC=corp' \
      -e LDAP_GROUPS_BASE_DN='OU=Groups,DC=acme,DC=corp' \
      swarmfile/sync-agent:latest
    

    Seven variables also accept a <NAME>_FILE variant pointing at a file with the value - SWARMFILE_HUB_URL, SWARMFILE_ORG_ID, SWARMFILE_SYNC_TOKEN, LDAP_URL, LDAP_USERS_BASE_DN, LDAP_BIND_DN, and LDAP_BIND_PASSWORD - which is the preferred way to keep secrets out of docker inspect. The rest (group base DN, filters, cadences, batch size, TLS/timeout, log level) are read from the environment only. If you bind-mount a host directory for the cursor instead of a named volume, chown 10001:10001 it first - the container runs as UID 10001.

  4. Confirm in the dashboard. Settings → Directory Sync shows the recent sync log; the agent's own logs print a summary per cycle (users=… groups=… disables=… pushed=… failures=…).

Configuration reference#

VariableRequiredDefaultNotes
SWARMFILE_HUB_URLYes-Hub base URL, no trailing slash (production https://hub.swarmfile.com; a staging org uses https://test-hub.swarmfile.com); _FILE variant supported
SWARMFILE_ORG_IDYes-UUID of the org this agent syncs into; _FILE variant supported
SWARMFILE_SYNC_TOKENYes-Bearer token from Settings → Directory Sync (swarmfile_scim_…); _FILE variant supported
LDAP_URLYes-ldap://host:389 or ldaps://host:636; _FILE variant supported
LDAP_BIND_DNYes-Service-account DN; _FILE variant supported
LDAP_BIND_PASSWORDYes-Service-account password; _FILE variant supported
LDAP_USERS_BASE_DNYes-Search base for users; _FILE variant supported
LDAP_GROUPS_BASE_DNNousers baseSearch base for groups
LDAP_USER_FILTERNo(&(objectClass=user)(!(objectClass=computer)))LDAP filter for users
LDAP_GROUP_FILTERNo(objectClass=group)LDAP filter for groups
SYNC_INTERVAL_SECONDSNo300Delta cadence
SYNC_FULL_INTERVAL_SECONDSNo86400Full cadence (governs deletion detection)
SYNC_BATCH_SIZENo100Ops per push request (server caps at 500)
SYNC_CURSOR_PATHNo/var/lib/swarmfile-sync/cursor.jsonPersist this across restarts
LDAP_TLS_REJECT_UNAUTHORIZEDNotrueOnly false for self-signed labs
LDAP_TIMEOUT_MSNo30000Connect/search timeout
LOG_LEVELNoinfodebug/info/warn/error

How AD attributes map#

AD attributeSwarmfile fieldNotes
objectGUIDexternalIdCanonical mixed-endian GUID, matching PowerShell
objectSidadSidUsed for Windows DACL projection
displayName → cn → sAMAccountNameDisplay nameFirst non-empty wins
mailEmailOptional
userPrincipalName → sAMAccountNameUPNFirst non-empty wins
userAccountControl bit 2Active (negated)The ACCOUNTDISABLE flag

For OIDC SSO, the agent pre-links each user's objectGUID as the JWT sub - the default behavior of AD FS and Entra Connect. If your IdP uses a different sub claim, rebind users manually in the dashboard.

Troubleshooting#

SymptomLikely cause
config: required env var X is missingA required variable isn't set; the agent fails before connecting
bind failedWrong bind DN/password, or wrong LDAP_URL; test with ldapsearch -x -H ldap://host -D '<bind dn>' -W -b '<base>'
/sync/push returned 401Token wrong, revoked, or minted for another org - mint a fresh one
/sync/push returned 503: tenant provisioning in progressThe org's tenant is still being provisioned; the agent retries next cycle
Users disappearedThe last full sync marked them disabled - check which OU the service account can actually read
Deletions lagLower SYNC_FULL_INTERVAL_SECONDS; note full scans cost O(directory size) per run

Security#

  • The sync token authorizes any principal operation on the org. Store it in a secret manager, never in a committed env file.
  • The agent reads AD only; give the service account Read on the OUs it needs and nothing more.
  • Use ldaps:// in production - ldap:// sends bind credentials in the clear. Certificate validation is standard (no skip option beyond the lab flag); there is no custom CA or client-certificate setting today, so your domain controllers need certificates the agent trusts.
  • The cursor file holds GUIDs, no secrets; normal filesystem permissions are enough.

Where to go next#

  • Identity - SSO, SCIM, and how synced principals join the org.
  • Permissions - grants and protected mode for the synced groups.
  • Security - the broader admin security model.