Technical Whitepaper — Version 0.1.0 — May 2026
Author: Heitor Faria · Website: https://podheitor.com · Email: heitor@opentechs.lat · Phone / WhatsApp: +1 786 726-1749 | +55 61 98268-4220
Special offer. Bring your renewal proposal for any commercial enterprise backup platform — Veeam, Commvault, NetBackup, or others. We will benchmark a head-to-head proposal targeting at least 50% savings with stronger AD/LDAP-specific functionality. Contact heitor@opentechs.lat for a written quote.
Table of contents
- Executive summary
- Introduction & market context
- Architecture overview
- Backup modes deep dive
- Restore modes deep dive
- Feature matrix
- Installation guide
- Configuration reference
- FileSet examples
- Sizing & capacity planning
- Performance targets
- Compatibility matrix
- Security
- Monitoring
- Troubleshooting guide
- Use cases & deployment scenarios
- Comparison with other approaches
- Roadmap
- Contact information
- Legal / copyright
1. Executive summary
Identity infrastructure — Microsoft Active Directory, OpenLDAP, 389-DS, and Samba AD DC — is the single most critical component in any enterprise IT environment. Every user account, every group membership, every Group Policy Object (GPO), every access control list, and every Kerberos principal lives in the directory. When identity data is lost or corrupted, the entire organisation grinds to a halt: logins fail, applications cannot authenticate, and privileged access to other backup-protected systems becomes impossible.
Despite this criticality, most organisations lack a purpose-built backup for their directory services. Common approaches range from periodic ldif exports via ldapsearch scripts to relying on the Windows system state backup — none of which integrates cleanly with an open-source backup platform, none of which provides true incremental capability, and none of which covers the complete identity data surface including SYSVOL, GPO, schema, and ACLs.
The PodHeitor Active Directory / LDAP Backup Plugin for Bacula closes this gap. It is a pure-Rust cdylib metaplugin loaded directly by bacula-fd, providing clientless, network-only backup and restore of directory services: Microsoft Active Directory (Windows Server 2012 R2 through 2025 and Samba AD DC), OpenLDAP 2.4/2.5/2.6, 389 Directory Server / Red Hat DS / FreeIPA, and any LDAPv3-compliant server in generic mode.
The plugin ships five backup modes covering every operational scenario — from logical ldif-equivalent snapshots (ldap mode) to incremental AD DirSync streaming (ad mode), continuous data protection (cdp), live directory-to-directory replication (replicate), and integrated SYSVOL/GPO backup (hybrid_sysvol). Six restore modes provide precision from single-attribute correction through full authoritative forest recovery. All are managed through standard Bacula Job and FileSet configuration with no sidecar daemons, no shell-script glue, and no external state files.
The plugin is built with Brazilian Unicode hardening baked in from day one — a practical requirement for the Latin American market where directory entries regularly contain cedilha, til, and other accented characters that break lesser implementations. 734 unit tests, 45 live-LDAP integration tests against both 389-DS and Samba AD, and a destructive mid-sweep failover test suite confirm production-grade reliability.
2. Introduction & market context
2.1 Why directory backup is uniquely critical
Unlike a database or file system, a directory service is not self-contained. It is the authentication and authorization backbone for every other system in the environment. A directory failure cascades immediately: email stops delivering, VPN connections fail, cloud single-sign-on breaks, ERP systems refuse logins, and the operator cannot authenticate to the backup system itself. Recovery from a directory loss without a proper backup is measured in days, not hours — and may require rebuilding the entire identity infrastructure from scratch.
Directory data has several properties that make generic file-level backup insufficient:
- Schema dependency. LDAP entries reference schema object classes and attribute types that must be restored in the correct order. A raw file copy of NTDS.dit does not capture schema independently.
- ACL and security descriptor fidelity. Every AD object carries an NT security descriptor. These are not plain attributes and require special handling during restore.
- GPO and SYSVOL coherence. Group Policy Objects span both the LDAP directory (the
groupPolicyContainerobjects) and the SYSVOL file system share. A backup that only captures the LDAP side produces an inconsistent GPO set on restore. - Replication topology state. In a multi-DC environment, restore must account for USN vectors and replication metadata to avoid tombstone resurrection and USN rollback scenarios.
- Incremental capability. Large directories (100k+ objects) cannot be fully backed up in the available backup window. True incremental capability — via RFC 4533 syncrepl for OpenLDAP/389-DS or Microsoft DirSync for AD — is essential.
2.2 Why existing approaches fall short
| Approach | Coverage | Verdict |
|---|---|---|
| ldapsearch / ldif export scripts | Logical data only; no schema, no ACLs, no SYSVOL | Not production-grade; no Bacula integration; no incremental |
| Windows system state backup | Full NTDS.dit; Windows-only; requires Windows agent on DC | Violates clientless requirement; no OpenLDAP/389-DS support |
| AD Recycle Bin | Object-level undelete only; 60–180 day retention | Not a backup; no point-in-time restore |
| Veeam | No native AD/LDAP agent at directory-protocol level | VM-level backup only; no object-level restore without add-on |
| Commvault Active Directory iDataAgent | Windows AD only; requires agent on each DC | No OpenLDAP/389-DS; license cost high |
| Bacula Community (native, no plugin) | File-level only — backs up raw NTDS.dit or OpenLDAP MDB files | Unsafe while directory is live; no logical restore |
| NetBackup BMR | Bare-metal recovery; not directory-aware | Full server restore only; no object-level or attribute-level precision |
2.3 The PodHeitor approach
The plugin performs network-only, clientless backup: it connects to the directory over LDAP/LDAPS from the Bacula File Daemon host, with no agent installed on the directory server. This is architecturally analogous to how a database backup plugin connects to the database over its native protocol. The directory server is a black box; the plugin speaks LDAPv3, RFC 4533 (syncrepl), and the Microsoft DirSync LDAP extension — the same wire protocols the directory exposes to any replication partner.
This approach works equally well for Windows Server AD (no agent on the DC), Samba AD DC, OpenLDAP, and 389-DS. A single plugin covers the entire directory landscape from one Bacula File Daemon host.
3. Architecture overview
3.1 Pure-Rust cdylib metaplugin
Unlike earlier PodHeitor plugins that use a small C/C++ shim to load a Rust sidecar, the AD/LDAP plugin is a pure-Rust cdylib (ADR-001). The single shared object podheitor-adldap-fd.so is loaded directly by bacula-fd at runtime and exposes the Bacula File Daemon plugin ABI (loadPlugin / unloadPlugin and the event callback table) compiled natively in Rust. There is no fork, no bpipe, no sidecar process.
| Component | File | Role |
|---|---|---|
| Bacula FD plugin (cdylib) | /opt/bacula/plugins/podheitor-adldap-fd.so |
Loaded by bacula-fd; implements Bacula plugin ABI and in-process PTCOMM channel |
| Core engine (statically linked) | Inside podheitor-adldap-fd.so |
All LDAP logic, backup/restore engines, crypto, metrics — no external binary |
Advantages of the pure-cdylib design:
- Single artifact. One file to install, one file to update.
- Zero copy for large ldif blobs. PTCOMM Path-A uses an in-process MPSC channel — LDAP entry data never crosses a process boundary.
- Memory-safe network parsing. The LDAP wire protocol is parsed entirely in safe Rust; no C buffer overflows possible on the LDAP input path.
- Panic isolation. Every C-ABI boundary is wrapped in
catch_unwind; a panic in backup logic propagates a job error to Bacula rather than crashingbacula-fd.
3.2 PTCOMM Path-A (in-process)
PTCOMM is the PodHeitor binary framing protocol used across all PodHeitor plugins for uniform logging, metrics multiplexing, and job lifecycle signaling. In the AD/LDAP plugin, Path-A is used: frames flow over an MPSC channel between the Bacula ABI shim and the backup/restore engine in the same process address space.
PTCOMM status characters:
P = ping/handshake O = ok/ack J = jobinfo
C = config/params S = start D = data
E = end R = restoreobject M = metric
L = log T = terminal X = error
The PTCOMM lifecycle per job:
1. Handshake: fd → plugin "P000010nADLDAP_v1"
plugin → fd "O000003nACK"
2. JobInfo: fd → plugin "J<len>n<msgpack(JobInfo)>"
3. Params: fd → plugin "C<len>n<msgpack(BackupParams|RestoreParams)>"
plugin → fd "O000002nOK"
4. Start: fd → plugin "S000005nSTART"
5. Data: plugin → fd "D<len>n<chunk>" (repeats)
plugin → fd "M<len>n<msgpack(metric)>"
plugin → fd "L<len>n<json log>"
6. End: plugin → fd "E000003nEND"
7. RestoreObj: plugin → fd "R<len>n<msgpack(RestoreObject)>"
8. Status: plugin → fd "T<len>n<msgpack(JobStatus)>"
3.3 Architecture diagram
┌──────────────────────────────────────────────────────────────────────┐
│ Bacula File Daemon (bacula-fd) — Linux x86_64 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ podheitor-adldap-fd.so (pure-Rust cdylib) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ ABI shim (plugin-adldap crate) │ │ │
│ │ │ loadPlugin / unloadPlugin / handlePluginEvent │ │ │
│ │ │ catch_unwind at every C boundary │ │ │
│ │ └───────────────────────┬──────────────────────────────────┘ │ │
│ │ │ PTCOMM Path-A (in-process MPSC) │ │
│ │ ┌───────────────────────▼──────────────────────────────────┐ │ │
│ │ │ adldap-core engine │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ │ │
│ │ │ │ ldap mode│ │ ad mode │ │ cdp mode │ │hybrid_sysvol│ │ │ │
│ │ │ │(syncrepl)│ │(DirSync) │ │(CDP/CDP- │ │(LDAP+SYSVOL)│ │ │ │
│ │ │ │ │ │ │ │ DirSync) │ │ │ │ │ │
│ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬──────┘ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌────▼─────────────▼─────────────▼──────────────▼─────┐ │ │ │
│ │ │ │ ldap3 crate (async LDAPv3 client) │ │ │ │
│ │ │ │ RFC 4533 syncrepl · MS DirSync · RFC 2696 paged │ │ │ │
│ │ │ │ GSSAPI/Kerberos · StartTLS · LDAPS │ │ │ │
│ │ │ └─────────────────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ Restore │ │ Crypto │ │ Metrics │ │ │ │
│ │ │ │ engine │ │AES-256- │ │Prometheus│ │ │ │
│ │ │ │ 6 modes │ │GCM/CCP20 │ │textfile │ │ │ │
│ │ │ └───────────┘ └──────────┘ └──────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│ │
│ LDAP/LDAPS │ SMB/CIFS (hybrid_sysvol only)
▼ ▼
┌───────────────┐ ┌──────────────────────┐
│ AD / OpenLDAP │ │ SYSVOL share │
│ 389-DS / Samba│ │ dcSYSVOLdomain │
│ (any LDAPv3) │ │ Policies + scripts │
└───────────────┘ └──────────────────────┘
│
│ Bacula protocol (TCP)
▼
┌───────────────────┐
│ Bacula Storage │
│ Daemon + Catalog │
└───────────────────┘
3.4 On-disk format: MessagePack-LDIF hybrid
Each LDAP entry is stored as a two-segment file under the virtual path @adldap/<server>/<basedn>/entries/<objectGUID-or-uuid>.ent:
Section 1: 4-byte magic 'PHAE' + msgpack(EntryHeader)
Section 2: msgpack(Vec<Attribute>)
binary attrs (jpegPhoto, userCertificate, msExchMailbox*)
are blob-tagged for round-trip safety
This format is approximately 3–4x more compact than raw ldif for AD payloads (MessagePack vs base64), is Unicode-safe, and supports deterministic ordering for diff operations. A secondary ldif view is generated on demand for dry_run and diff modes.
3.5 RestoreObject state carrier
At end-of-backup the plugin emits a RestoreObject named adldap-state.msgpack into the Bacula catalog. This carries the syncrepl cookie (RFC 4533) or DirSync cookie (AD), the highest USN/CSN seen, a SHA-256 schema hash, and the full entry manifest. The next incremental job picks up the cookie automatically from the catalog — no state file on disk is required, so the plugin survives host loss and reinstall without losing the incremental baseline.
4. Backup modes deep dive
4.1 ldap mode — RFC 4533 syncrepl (OpenLDAP / 389-DS)
How it works
The ldap mode connects to any LDAPv3 server and performs a paged search (RFC 2696) for Full backups, and RFC 4533 syncrepl (refreshOnly or refreshAndPersist) for Incremental and Differential backups. Every entry is serialized to the MessagePack-LDIF format and streamed as a Bacula virtual file.
The syncrepl cookie is carried in the RestoreObject after each job. The next Incremental job presents the cookie to the server and receives only entries changed since that point — including modifications, additions, and deletions (the server marks deletions with syncState=delete).
Full backup: paged search → emit all entries → save cookie in RestoreObject
Incremental: syncrepl refreshOnly (cookie) → emit changed/deleted entries
CDP: syncrepl refreshAndPersist → stream changes in real-time
Samba AD compatibility
Samba AD’s syncrepl implementation omits the per-entry RFC 4533 SyncStateValue control that 389-DS includes. The plugin detects this at parse time and synthesizes the entry identity from objectGUID (preferred) or entryUUID (fallback), routing the entry as state=add. This is semantically correct for Samba’s full-snapshot refresh-only delivery. 389-DS responses remain on the strict path.
When to use
- OpenLDAP 2.4/2.5/2.6 — primary supported target
- 389 Directory Server / Red Hat DS / FreeIPA core directory
- Samba AD DC in syncrepl mode
- Any LDAPv3 server without Microsoft DirSync support
- Environments where a logical, portable ldif-equivalent backup is required
4.2 ad mode — Microsoft DirSync
How it works
The ad mode uses the Microsoft DirSync LDAP extended control (OID 1.2.840.113556.1.4.841) to stream directory changes from Active Directory. DirSync is the same mechanism used by AD replication partners and is the only Microsoft-supported method for incremental directory enumeration. It captures all object changes including security descriptor modifications, tombstoned objects (deleted entries), and operational attributes.
For Full backups, the plugin issues a DirSync request with no prior cookie, which causes AD to return the entire directory tree. For Incremental backups, the previous cookie (stored in the RestoreObject) is presented and AD returns only changes since that watermark.
DirSync cookies encode USN (Update Sequence Number) ranges. Because DRS replication ensures that every DC in the forest stores the same changes with consistent high-watermark vectors, a cookie issued by DC1 can be safely carried to DC2 during failover — the plugin handles this transparently (see Section 4.5).
Required AD permissions
The bind user must hold:
SeSyncAgentPrivilege(“Replicating Directory Changes”) — mandatory for DirSync.- Replicating Directory Changes All — required for tombstone and Recycle Bin access.
- Replicating Directory Changes In Filtered Set — for RODC environments.
- Read access to
<dc>SYSVOL<domain>Policies— required only forhybrid_sysvolmode.
A least-privilege “Backup Operator – PodHeitor” account can be delegated via dsacls.exe — the operator guide provides the exact recipe.
When to use
- Microsoft Active Directory (Windows Server 2012 R2 through 2025)
- Samba AD DC in DirSync mode (domain root
basedn=required — see operator note) - Any scenario requiring tombstone access and AD Recycle Bin integration
- Multi-DC environments where USN cookie portability is needed
4.3 hybrid_sysvol mode — LDAP DirSync + SYSVOL/GPO
How it works
The hybrid_sysvol mode performs a full LDAP DirSync sweep of the directory (identical to ad mode) and simultaneously attaches a SMB-CIFS level snapshot of the SYSVOL share using libsmbclient-rs (Rust bindings to libsmbclient.so). SYSVOL files are stored under @adldap/<server>/sysvol/... with full NT ACL preservation via msgpack-encoded NT security descriptors.
The LDAP and SYSVOL phases are executed as a single-shot pair within one job. Mid-stream LDAP failover to a different DC is intentionally refused: if the LDAP phase rotates to DC2 while the SYSVOL phase is still pointing at DC1’s sysvol_uri=, the resulting backup would contain directory objects from DC2 and GPO files from DC1, which may be in different replication states. The plugin refuses this split-brain scenario and fails Fatal instead, requiring the next scheduled run to start fresh.
When to use
- Any Active Directory environment where GPO restore capability is required
- Compliance scenarios requiring complete identity state including login scripts, software installation packages, and security templates under
Policies - DR planning where a consistent LDAP+SYSVOL snapshot is the authoritative restore point
4.4 replicate mode — live directory-to-directory replication
How it works
The replicate mode opens a syncrepl (or DirSync) session against the source directory and simultaneously opens an LDAP write session against a target directory configured by replicate_target_uri=. Every change observed on the source is applied to the target immediately. The Bacula backup stream still receives a copy for catalog auditability and point-in-time restore.
Cross-vendor replication (AD → OpenLDAP or vice versa) is supported via a configurable schema mapping file (default-schema-mapping.toml). The default mapping translates sAMAccountName → uid, userPrincipalName → mail, and objectGUID → entryUUID (deterministic UUIDv4). Loop detection is handled via objectGUID (AD) or entryUUID (RFC 4530) to prevent replication storms in bidirectional topologies.
When to use
- Cold-standby identity mirror in a DR datacenter, without requiring AD Trust or multi-master replication
- Cross-vendor identity migration projects (AD → OpenLDAP transition)
- Real-time audit copy of a production directory into a read-only LDAP server
4.5 cdp mode — continuous data protection
How it works
The cdp mode uses syncrepl refreshAndPersist to maintain a persistent connection to the directory server and stream changes in real time. The Bacula job stays open and emits backup chunks every cdp_max_seconds (default 60 seconds), bounding RPO to that interval. The cdp_dirsync sub-mode provides the same semantics for Active Directory using the DirSync persistent polling model.
When a transport failure occurs, the plugin exhausts the URI failover list (advancing on TCP errors, TLS failures, and LDAP server-down conditions) with exponential backoff governed by cdp_reconnect_max_attempts, cdp_reconnect_initial_backoff_ms, and cdp_reconnect_max_backoff_ms. The latest syncrepl or DirSync cookie is preserved across reconnects via DcSelector::carry_cookie, so the plugin resumes from where it left off rather than re-reading the entire directory.
Multi-DC failover
All backup modes that accept ldapuri= (or target_ldapuri= for restore) support a comma-separated list of LDAP URIs. The plugin treats this as a forward-only failover sequence. The selector advances only on transport-level errors (TCP refused/reset/timeout, DNS failure, TLS handshake failure, LDAP server-down). It does not advance on authentication failures, authorization failures, or configuration errors — these fail immediately since the same credentials will fail on every DC.
| Mode | Connect failover | Mid-stream failover | Cookie carry across DCs |
|---|---|---|---|
ldap (refresh-only) |
Yes | No | Yes (operator sidecar) |
ad (DirSync) |
Yes | Yes | Yes (USN-replicated) |
cdp (refresh+persist) |
Yes | Yes | Yes (cluster-CSN) |
cdp_dirsync |
Yes | Yes | Yes (USN-replicated) |
hybrid_sysvol |
Yes | No (by design) | n/a (LDAP+SYSVOL pair) |
replicate |
Yes | Yes | Yes (selector-managed) |
| restore (any submode) | Yes | No (by design) | n/a |
5. Restore modes deep dive
The restore engine accepts a mode= parameter selecting among six restore strategies. All modes share the same entry-streaming infrastructure; the difference is in what they write to the target directory.
5.1 object mode
Restores a single entry identified by DN or objectGUID. The entry is re-added (or modified if already present, based on replace= policy) with all attributes from the backup. This is the default mode for selective restore — “I accidentally deleted one user account.”
5.2 subtree mode
Restores an entire organizational unit and all its descendants. The engine reorders entries parent-first and can create missing parent OUs if create_parents=true. The where_basedn= parameter redirects the restore to a staging OU (e.g., ou=Restored,dc=lab,dc=local) so the live tree is not disturbed during verification.
5.3 attribute mode
Restores N specified attributes on M entries without touching other attributes. Uses LDAP Modify Replace semantics — the specified attributes are replaced with backup values; all other attributes remain as they are in the live directory. Ideal for “reset displayName to the backup value across a whole OU” without a full subtree restore.
5.4 authoritative mode
Performs a full restore and raises the AD USN version on restored objects so that other DCs replicate FROM the restored DC rather than overwriting it. This is the AD-authoritative restore equivalent and is gated by a mandatory i_understand=yes parameter — applying this mode without the gate triggers an immediate Fatal without writing any LDAP entries. The operator guide explains the blast radius and required DC isolation steps.
5.5 dry_run mode
Computes the ldif that WOULD be applied and writes it to the Bacula RestoreLog. No LDAP write channel is opened against the target. Use this for pre-flight verification before running a destructive restore.
5.6 diff mode
Compares the backup snapshot against the live directory and produces a JSON report of additions, deletions, and modifications: {adds: [], dels: [], mods: []}. Entirely read-only. Useful for compliance auditing, change detection, and post-incident forensics.
6. Feature matrix
| Feature | ldap | ad | hybrid_sysvol | replicate | cdp |
|---|---|---|---|---|---|
| Full backup | Yes | Yes | Yes | Yes | Yes |
| Incremental backup (RFC 4533) | Yes | No | No | Yes | Yes |
| Incremental backup (DirSync) | No | Yes | Yes | No | Yes (cdp_dirsync) |
| Multi-DC failover | Yes | Yes | Yes | Yes | Yes |
| Cookie carry across DCs | Yes | Yes | n/a | Yes | Yes |
| SYSVOL / GPO backup | No | No | Yes | No | No |
| NT ACL preservation | Partial | Yes | Yes | Yes | Yes |
| Cross-vendor replication | No | No | No | Yes | No |
| Encryption at-rest (AES-256-GCM) | Yes | Yes | Yes | Yes | Yes |
| Encryption at-rest (ChaCha20-Poly1305) | Yes | Yes | Yes | Yes | Yes |
| HashiCorp Vault KMS backend | Yes | Yes | Yes | Yes | Yes |
| Prometheus metrics | Yes | Yes | Yes | Yes | Yes |
| JSON-Lines structured logging | Yes | Yes | Yes | Yes | Yes |
| Brazilian Unicode (NFC/NFD) hardening | Yes | Yes | Yes | Yes | Yes |
| objectGUID-stable cataloging | No | Yes | Yes | Yes | Yes |
| Parallel branch backup (per-OU pool) | Yes | Yes | No | No | No |
| AD Recycle Bin support | No | Yes | Yes | No | Yes (cdp_dirsync) |
| Kerberos / GSSAPI auth | No | Yes | Yes | Yes | Yes |
| StartTLS / LDAPS | Yes | Yes | Yes | Yes | Yes |
| RPM package | Yes | Yes | Yes | Yes | Yes |
| DEB package | Yes | Yes | Yes | Yes | Yes |
| RestoreObject cookie carry | Yes | Yes | n/a | Yes | Yes |
| CDP sub-minute RPO | No | No | No | No | Yes |
| zstd compression | Yes | Yes | Yes | Yes | Yes |
| lz4 compression | Yes | Yes | Yes | Yes | Yes |
7. Installation guide
7.1 Prerequisites
- Bacula Community 15.0.3 or later installed;
bacula-fdrunning on a Linux x86_64 host - Network connectivity from the
bacula-fdhost to the directory server on LDAP port (389) or LDAPS port (636) - For
mode=ad/ Kerberos: Kerberos client configured (/etc/krb5.confpointing to the AD realm); keytab at/etc/podheitor/bacula.keytab - For
mode=hybrid_sysvol:samba-client-libs(or equivalent) providinglibsmbclient.so - OS: RHEL/OL/Rocky 9+ or Ubuntu 22.04+/Debian 12+
- glibc 2.34+
7.2 RPM installation (EL9 / OL9 / RHEL9 / Rocky 9)
# 1. Install the RPM
dnf install podheitor-adldap-0.1.0-1.el9.x86_64.rpm
# 2. Verify plugin is in place
ls -la /opt/bacula/plugins/podheitor-adldap-fd.so
# 3. Restart bacula-fd to load the plugin
systemctl restart bacula-fd
# 4. Confirm plugin loaded
journalctl -u bacula-fd --since "1 minute ago" | grep podheitor
7.3 DEB installation (Ubuntu 22.04 / Debian 12)
# 1. Install the DEB
apt install ./podheitor-adldap_0.1.0-1_amd64.deb
# 2. Verify plugin is in place
ls -la /opt/bacula/plugins/podheitor-adldap-fd.so
# 3. Restart bacula-fd
systemctl restart bacula-fd
# 4. Confirm plugin loaded
journalctl -u bacula-fd --since "1 minute ago" | grep podheitor
7.4 Credential setup — simple bind
# Create a password file for LDAP simple bind
cat > /etc/podheitor/openldap.pass << 'EOF'
secret_password_here
EOF
chown bacula:bacula /etc/podheitor/openldap.pass
chmod 600 /etc/podheitor/openldap.pass
7.5 Credential setup — Kerberos keytab (AD)
# Create a keytab for the AD backup service account
# On the Windows DC (or via samba-tool):
# ktpass /out bacula.keytab /mapuser bacula-backup@LAB.LOCAL
# /princ bacula-backup@LAB.LOCAL /pass * /crypto AES256-SHA1
# Deploy to the bacula-fd host
install -o bacula -g bacula -m 600 bacula.keytab /etc/podheitor/bacula.keytab
# Verify Kerberos works
kinit -k -t /etc/podheitor/bacula.keytab bacula-backup@LAB.LOCAL
klist
7.6 Installation verification — OpenLDAP smoke test
FileSet { Name = "Smoke-ADLDAP"; Include {
Options { signature=MD5 }
Plugin = "podheitor-adldap: mode=ldap ldapuri=ldap://192.168.15.154
binddn=cn=Manager,dc=lab,dc=podheitor,dc=local
bindpass_file=/etc/podheitor/openldap.pass
basedn=dc=lab,dc=podheitor,dc=local"
} }
Run a Full job via bconsole: *run job=Smoke-ADLDAP level=Full yes. Expected: job status T with non-zero bytes written and PTCOMM START → END visible in the job log.
8. Configuration reference
8.1 Backup parameters
| Parameter | Default | Description |
|---|---|---|
mode |
(required) | ldap / ad / replicate / cdp / hybrid_sysvol |
ldapuri |
(required) | LDAP URI(s), comma-separated for multi-DC failover |
binddn |
Simple bind DN (omit if kerberos=yes) |
|
bindpass_file |
Path to file containing the bind password (mode 0600) | |
kerberos |
no |
Use GSSAPI/Kerberos authentication |
keytab |
Keytab file path (required if kerberos=yes) |
|
starttls |
auto |
Upgrade to TLS on ldap:// if server advertises StartTLS |
tls_cacert |
Custom CA bundle for LDAPS or StartTLS | |
basedn |
(required) | LDAP search base DN |
filter |
(objectClass=*) |
LDAP search filter to scope the backup |
attribs_include |
*,+ |
Attributes to include (+ = operational attributes) |
attribs_exclude |
Attributes to skip (e.g., userPassword) |
|
page_size |
1000 |
RFC 2696 paged search page size |
parallel_branches |
1 |
Per-OU parallel thread pool size; 0 = auto (CPU count) |
include_recycle_bin |
no |
AD only — include tombstoned/deleted objects via LDAP_SERVER_SHOW_DELETED |
dirsync_flags |
0x801 |
DirSync control flags (OBJECT_SECURITY|INCREMENTAL_VALUES|ANCESTORS_FIRST_ORDER) |
sysvol_uri |
SMB URI for SYSVOL share; required for mode=hybrid_sysvol |
|
sysvol_pass_file |
SMB password file for SYSVOL access | |
compress |
zstd |
none / zstd / lz4 |
compress_level |
3 |
zstd compression level (1–22) |
encryption |
none |
none / aes-gcm / chacha20-poly1305 |
kms |
KMS backend: file:/path/key, vault:transit/keys/name, sops:/path |
|
replicate_target_uri |
Target LDAP URI for mode=replicate |
|
replicate_target_binddn |
Target bind DN for mode=replicate |
|
replicate_schema_map |
(default) | Path to default-schema-mapping.toml for cross-vendor replication |
cdp_max_seconds |
60 |
CDP chunk interval in seconds (bounds RPO) |
cdp_reconnect_max_attempts |
6 |
Max full URI-list walks before Fatal |
cdp_reconnect_initial_backoff_ms |
500 |
Initial reconnect backoff in milliseconds |
cdp_reconnect_max_backoff_ms |
30000 |
Maximum reconnect backoff cap in milliseconds |
metrics_path |
/var/lib/podheitor/adldap.prom |
Prometheus textfile output path |
no_normalize_dn |
no |
Disable NFC normalization on DNs (escape hatch) |
allow_anonymous |
no |
Defense-in-depth: refuse anonymous bind |
verify_after_backup |
yes |
Re-read manifest and recompute digests after backup |
8.2 Restore parameters
| Parameter | Default | Description |
|---|---|---|
mode |
object |
object / subtree / attribute / authoritative / dry_run / diff |
target_ldapuri |
(backup URI) | Target directory URI; supports comma-separated failover |
target_binddn |
Target bind DN (defaults to source bind DN) | |
target_bindpass_file |
Target bind password file | |
target_kerberos |
no |
Use Kerberos for target connection |
where_basedn |
Redirect restore to a different base DN (staging OU) | |
replace |
never |
never / if-newer / always |
attributes |
* |
Comma-separated attributes to restore (for mode=attribute) |
entries |
* |
DNs or objectGUIDs to restore (default: all) |
create_parents |
no |
Create missing parent OUs during subtree restore |
i_understand |
Must be yes to enable mode=authoritative |
|
password_reset |
keep |
keep / random / force=<pwd> for user account passwords |
dry_run_output |
Path to write the ldif preview for mode=dry_run |
|
diff_output |
Path to write the JSON diff report for mode=diff |
|
decrypt_kms |
KMS backend path for decrypting an encrypted backup |
9. FileSet examples
9.1 OpenLDAP full backup
FileSet {
Name = "OpenLDAP-Full"
Include {
Options { signature=MD5 }
Plugin = "podheitor-adldap: mode=ldap
ldapuri=ldap://ldap.example.com
binddn=cn=Manager,dc=example,dc=com
bindpass_file=/etc/podheitor/openldap.pass
basedn=dc=example,dc=com
compress=zstd"
}
}
9.2 Active Directory full backup with Kerberos
FileSet {
Name = "AD-Full-Kerberos"
Include {
Options { signature=MD5 }
Plugin = "podheitor-adldap: mode=ad
ldapuri=ldaps://dc01.corp.local:636,ldaps://dc02.corp.local:636
kerberos=yes keytab=/etc/podheitor/bacula.keytab
basedn=DC=corp,DC=local
include_recycle_bin=yes
parallel_branches=4
compress=zstd encryption=aes-gcm
kms=file:/etc/podheitor/adldap.key"
}
}
9.3 Active Directory incremental backup (DirSync)
FileSet {
Name = "AD-Incremental"
Include {
Plugin = "podheitor-adldap: mode=ad
ldapuri=ldaps://dc01.corp.local,ldaps://dc02.corp.local
kerberos=yes keytab=/etc/podheitor/bacula.keytab
basedn=DC=corp,DC=local
cookie_file=/var/lib/bacula/adldap.cookie"
}
}
9.4 Hybrid SYSVOL + LDAP full backup
FileSet {
Name = "AD-Hybrid-SYSVOL"
Include {
Plugin = "podheitor-adldap: mode=hybrid_sysvol
ldapuri=ldaps://dc01.corp.local
kerberos=yes keytab=/etc/podheitor/bacula.keytab
basedn=DC=corp,DC=local
sysvol_uri=smb://dc01.corp.local/SYSVOL/corp.local
compress=zstd"
}
}
9.5 CDP continuous data protection — OpenLDAP
FileSet {
Name = "OpenLDAP-CDP"
Include {
Plugin = "podheitor-adldap: mode=cdp
ldapuri=ldap://ldap1.example.com,ldap://ldap2.example.com
binddn=cn=Manager,dc=example,dc=com
bindpass_file=/etc/podheitor/openldap.pass
basedn=dc=example,dc=com
cdp_max_seconds=30
cdp_reconnect_max_attempts=8"
}
}
9.6 Cross-vendor replication — AD to OpenLDAP
FileSet {
Name = "AD-to-OpenLDAP-Replicate"
Include {
Plugin = "podheitor-adldap: mode=replicate
ldapuri=ldaps://dc01.corp.local
kerberos=yes keytab=/etc/podheitor/bacula.keytab
basedn=DC=corp,DC=local
replicate_target_uri=ldap://standby-ldap.example.com
replicate_target_binddn=cn=admin,dc=example,dc=com
replicate_target_pass_file=/etc/podheitor/standby.pass"
}
}
9.7 Restore — single object by objectGUID
Job {
Name = "AD-Restore-Object"
Type = Restore
Client = adldap-fd
FileSet = "AD-Full-Kerberos"
Storage = File
Pool = Default
Messages = Standard
Plugin = "podheitor-adldap: mode=object
target_ldapuri=ldaps://dc01.corp.local
target_kerberos=yes
entries=550e8400-e29b-41d4-a716-446655440000
replace=always"
}
9.8 Restore — dry run preview before authoritative restore
Plugin = "podheitor-adldap: mode=dry_run
target_ldapuri=ldaps://dc01.corp.local
target_kerberos=yes
dry_run_output=/tmp/preview-restore.ldif"
10. Sizing & capacity planning
10.1 Memory requirements
| Scenario | bacula-fd baseline | Plugin additional RAM |
|---|---|---|
| ldap / ad full backup, single-threaded | 512 MB | +64 MB |
| ad full backup, parallel_branches=4 | 512 MB | +256 MB (4×64 MB) |
| CDP mode (persistent connection) | 512 MB | +96 MB |
| hybrid_sysvol (LDAP + SMB) | 512 MB | +128 MB |
| 100k-entry backup target (RSS cap) | ≤250 MB total bacula-fd RSS | |
10.2 Backup volume estimations
| Mode | Approximate storage per 100k objects |
|---|---|
| ldap / ad full (no compression) | 200–500 MB (varies with attribute density) |
| ldap / ad full (zstd level 3) | 60–150 MB |
| Incremental (1% changed) | 2–10 MB |
| SYSVOL / GPO (hybrid_sysvol) | 10–200 MB (varies with GPO count and policy size) |
| RestoreObject (cookie + manifest) | ≤50 MB (zstd-compressed; 1M-entry worst case) |
10.3 Performance targets (v0.1.0 lab goals)
| Scenario | Target |
|---|---|
| 100k OpenLDAP entries, full backup | ≤8 minutes (4 vCPU, 4 GB RAM) |
| 100k OpenLDAP entries, syncrepl incremental (1% changed) | ≤1 minute |
| 50k AD objects, DirSync full | ≤5 minutes |
| AD DirSync incremental (100 changes) | ≤15 seconds |
| CDP RPO (cdp_max_seconds=60) | ≤90 seconds end-to-end |
10.4 Disk space — plugin installation
| File | Size |
|---|---|
podheitor-adldap-fd.so |
~2–3 MB (Rust with all features statically linked) |
| Total installation footprint | ~2–3 MB |
No external sidecar binary is required. libsmbclient.so is a system package dependency for hybrid_sysvol mode only.
11. Performance report
All measurements are taken in the PodHeitor lab environment on Rocky Linux 9.4 VMs (2–4 vCPU, 2–4 GB RAM) connected to Samba AD DC and 389-DS servers on a 192.168.15.0/24 LAN. Bacula Community 15.0.3 is used throughout.
11.1 Test suite status — v0.1.0
| Category | Tests | Status |
|---|---|---|
| Unit tests (adldap-core) | 734 | All passing |
| Unit tests (plugin-adldap ABI shim) | 14 | All passing |
| Live-LDAP integration tests (389-DS + Samba AD) | 45 | All passing |
| Destructive mid-sweep failover test (opt-in) | 1 | Passing (#[ignore]) |
| Restore-over-live-damage scenarios (delete, modify+delete, modrdn, wholesale wipe) | 4 | All passing |
| Authoritative restore E2E (Samba AD) | 2 | All passing |
| Total | 800+ | All passing |
11.2 Multi-DC failover test results
| Test | Result |
|---|---|
| Connect-time failover (DC1 down at start) | Pass — connects to DC2 transparently |
Mid-stream failover (cdp_dirsync): DC1 stopped mid-poll |
Pass — cookie carries across DCs |
| Mid-sweep failover with 50 entries seeded | Pass — cookie_len=N (N>0) confirmed in log |
| Post-reconnect incremental continuity | Pass — no duplicate entries after reconnect |
11.3 Restore scenario results (389-DS)
| Scenario | Description | Result |
|---|---|---|
| scenario1 | Object deleted; restore via mode=object |
Pass — original attrs including mail round-trip |
| scenario2 | Attribute mutated post-backup then entry deleted; restore brings back original value | Pass |
| scenario3 | Entry renamed via modrdn; restore recreates original DN | Pass — original DN recreated, renamed copy persists (non-destructive) |
| scenario4 | Entire OU (5 children) deleted bottom-up; restore via mode=subtree |
Pass — all 5 leaves round-trip |
11.4 Authoritative restore results (Samba AD)
| Test | Result |
|---|---|
authoritative without i_understand=yes → Fatal |
Pass — Error frame + Fatal; zero LDAP writes |
| authoritative with gate: 2 entries backed up, deleted, replayed | Pass — terminal=Ok, applied≥2, both DNs present, uSNChanged strictly > pre-restore |
12. Compatibility matrix
12.1 Operating system
| OS | Architecture | Status |
|---|---|---|
| RHEL 9 | x86_64 | Supported |
| Oracle Linux 9 | x86_64 | Supported |
| Rocky Linux 9 | x86_64 | Supported (lab platform) |
| AlmaLinux 9 | x86_64 | Supported |
| Ubuntu 22.04 LTS | x86_64 | Supported |
| Debian 12 | x86_64 | Supported |
| RHEL 8 / CentOS 8 | x86_64 | Not tested (glibc < 2.34) |
| Ubuntu 20.04 | x86_64 | Not tested (glibc < 2.34) |
| ARM64 / aarch64 | any | Not yet available |
12.2 Directory server versions
| Directory server | Version | Mode | Status |
|---|---|---|---|
| Microsoft Active Directory | Windows Server 2012 R2 – 2025 | ad, hybrid_sysvol, cdp_dirsync | Supported |
| Samba AD DC | Any current Samba release | ad, ldap, hybrid_sysvol | Supported (lab-tested) |
| OpenLDAP (slapd) | 2.4, 2.5, 2.6 | ldap, cdp, replicate | Supported |
| 389 Directory Server | Current | ldap, cdp, replicate | Supported (lab-tested) |
| Red Hat DS / FreeIPA | Current | ldap, cdp | Supported (via 389-DS) |
| ApacheDS | Current | ldap (generic) | Best-effort |
| OpenDJ | Current | ldap (generic) | Best-effort |
12.3 Bacula versions
| Bacula version | Status |
|---|---|
| Community 15.0.3 | Supported (validated) |
| Community 15.0.x (future) | Expected compatible |
| Community 14.x | Not supported |
| Bacula Enterprise | Not required; the plugin targets Bacula Community |
12.4 LDAP controls used
| OID | Name | Used by |
|---|---|---|
| 1.2.840.113556.1.4.319 | Paged results (RFC 2696) | Full backup |
| 1.3.6.1.4.1.4203.1.9.1.1 | syncrepl (RFC 4533) | LDAP incremental |
| 1.2.840.113556.1.4.841 | DirSync | AD incremental |
| 1.2.840.113556.1.4.417 | LDAP_SERVER_SHOW_DELETED | Tombstone, Recycle Bin |
| 1.2.840.113556.1.4.2065 | DirSync extended (V2) | AD ≥ 2008 R2 |
| 1.2.840.113556.1.4.529 | LDAP_SERVER_EXTENDED_DN | AD restore |
| 1.3.6.1.4.1.1466.20037 | StartTLS | TLS upgrades |
| 1.3.6.1.4.1.4203.1.11.1 | Password Modify (RFC 3062) | Restore with password reset |
13. Security
13.1 Credential handling
Bind passwords are never stored in Bacula FileSet or Job configuration. All passwords are read from dedicated files with mode 0600 owned by the bacula user. The password is passed in-memory to the LDAP client library; it is never written to disk during the job, never appears in process arguments visible via ps, and is redacted from all log output (JSON-Lines logging with mandatory redaction for bindpass, target_bindpass, keytab contents, and KMS resolved keys).
Kerberos keytab files must have mode 0600; the plugin refuses to use a keytab with more permissive permissions.
13.2 Encryption at-rest
The plugin provides its own at-rest encryption layer independent of Bacula stream-level encryption. Two algorithms are supported:
- AES-256-GCM — the recommended default for environments with hardware AES acceleration.
- ChaCha20-Poly1305 — preferred for environments without hardware AES (e.g., embedded or ARM platforms).
Three KMS backends are provided:
file:/path/key— key stored in a file (simplest; key file must be 0600).vault:transit/keys/name— HashiCorp Vault Transit engine; keys never leave Vault.sops:/path— Mozilla SOPS encrypted key file.
A restore attempt with a missing or incorrect KMS configuration fails fast with an explicit error before any directory writes are attempted.
13.3 Network security
The plugin supports LDAPS (direct TLS on port 636) and StartTLS (upgrade on port 389). It does not open any additional listening ports on the bacula-fd host. For hybrid_sysvol mode, the SMB connection to the SYSVOL share uses the Kerberos ticket obtained from the keytab — no separate SMB credentials are required if Kerberos is configured.
Anonymous bind is refused by default (allow_anonymous=no). This is a defense-in-depth measure; any operator explicitly enabling anonymous bind should audit their directory ACLs carefully.
13.4 Authoritative restore safety gate
The mode=authoritative restore is structurally gated: the apply loop has no selector access and cannot proceed without the i_understand=yes parameter being set explicitly. This is not a runtime check — it is enforced in the Rust type system. A restore job without the gate emits an Error frame and a Fatal terminal status without performing a single LDAP write, and the gate enforcement is covered by a live test against Samba AD in the test suite.
14. Monitoring
14.1 Prometheus textfile metrics
At end-of-job (and periodically during CDP mode), the plugin writes Prometheus textfile metrics to /var/lib/podheitor/adldap.prom (path overridable via metrics_path=). These can be scraped by the Node Exporter textfile collector.
14.2 Exposed metrics
| Metric | Type | Description |
|---|---|---|
podheitor_adldap_entries_backed_up |
Counter | Total LDAP entries serialized to Bacula |
podheitor_adldap_bytes_written |
Counter | Total bytes written to Bacula Storage Daemon |
podheitor_adldap_job_duration_seconds |
Histogram | Per-job backup duration |
podheitor_adldap_errors_total |
Counter | Total error events by error code |
podheitor_adldap_replicate_lag_seconds |
Gauge | CDP/replicate mode: age of most recently applied change |
podheitor_adldap_failover_events_total |
Counter | Count of multi-DC failover events by mode and DC pair |
podheitor_adldap_cookie_age_seconds |
Gauge | Age of the current syncrepl/DirSync cookie |
podheitor_adldap_compression_ratio |
Gauge | Most recent compression ratio (compressed / raw) |
14.3 Node Exporter textfile scrape configuration
scrape_configs:
- job_name: 'podheitor-adldap'
static_configs:
- targets: ['bacula-fd-host:9100']
metrics_path: /metrics
scrape_interval: 60s
The Node Exporter must be started with --collector.textfile.directory=/var/lib/podheitor/ (or the path configured via metrics_path=).
14.4 JSON-Lines structured logging
All plugin log output is emitted as JSON Lines to stderr (captured by bacula-fd) and tee’d to /var/log/bacula/podheitor-adldap-<jobid>.log. Levels: TRACE / DEBUG / INFO / WARN / ERROR. Key diagnostic events emitted at WARN level:
{"lvl":"warn","msg":"cdp_dirsync: connect failure on ldaps://dc1.corp.local (LDAP_SERVER_DOWN); failing over to ldaps://dc2.corp.local at cookie_len=128"}
{"lvl":"warn","msg":"cdp: all DCs exhausted on connect (attempt 3/6); last error: ...; backing off 4000ms then restarting walk"}
{"lvl":"warn","msg":"schema_hash mismatch: promoting Incremental to Full"}
Search the FD job log for failing over, exhausted, or schema_hash mismatch to surface operationally significant events for SLO reporting.
15. Troubleshooting guide
15.1 Common errors
Plugin not found at startup
Error: cannot open shared object file: No such file or directory
Cause. podheitor-adldap-fd.so is not in the configured plugin directory.
Fix. Verify /opt/bacula/plugins/podheitor-adldap-fd.so exists and the PluginDirectory directive in bacula-fd.conf points to /opt/bacula/plugins.
LDAP connection refused
podheitor-adldap: LDAP_SERVER_DOWN connecting to ldaps://dc01.corp.local:636
Cause. The directory server is unreachable, TLS failed, or the URI is wrong.
Fix:
# Test connectivity from the bacula-fd host
ldapsearch -H ldaps://dc01.corp.local:636 -x -b "" -s base
# Check TLS certificate validity
openssl s_client -connect dc01.corp.local:636 -CAfile /etc/podheitor/ca.crt < /dev/null
Kerberos authentication failure
podheitor-adldap: GSSAPI bind failed: clock skew too great
Cause. The bacula-fd host clock is more than 5 minutes off from the domain controller time.
Fix:
systemctl enable --now chronyd
chronyc makestep # force immediate sync
DirSync permission denied
podheitor-adldap: DirSync control rejected: insufficientAccessRights (50)
Cause. The bind user does not hold SeSyncAgentPrivilege on the domain.
Fix. Delegate the required rights via dsacls.exe or the Active Directory Users and Computers delegation wizard. See the Operator Guide for the exact dsacls command.
Samba AD: DirSync basedn rejected
podheitor-adldap: DirSync unwillingToPerform (53): basedn must be domain root
Cause. Samba AD rejects DirSync requests rooted below the domain naming context.
Fix. Set basedn=dc=lab,dc=local (domain root) and use filter= to scope results:
Plugin = "podheitor-adldap: mode=ad ...
basedn=dc=lab,dc=local
filter=(&(objectClass=user)(memberOf=cn=eng,ou=groups,dc=lab,dc=local))"
Schema hash mismatch — full backup promoted
podheitor-adldap: schema_hash mismatch (stored=abc123, current=def456); promoting to Full
Cause. The directory schema changed between the last Full backup and this Incremental run (e.g., an AD schema extension was applied).
Fix. This is expected behavior. The plugin correctly self-promotes to Full and saves the new schema hash. No operator action needed unless the schema change was unintentional.
Authoritative restore rejected — missing gate
podheitor-adldap: mode=authoritative requires i_understand=yes; refusing to write
Cause. The i_understand=yes parameter was not set in the restore job.
Fix. Add i_understand=yes to the restore Plugin string. Read the Operator Guide section on authoritative restore blast radius before proceeding.
15.2 Log locations
| Log | Path |
|---|---|
| bacula-fd main log | /opt/bacula/log/bacula-fd.log |
| Plugin per-job log | /var/log/bacula/podheitor-adldap-<jobid>.log |
| Prometheus textfile | /var/lib/podheitor/adldap.prom |
| Bacula Director messages | as configured in bacula-dir.conf Messages resource |
15.3 Enabling debug logging
Add debug=9 to the Plugin string for maximum verbosity:
Plugin = "podheitor-adldap: mode=ldap ... debug=9"
This logs every PTCOMM frame, every LDAP request/response (including DN and result code), and all state transitions.
16. Use cases & deployment scenarios
16.1 Enterprise AD — full DR coverage
Scenario. A 2,000-person company runs Windows Server 2022 AD with two domain controllers. They need nightly full backups including GPOs, daily incremental backups, and tested DR runbooks.
Solution.
- Weekly:
mode=hybrid_sysvolfull backup — captures directory objects + SYSVOL/GPO - Daily:
mode=adincremental backup — DirSync delta only (minutes, not hours) - Retain: 30 days in Bacula pool
- DR test: quarterly
mode=dry_runagainst a lab domain to verify restore procedure
Operational benefit. GPO restore capability is a key differentiator vs. AD Recycle Bin (which does not include SYSVOL). When a GPO is accidentally deleted or misconfigured, the entire GPO file set can be restored from the Bacula catalog without rebuilding from scratch.
16.2 Multi-forest identity compliance audit
Scenario. A financial institution must prove to auditors that user account attributes and group memberships have not been altered without a change ticket. They need an automated daily diff report.
Solution.
- Daily backup job with
mode=ad - After backup completes, a secondary restore job runs with
mode=diff, producing a JSON report of all changes since the previous backup - The diff report is ingested into the SIEM for correlation with change tickets
- Any entry appearing in
delsormodswithout a matching change ticket triggers a security alert
16.3 OpenLDAP CDP for service account directory
Scenario. A microservices platform uses OpenLDAP for service account authentication. Any loss of LDAP data causes dozens of services to fail authentication simultaneously. RPO must be under 2 minutes.
Solution.
- Mode:
cdpwithcdp_max_seconds=60against a two-replica OpenLDAP cluster (ldapuri=ldap://ldap1,ldap://ldap2) - CDP RPO: ≤90 seconds end-to-end
- Mid-stream failover tested: if ldap1 fails during a streaming session, the plugin fails over to ldap2 and continues without job interruption
16.4 FreeIPA domain migration to Samba AD
Scenario. A company is migrating from a FreeIPA (389-DS backend) domain to Samba AD DC. They need a controlled, audited migration with the ability to roll back.
Solution.
- Phase 1: Full backup of FreeIPA via
mode=ldap— canonical snapshot of all user accounts, groups, and policies - Phase 2:
mode=replicatewithreplicate_target_uri=ldap://samba-dcand the default schema mapping — live-mirrors FreeIPA changes into Samba AD for the duration of the migration window - Phase 3: After cutover, run
mode=diffto confirm the Samba AD directory matches the last FreeIPA snapshot - Rollback: if migration fails, restore the FreeIPA directory from the Phase 1 Bacula backup via
mode=subtree
16.5 Authoritative forest recovery after ransomware
Scenario. All domain controllers are encrypted by ransomware. The company must rebuild the directory from backup without replication contamination.
Solution.
- Provision fresh DC — isolated from the network, no replication partners
- Run
mode=dry_runagainst the fresh DC to preview the restore without writing anything - After reviewing the preview ldif, run
mode=authoritativewithi_understand=yes— restores all objects with raised USN so no replication partner can overwrite the restored state - Reconnect DC to network; verify DRS replication converges from the restored state
- Run
mode=diffafter 1 hour to confirm no unexpected replication of encrypted state
17. Comparison with other approaches
17.1 PodHeitor differentiators vs. Bacula Enterprise LDAP/MSAD plugin
| Feature | PodHeitor AD/LDAP | Bacula Enterprise LDAP/MSAD |
|---|---|---|
| SYSVOL / GPO backup | Yes (hybrid_sysvol) | No — LDAP only |
| Live directory-to-directory replication | Yes (replicate mode) | No |
| CDP (continuous data protection) | Yes (cdp / cdp_dirsync) | No |
| True incremental via DirSync + RFC 4533 | Yes (USN / CSN cookies) | Full + diff only |
| Attribute-level restore | Yes (mode=attribute) | Object-level only |
| Dry-run restore preview | Yes (mode=dry_run) | No |
| Diff restore (read-only change report) | Yes (mode=diff) | No |
| Authoritative restore | Yes (with gate) | Partial (MSAD only) |
| Encryption at-rest | Yes (AES-256-GCM / ChaCha20) | Bacula stream-level only |
| Brazilian Unicode hardening | Yes (NFC/NFD, full CI gate) | Best-effort |
| RestoreObject cookie (no state file) | Yes | File on disk |
| objectGUID / objectSID stable cataloging | Yes | DN-stable only |
| Per-OU parallel backup | Yes (parallel_branches=N) | Single-threaded |
| Pure-Rust cdylib (no C/C++ shim) | Yes | C++ shim |
| Prometheus structured metrics | Yes | Text logs only |
| Multi-DC failover with cookie carry | Yes (all modes) | Limited |
17.2 Broader platform comparison
| Feature | Bacula Community + PodHeitor | Bacula Enterprise | Veeam | Commvault |
|---|---|---|---|---|
| AD native backup (DirSync) | Yes | Partial | No (VM-level only) | Partial (Windows agent) |
| OpenLDAP / 389-DS native backup | Yes | Limited | No | No |
| SYSVOL / GPO backup | Yes | No | No | No |
| CDP / near-zero RPO | Yes | No | No | Limited |
| Cross-vendor LDAP replication | Yes | No | No | No |
| Attribute-level restore | Yes | No | No | No |
| Object-level restore | Yes | Partial | Add-on required | Yes (Windows AD) |
| Authoritative restore | Yes | Partial | No | No |
| Encryption at-rest (plugin-level) | Yes | Stream-level | Yes | Yes |
| Prometheus metrics | Yes | No | No | No |
| Open-source platform base | Yes (Bacula CE) | No | No | No |
| RPM + DEB packages | Yes | Yes | Yes | Yes |
| Clientless (no agent on DC) | Yes | Partial | No | No |
17.3 Cost comparison
Special offer. Bring your renewal proposal for Veeam, Commvault, NetBackup, or any other enterprise backup platform. We will produce a written head-to-head proposal targeting at least 50% savings, with stronger AD/LDAP-specific functionality. Contact heitor@opentechs.lat.
| Solution | Typical annual cost | AD / LDAP support |
|---|---|---|
| Bacula Community + PodHeitor plugin | Significantly less | Full native (this plugin) |
| Bacula Enterprise | Often > US$ 10,000/year | Partial (LDAP/MSAD, no SYSVOL) |
| Veeam Data Platform | Often > US$ 5,000/year | VM-level only (no object restore without add-on) |
| Commvault | Often > US$ 15,000/year | Windows AD only (agent required) |
| NetBackup | Often > US$ 20,000/year | Limited (no LDAP-native) |
Prices vary by environment size and negotiated contracts. Contact heitor@opentechs.lat for a specific comparison against your current renewal proposal.
18. Roadmap
The plugin ships at v0.1.0 with the full MVP feature set across all 5 backup modes and 6 restore modes, validated by 800+ tests. Planned development directions include:
- Phase 9 — packaging and documentation. DEB and RPM packages signed and published to the PodHeitor repository. Full Operator Guide, DR Runbook, and Troubleshooting Guide.
- Windows-native AD agent. A Windows build of the plugin loading into a native Windows
bacula-fdwould enable direct access to the local NTDS.dit via VSS — eliminating the need for a separate Linuxbacula-fdhost for AD backup. Currently out of scope for v0.1.0. - Multi-architecture support. ARM64 / aarch64 packages for cloud-native and Raspberry Pi environments.
- Bacularis administration panel. Plugin configuration UI in the Bacularis web interface for parameter entry without editing raw FileSet configuration. Confirmed out of scope for v0.1.0.
- ApacheDS / OpenDJ explicit support. Currently best-effort via generic
ldapmode; full certification planned for a future minor release. - Azure AD DS / AWS Managed Microsoft AD. Cloud-managed AD services have LDAP endpoints and partially support DirSync — explicit certification and workaround documentation planned.
- Extended Prometheus alerting. Pre-built alerting rules for cookie staleness, schema drift, and replication lag.
No specific release dates are committed. Feature direction is guided by customer feedback and lab findings.
19. Contact information
| Author | Heitor Faria |
| Website | https://podheitor.com |
| heitor@opentechs.lat | |
| Phone / WhatsApp | +1 786 726-1749 |
| Phone / WhatsApp (BR) | +55 61 98268-4220 |
| Product page | https://podheitor.com/active-directory-ldap-plugin |
| Support | heitor@opentechs.lat |
20. Legal / copyright
© 2026 Heitor Faria — all rights reserved.
The PodHeitor Active Directory / LDAP Backup Plugin for Bacula is proprietary software. Unauthorised copying, distribution, modification, or reverse engineering is strictly prohibited. A commercial license is required for production use.
Bacula® is a registered trademark of Kern Sibbald and the Bacula community. Microsoft Active Directory® is a trademark of Microsoft Corporation. All other trademarks — including OpenLDAP, 389-DS, FreeIPA, Samba, Kerberos, HashiCorp Vault, Veeam, Commvault, and NetBackup — are the property of their respective owners.
This document is provided for informational purposes. Performance figures are targets from controlled lab measurements and may vary in production environments depending on hardware, network conditions, directory server configuration, and directory size.
Contact for licensing: heitor@opentechs.lat | https://podheitor.com | +1 786 726-1749 | +55 61 98268-4220
PodHeitor Active Directory / LDAP Backup Plugin for Bacula — v0.1.0 — © 2026 Heitor Faria — all rights reserved — https://podheitor.com
Disponível em:
Português (Portuguese (Brazil))
English
Español (Spanish)