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 whoseuSNChangedis 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#
-
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. -
Create the AD service account with read access to the users and groups OUs you'll sync.
-
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:latestSeven variables also accept a
<NAME>_FILEvariant 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, andLDAP_BIND_PASSWORD- which is the preferred way to keep secrets out ofdocker 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:10001it first - the container runs as UID 10001. -
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#
| Variable | Required | Default | Notes |
|---|---|---|---|
SWARMFILE_HUB_URL | Yes | - | 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_ID | Yes | - | UUID of the org this agent syncs into; _FILE variant supported |
SWARMFILE_SYNC_TOKEN | Yes | - | Bearer token from Settings → Directory Sync (swarmfile_scim_…); _FILE variant supported |
LDAP_URL | Yes | - | ldap://host:389 or ldaps://host:636; _FILE variant supported |
LDAP_BIND_DN | Yes | - | Service-account DN; _FILE variant supported |
LDAP_BIND_PASSWORD | Yes | - | Service-account password; _FILE variant supported |
LDAP_USERS_BASE_DN | Yes | - | Search base for users; _FILE variant supported |
LDAP_GROUPS_BASE_DN | No | users base | Search base for groups |
LDAP_USER_FILTER | No | (&(objectClass=user)(!(objectClass=computer))) | LDAP filter for users |
LDAP_GROUP_FILTER | No | (objectClass=group) | LDAP filter for groups |
SYNC_INTERVAL_SECONDS | No | 300 | Delta cadence |
SYNC_FULL_INTERVAL_SECONDS | No | 86400 | Full cadence (governs deletion detection) |
SYNC_BATCH_SIZE | No | 100 | Ops per push request (server caps at 500) |
SYNC_CURSOR_PATH | No | /var/lib/swarmfile-sync/cursor.json | Persist this across restarts |
LDAP_TLS_REJECT_UNAUTHORIZED | No | true | Only false for self-signed labs |
LDAP_TIMEOUT_MS | No | 30000 | Connect/search timeout |
LOG_LEVEL | No | info | debug/info/warn/error |
How AD attributes map#
| AD attribute | Swarmfile field | Notes |
|---|---|---|
objectGUID | externalId | Canonical mixed-endian GUID, matching PowerShell |
objectSid | adSid | Used for Windows DACL projection |
displayName → cn → sAMAccountName | Display name | First non-empty wins |
mail | Optional | |
userPrincipalName → sAMAccountName | UPN | First non-empty wins |
userAccountControl bit 2 | Active (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#
| Symptom | Likely cause |
|---|---|
config: required env var X is missing | A required variable isn't set; the agent fails before connecting |
bind failed | Wrong bind DN/password, or wrong LDAP_URL; test with ldapsearch -x -H ldap://host -D '<bind dn>' -W -b '<base>' |
/sync/push returned 401 | Token wrong, revoked, or minted for another org - mint a fresh one |
/sync/push returned 503: tenant provisioning in progress | The org's tenant is still being provisioned; the agent retries next cycle |
| Users disappeared | The last full sync marked them disabled - check which OU the service account can actually read |
| Deletions lag | Lower 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
Readon 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.