Technical whitepaper — PodHeitor Active Directory / LDAP for Bacula

Technical whitepaper — PodHeitor Active Directory / LDAP for Bacula

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

  1. Executive summary
  2. Introduction & market context
  3. Architecture overview
  4. Backup modes deep dive
  5. Restore modes deep dive
  6. Feature matrix
  7. Installation guide
  8. Configuration reference
  9. FileSet examples
  10. Sizing & capacity planning
  11. Performance targets
  12. Compatibility matrix
  13. Security
  14. Monitoring
  15. Troubleshooting guide
  16. Use cases & deployment scenarios
  17. Comparison with other approaches
  18. Roadmap
  19. Contact information
  20. 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 groupPolicyContainer objects) 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 crashing bacula-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 for hybrid_sysvol mode.

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 sAMAccountNameuid, userPrincipalNamemail, and objectGUIDentryUUID (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-fd running on a Linux x86_64 host
  • Network connectivity from the bacula-fd host to the directory server on LDAP port (389) or LDAPS port (636)
  • For mode=ad / Kerberos: Kerberos client configured (/etc/krb5.conf pointing to the AD realm); keytab at /etc/podheitor/bacula.keytab
  • For mode=hybrid_sysvol: samba-client-libs (or equivalent) providing libsmbclient.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 STARTEND 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-&lt;jobid&gt;.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 &lt; /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=(&amp;(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-&lt;jobid&gt;.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_sysvol full backup — captures directory objects + SYSVOL/GPO
  • Daily: mode=ad incremental backup — DirSync delta only (minutes, not hours)
  • Retain: 30 days in Bacula pool
  • DR test: quarterly mode=dry_run against 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 dels or mods without 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: cdp with cdp_max_seconds=60 against 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=replicate with replicate_target_uri=ldap://samba-dc and the default schema mapping — live-mirrors FreeIPA changes into Samba AD for the duration of the migration window
  • Phase 3: After cutover, run mode=diff to 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.

  1. Provision fresh DC — isolated from the network, no replication partners
  2. Run mode=dry_run against the fresh DC to preview the restore without writing anything
  3. After reviewing the preview ldif, run mode=authoritative with i_understand=yes — restores all objects with raised USN so no replication partner can overwrite the restored state
  4. Reconnect DC to network; verify DRS replication converges from the restored state
  5. Run mode=diff after 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-fd would enable direct access to the local NTDS.dit via VSS — eliminating the need for a separate Linux bacula-fd host 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 ldap mode; 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
Email 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: pt-brPortuguês (Portuguese (Brazil))enEnglishesEspañol (Spanish)

Leave a Reply