CBOM Documentation

CBOM Secure

  • Product Overview
    • Overview
    • Dashboard Interface
    • Dashboard Operations
    • Data Visualization
    • Overview
    • Host
    • Template
      • Networked Deployments
      • Cloud Deployments
      • Off-Grid Deployments
    • Discovery History
      • Tags
      • Tag Scope
      • Integrations
      • Cloud Servers
      • CycloneDX Export
    • Sensors Reference
    • Overview
    • Summary Report
    • PQC Report
    • AI Services
    • Source Code Visualizer
    • Key
    • Certificate
    • CipherSuites
    • Analysis
    • Policy Management
    • User Policy
    • Standard Policy
    • Overview
    • Cases
    • Criteria
    • Notification Format
    • Rules
      • Connections
      • Health
    • User Management
    • Roles & Permissions
    • Audit Trail
    • System Logs
    • Top Bar and User Menu
    • System Configuration
  • Login and Authentication
    • Introduction
      • Components
      • Data Flows
      • Execution Models
      • Incremental Scanning
      • Security Model
      • Topologies
      • Sizing Guide
      • HA and Scaling
      • Ports and Firewall
      • Prerequisites
      • Docker Compose
      • Native on Linux
      • Deployment Server
      • Install Troubleshooting
      • Validation Checklist
      • Managing Services
      • Logging
      • Certificates
      • Configuration
      • Backup and Recovery
      • Ops Troubleshooting
      • Methods
      • SAML 2.0 SSO
      • OIDC and Entra ID
      • Token Authentication
      • Password Policy
      • Sensor vs Connector
      • Sensor Guide Structure
      • Capability Matrix
      • GitHub Sensor
        • AWS
        • Azure
        • Google Cloud
        • Active Directory
        • LDAP Directories
        • AD CS
        • Thales Luna HSM
        • Entrust nShield HSM
        • HashiCorp Vault KMIP
        • Windows Cert Store
        • Linux Trust Store
        • MSSQL TDE
      • Roadmap
      • Integration Model
      • CrowdStrike
      • ServiceNow
      • ServiceNow and Jira Setup
      • Notification Engine
  • Administrative Guide
    • Glossary
    • Configuration Reference
    • Data Model Reference
    • API Reference
    • API Authentication
    • CycloneDX Export Specification
    • Risk and PQC Scoring
    • Built-in Standards
    • Widget Catalogue
    • AI Services Data Handling

LDAP Sensor — Reference

File: DiscoveryManager/Sensors/discover_ldap/ldap_sensor.py Sensor name: Discover_LDAP Protocol: LDAP / LDAPS (port 389 / 636) Last updated: 2026-05-18

Status legend

SymbolMeaning
XCompleted — implemented, tested, output verified
WWorks — implemented and running, limited real-server testing
DDeveloping — partially coded, not finished
FFuture — not started, planned for a later phase
RRemoved — was implemented, pulled out of scope for now (see §6)
—Out of scope — not key/cert material, skip by design

1 — Vendor support matrix

VendorDetected via Root DSECommon phasesShared phasesVendor phases
OpenLDAPvendorName contains OpenLDAPXSSH genericnone
FreeIPA / 389-Directory / RHDSvendorName contains 389-Directory or Red HatXSSH genericR — all removed (see §6c, §6d)
Oracle Internet DirectoryvendorName contains OracleXSSH genericR — removed (see §6a)
Novell eDirectory / NetIQvendorName contains Novell or NetIQXSSH genericR — removed (see §6b)
Unknown / any otherfallbackXSSH genericnone

Current state: All vendor-specific phases removed. Every vendor runs common phases + SSH generic + custom_attributes only. Vendor detection still runs (vendorName stamped in the status doc) but drives no extra phases.

2 — Common phases (all vendors)

These phases run on every target regardless of vendor.

Scan targetStatusLDAP attributeWhat is discovered
Server TLS certificateXTLS handshake (not an LDAP attribute)The LDAPS server's own TLS certificate — issuer, subject, validity, algorithm, public key
User certificatesXuserCertificateX.509 DER certs on user/computer objects — smart card, email signing, VPN
CA certificatesXcACertificateRoot and intermediate CA certs
S/MIME certificatesXuserSMIMECertificatePKCS#7 wrapped cert bundles — each cert in the bundle extracted individually
Cross-certificate pairsXcrossCertificatePairTwo-cert DER pairs — each cert extracted individually
PKCS#12 bundlesXuserPKCS12Password-protected key + cert bundles — passwords supplied via pkcs12_passwords config

3 — Shared phases (all vendors)

Scan targetStatusLDAP attributeNotes
SSH public keys (AD-style)XaltSecurityIdentitiesValues prefixed SSH: <type> <b64> — AD / Samba4 format
SSH public keys (generic)XsshPublicKeyBare OpenSSH public key lines — openssh-lpk community schema, de-facto standard for OpenLDAP
SSH public keys (FreeIPA attr)XipaSshPubKeySame wire format as sshPublicKey — included in the same LDAP filter at zero cost; returns nothing on non-FreeIPA servers
FIDO / U2F SSH keysXall three attrs above[email protected] and [email protected] key types handled by _parse_ssh_wire() — no special config needed
Kerberos keysRkrbPrincipalKey, krbMKeyRemoved — see §6d

All three SSH attributes are handled in one LDAP search by _shared_ssh_generic.py. No FreeIPA-specific logic remains active.

4 — Custom attribute discovery (any vendor)

The sensor supports user-declared attribute scanning via config. Any LDAP attribute value is passed through EC_Libs.parser which auto-detects the format and type.

CapabilityStatusNotes
Unprotected custom attributesXparse(raw) — format auto-detected: PEM / DER / JWK / OpenSSH / PGP / PKCS#12 / PKCS#7 / hex / base64
Password-protected attributesXPasswords tried in order — first successful decryption wins (PKCS#12, JKS)
type hint for raw symmetric keysXRequired in both custom_attributes and custom_protected_attributes when the attribute holds raw symmetric key bytes with no self-describing header (e.g. raw AES, HMAC). Without it parse() cannot produce asset_type="secret_key" and the algorithm/key_size overrides have no effect
Fingerprint-based deduplicationXCertificates deduped on cert_sha256; public keys on public_key_sha256

Design decision: custom_attributes eliminates the need for vendor-specific code in the large category of vendors that store crypto material in self-describing formats (PEM, DER, JWK, PKCS#12). A new unknown vendor that stores certs in a proprietary attribute name needs only a YAML config entry — no code change.

Vendor-specific code is still required when:

  • Crypto material is split across two or more LDAP attributes (e.g. key bytes in one attribute, algorithm in another)

  • Bytes have no self-describing format or header (e.g. raw HMAC shared secrets)

  • Domain objects correlate multiple attributes into one crypto concept (OTP token, vault, NICI secret store)

5 — Future vendor scope

Vendors not yet covered by any dedicated phase. For most, the generic common phases already pick up the bulk of crypto material (standard userCertificate, sshPublicKey, etc.). A dedicated phase would only be needed if the vendor has proprietary attributes with non-standard content.

VendorStatusNotes
IBM Security Directory Server (SDS)FUses standard LDAP schema for certs — common phases cover most material. IBM-specific attributes (ibm-entryUuid, IBM SSL config) are not crypto material. Low priority.
OpenDJ / ForgeRock DirectoryFUses standard LDAP schema. No proprietary crypto attributes known. Common phases + custom_attributes sufficient.
389-Directory standalone (no FreeIPA schema)FDetected as FREEIPA by Root DSE but will not have ipa* attributes. Vendor phases return 0 results silently. No action needed unless proprietary attrs surface.
Microsoft AD LDSFStandalone LDAP, not a full AD DC. Shares some AD schema. Evaluate whether a shared phase with discover_activedirectory makes more sense than a separate LDAP sensor phase.
Apache Directory Server / ApacheDSFNiche, mostly used in testing. Standard schema. Common phases cover it.
Ping Identity / PingDirectoryFEnterprise directory. Standard LDAP schema + some proprietary extensions. Evaluate on demand.

6 — Removed vendor phases (research notes for future reinstatement)

All vendor-specific phases were implemented and then removed to reduce maintenance surface. Full research notes are preserved here so they can be reinstated without starting from scratch.

6a — Oracle Internet Directory (OID)

Detection: vendorName contains Oracle Removed file: _vendor_oracle_pki.py Removed: 2026-05-14 Reason: Oracle Internet Directory is a niche enterprise product with very few new deployments. Real testing requires OID infrastructure. Reinstate when a customer engagement surfaces OID.

Attributes targeted
AttributeTypeWhat it holds
orclpkiCertificateDER bytesX.509 certificate — fully parseable by EC_Libs.parser
orclpkiPrivateKeyEncrypted DEROracle-proprietary encrypted key envelope — existence + metadata only; no unwrap
orclCertificateUsageString flagsComma-separated purpose flags e.g. "signing,encryption" — stored in sourceProperties
Implementation notes
  • Filter: (orclpkiCertificate=*)

  • orclpkiCertificate is standard DER — parseable directly; the vendor phase adds orclCertificateUsage context that custom_attributes cannot supply (it reads one attribute at a time)

  • orclCertificateUsage should be mapped to a keyUsage bitmask in the CBOM doc on reinstatement

  • orclpkiPrivateKey blob is an Oracle-proprietary envelope — no open spec; existence-only reporting is the correct approach

To reinstate
  1. Restore _vendor_oracle_pki.py from git history

  2. In ldap_sensor.py: add from . import _vendor_oracle_pki as _phase_oracle

  3. Change VENDOR_PHASES[ORACLE] from [] to [_phase_oracle]

6b — Novell eDirectory / NetIQ (NICI)

Detection: vendorName contains Novell or NetIQ Removed file: _vendor_edirectory_nici.py Removed: 2026-05-14 Reason: Novell is effectively dead (acquired by Micro Focus, then OpenText). eDirectory is pure legacy — organisations migrate away, not toward it. NICI is completely proprietary with no open specification. Requires real NetIQ infrastructure to test.

Attributes targeted
AttributeTypeWhat it holds
ndspkiPublicKeyDER bytesRSA / EC / Ed25519 public key — standard DER SubjectPublicKeyInfo
ndspkiPrivateKeyEncrypted blobNICI-wrapped private key — existence only; NICI unwrap is proprietary
ndspkiCertificateChainConcatenated DEROne or more X.509 certs concatenated end-to-end — no delimiter
ndspkiKeyPairStatusString"Active" / "Inactive" key pair validity flag
ndspkiSDOpaque blobNICI secret store symmetric key — no algorithm header
Implementation notes
  • Filter: (ndspkiPublicKey=*)

  • ndspkiCertificateChain — split by walking DER TLV length fields (0x30 <len> ...); helper functions _split_cert_chain() and _der_length() existed in the removed file and are straightforward to recover

  • ndspkiSD — NICI secret store key. len(blob) * 8 = size in bits. No way to determine algorithm — report algorithm="Symmetric", secured_by="Novell NICI"

  • ndspkiPrivateKey — algorithm can be inferred from the corresponding ndspkiPublicKey if both are on the same entry; the phase correlated them per DN

  • ndspkiKeyPairStatus → sourceProperties["keyPairStatus"]

To reinstate
  1. Restore _vendor_edirectory_nici.py from git history

  2. In ldap_sensor.py: add from . import _vendor_edirectory_nici as _phase_edir

  3. Change VENDOR_PHASES[EDIRECTORY] from [] to [_phase_edir]

6c — FreeIPA vendor phases (OTP, RADIUS, Vault)

Detection: vendorName contains 389-Directory or Red Hat Removed files: _vendor_freeipa_otp.py, _vendor_freeipa_radius.py, _vendor_freeipa_vault.py Removed: 2026-05-14 Reason: FreeIPA phases were implemented against the schema spec but never run against a live FreeIPA instance. Removing to keep the active sensor lean while the core is being stabilised. FreeIPA is an actively deployed product — reinstatement is higher priority than Oracle or eDirectory.

OTP / TOTP / HOTP token secrets
AttributeTypeWhat it holds
ipaTokenOTPkeyRaw bytesHMAC symmetric key material
ipaTokenOTPalgorithmStringSHA1 / SHA256 / SHA512 — algorithm for the HMAC key
ipaTokenOTPtypeStringtotp / hotp — token type
ipaTokenOTPdigitsString6 / 8 — OTP digit count
ipatokenuniqueidStringUnique token identifier — stored in sourceProperties

Why vendor code is required: ipaTokenOTPkey is raw bytes with no self-describing header. The algorithm comes from ipaTokenOTPalgorithm — a separate attribute. custom_attributes reads one attribute at a time and cannot perform this correlation. Without the sibling attribute, algorithm is unknown.

enctype → algorithm mapping used:

ipaTokenOTPalgorithmCBOM algorithmalgorithmRefsize
SHA1HMAC-SHA1HMAC-SHA1160
SHA256HMAC-SHA256HMAC-SHA256256
SHA512HMAC-SHA512HMAC-SHA512512
  • Filter: (ipaTokenOTPkey=*)

  • secured_by = "FreeIPA", state = "Active"

  • discoverID = UUID5 seeded from ldap::otp::{host}::{dn}::{sha256}

RADIUS shared secrets
AttributeTypeWhat it holds
ipaTokenRadiusSecretRaw bytesRADIUS HMAC shared secret — no header, no algorithm tag
ipaTokenRadiusServerStringRADIUS server hostname/IP — stored in sourceProperties
ipaTokenRadiusUserNameStringRADIUS username — stored in sourceProperties

Why vendor code is required: ipaTokenRadiusSecret is raw symmetric bytes with no self-describing format. parse() would produce nothing or SIZE_HEURISTIC at best. The vendor phase gives it meaningful context: algorithm="RADIUS-SharedSecret", algorithmRef="RADIUS/SharedSecret", secured_by="FreeIPA".

  • Filter: (ipaTokenRadiusSecret=*)

  • Size = len(secret_bytes) * 8 bits

  • discoverID = UUID5 seeded from ldap::radius::{host}::{dn}::{sha256}

Vault keys
AttributeTypeWhat it holds
ipaVaultPublicKeyDER bytesRSA / EC public key for asymmetric vaults — parseable by EC_Libs.parser
ipaVaultSaltRaw bytesSalt for symmetric vault encryption
ipaVaultTypeStringstandard / symmetric / asymmetric — vault type context

Note: ipaVaultPublicKey is standard DER and could technically be covered by custom_attributes. However ipaVaultType context (which vault type this key belongs to) comes from a sibling attribute — the vendor phase correlates them per entry. ipaVaultSalt is domain-specific salt material; parse() would not recognise it as crypto material.

  • Filter: (|(ipaVaultPublicKey=*)(ipaVaultSalt=*))

  • ipaVaultSalt reported as secret_key, algorithm="AES", algorithmRef="AES/VaultSalt", size = len(salt) * 8

  • discoverID = UUID5 seeded from ldap::vault::pubkey::{host}::{dn}::{sha256} or ldap::vault::salt::{host}::{dn}::{sha256}

To reinstate
  1. Restore _vendor_freeipa_otp.py, _vendor_freeipa_radius.py, _vendor_freeipa_vault.py from git history

  2. In ldap_sensor.py:

    from . import _vendor_freeipa_otp    as _phase_otp
    from . import _vendor_freeipa_radius as _phase_radius
    from . import _vendor_freeipa_vault  as _phase_vault
  3. Change VENDOR_PHASES[FREEIPA] from [] to [_phase_otp, _phase_radius, _phase_vault]

  4. Test against a live FreeIPA instance — these phases were never run on real infrastructure

6d — Kerberos key scanning (FreeIPA / MIT KDC)

Detection: Ran as a shared phase for FreeIPA only Removed file: _shared_krb_keys_freeipa.py Removed: 2026-05-14 Also removed: KRB_ENCTYPES dict from _helpers.py (was only used by this phase) Reason: Never tested on a live FreeIPA or MIT KDC server. The ASN.1 blob parsing is non-trivial (relies on pyasn1 with a fallback byte scan). Removed alongside FreeIPA vendor phases.

Attributes targeted
AttributeTypeWhat it holds
krbPrincipalKeyASN.1 blobEncrypted Kerberos session keys — one value per enctype per principal
krbMKeyRaw blobKerberos master key — encrypts all principal keys in the KDC database
Implementation notes
  • Filter: (|(krbPrincipalKey=*)(krbMKey=*))

  • krbPrincipalKey is an MIT KDB ASN.1 envelope: SEQUENCE OF EncryptionKey { keytype [0] INTEGER, keyvalue [1] OCTET STRING }. Multiple DER structs may be concatenated in one attribute value

  • Enctype extraction: try pyasn1 BER decode first; fall back to a byte scan for 0x02 <len> <val> patterns where val is a known enctype integer

  • krbMKey is the KDC master key — reported as AES/KerberosMasterKey; actual algorithm not inferrable from the blob alone

  • Key material is always encrypted under the KDC master key — never extractable

Enctype → algorithm mapping:

Enctype intNameCBOM algorithmsize
1DES-CBC-CRCDES56
3DES-CBC-MD5DES56
17AES128-CTS-HMAC-SHA1-96AES128
18AES256-CTS-HMAC-SHA1-96AES256
23RC4-HMACRC4128
  • secured_by = "Kerberos KDC", state = "Active"

  • discoverID = UUID5 seeded from ldap::krb::{host}::{dn}::enctype{n}

To reinstate
  1. Restore _shared_krb_keys_freeipa.py from git history

  2. Restore KRB_ENCTYPES dict to _helpers.py (the 5-entry enctype table above)

  3. In ldap_sensor.py: add from . import _shared_krb_keys_freeipa as _phase_krb

  4. Change SHARED_PHASES[FREEIPA] from [_phase_ssh_generic] to [_phase_ssh_generic, _phase_krb]

  5. Test on a live FreeIPA server before declaring working

7 — What the sensor currently emits

CBOM typekeyTypeSourceNotes
certificate—userCertificate, cACertificate, userSMIMECertificate, crossCertificatePair, TLS handshakesubject, issuer, serialNumber, validFrom, validTo, algorithm, publicKey (b64), certSha256
keypublic_keySSH keys (altSecurityIdentities, sshPublicKey, ipaSshPubKey)SSH wire format — RSA / ECDSA / Ed25519
keyprivate_keyPKCS#12 bundles (userPKCS12)Existence + algorithm; material never extracted
keysecret_keyPKCS#12 symmetric keysExistence + size; material never extracted
keyanyCustom attributes (user-declared)Format and type auto-detected by EC_Libs.parser
status—End of each target scanvendor, phase_counts, total, errors

8 — Phase execution order

doDiscover(config)
│
│   ── per target ──────────────────────────────────────────────────────────
│
├── connect()                _connection.connect()
│                            auto-detects LDAPS/636 → StartTLS/389 → plain/389
│
├── resolve base DNs         base_dn supplied  → use as-is
│                            base_dn empty     → _connection.get_naming_contexts()
│                                                reads Root DSE; filters out AD
│                                                infrastructure partitions
│
├── _phase_server_tls        runs once (TLS handshake, ignores base_dn)
│
└── per base DN ─────────────────────────────────────────────────────────
    ├── _phase_stored_certs  userCertificate, cACertificate, userSMIMECertificate,
    │                        crossCertificatePair, userPKCS12
    │                        Phase 1: CRL/ARL scan → populates revoked_serials set
    │                                 stored as (issuer_dn, colon-hex-serial) tuples
    │                                 (RFC 5280 §5.1 — serials are unique per issuer only)
    │                        Phase 2: cert scan → emits certificate + public_key docs
    │
    ├── _phase_ssh_keys      altSecurityIdentities, sshPublicKey, ipaSshPubKey
    │                        (one LDAP search covers all three attributes)
    │
    ├── _phase_custom_attrs  user-declared attributes via custom_attributes /
    │   (if configured)      custom_protected_attributes config keys
    │                        EC_Libs.parser auto-detects format and type
    │
    └── status doc           one per target — phase_counts, total, errors

Memory model: _connection.search() is a generator — entries are never accumulated in a Python list. Each phase iterates directly over the generator, processing and discarding one entry at a time. Results are written to a ListOnDisk (SQLite-backed, PRAGMA synchronous=OFF) so the full result set never lives in RAM regardless of directory size.

9 — sourceProperties — fields on every document

FieldTypeDescription
hoststringIP / hostname of the LDAP server scanned
distinguishedNamestringFull DN of the LDAP entry containing the material
baseDnstringSearch base DN used for this scan
ldapAttributestringAttribute that contained the crypto material

10 — Configuration reference

Full annotated example: DiscoveryManager/Sensors/discover_ldap/ConfigExample.yaml

All config keys
KeyTypeDefaultDescription
hoststring—Hostname or IP of the LDAP server
portint00 = auto-detect (LDAPS/636 → StartTLS/389 → plain/389)
use_sslbooltrueUse LDAPS. Ignored when port=0
verify_sslbooltrueValidate server TLS cert against trusted CAs. Disable only for lab servers with self-signed certs
ca_cert_pathstring""Path to a PEM CA certificate (or bundle) used to verify the server cert. Only used when verify_ssl: true. Leave blank to use the system trust store. Useful for internal/self-signed CAs without importing them into the OS store
auth_methodstring"simple"simple / anonymous / ntlm / sasl
usernamestring""Full bind DN (simple/NTLM) or username (SASL)
passwordstring""Bind password
base_dnstring""Search base. Empty = auto-discover from Root DSE
connect_timeoutint10TCP + TLS handshake timeout in seconds
search_timeoutint30Per-operation LDAP search timeout in seconds
page_sizeint1000Entries per LDAP round-trip (RFC 2696 Simple Paged Results). AD enforces a hard server-side cap of 1000 — setting higher has no effect on AD. Reduce to 250–500 for slow or throttled servers; increase beyond 1000 for OpenLDAP/FreeIPA bulk scans
pkcs12_passwordslist[]Passwords tried in order when opening encrypted userPKCS12 bundles
custom_attributeslist[]User-declared unprotected attributes to scan
custom_protected_attributeslist[]User-declared password-protected attributes to scan
Minimal — single OpenLDAP server
ExtName: Discover_LDAP
host: ldap.corp.example.com
port: 636
use_ssl: true
verify_ssl: true
auth_method: simple
username: "CN=svc-ldap,OU=ServiceAccounts,DC=corp,DC=example,DC=com"
password: "s3cr3t!"
base_dn: "DC=corp,DC=example,DC=com"
With PKCS#12 passwords, custom attributes, and tuned page size
ExtName: Discover_LDAP
host: ldap.corp.example.com
port: 636
use_ssl: true
verify_ssl: true
ca_cert_path: /etc/ssl/certs/internal-ca.pem   # pin internal CA; omit to use OS trust store
auth_method: simple
username: "cn=admin,dc=corp,dc=example,dc=com"
password: "s3cr3t!"
base_dn: "dc=corp,dc=example,dc=com"

page_size: 500              # reduce if the server throttles large pages

pkcs12_passwords:
  - "changeit"
  - "export123"
  - ""

custom_attributes:
  - name: pgpKey
  - name: myInternalCert
  - name: mySSHKey
  - name: myAESKey
    type: secret_key        # hint required — raw bytes have no detectable structure
    algorithm: AES
    key_size: "256"

custom_protected_attributes:
  - name: companyPKCS12
    passwords:
      - "export123"
      - "changeit"
      - ""
  - name: myHMACKey
    type: secret_key        # hint required for raw symmetric bytes in protected attrs too
    algorithm: HMAC-SHA256
    key_size: "256"
    passwords:
      - "vaultpass"
      - ""
Multi-host
ExtName: Discover_LDAP
username: "cn=admin,dc=corp,dc=example,dc=com"
password: "s3cr3t!"
auth_method: simple
page_size: 1000

hosts:
  - host: ldap1.corp.example.com
    port: 636
    use_ssl: true
    base_dn: "dc=corp,dc=example,dc=com"
  - host: ldap2.corp.example.com
    port: 636
    use_ssl: true
    base_dn: "dc=eu,dc=corp,dc=example,dc=com"

11 — Known limitations

LimitationDetail
Custom attrs — symmetric key hintRaw symmetric key bytes have no self-describing header. A raw AES key in a custom attribute requires type: secret_key in config; otherwise parse() returns nothing.
verify_ssl: trueServer cert must be trusted. For internal / self-signed CAs, set ca_cert_path to the PEM CA cert file instead of disabling verification entirely — verify_ssl: false should be used only in throwaway lab environments.
krbPrincipalKey materialEncrypted under KDC master key — never extractable. Existence + enctype reported only. (Phase currently removed — see §6d.)
FreeIPA OTP / RADIUS key bytesRaw symmetric bytes — existence and size only; material never extracted. (Phases currently removed — see §6c.)

12 — Permanent out-of-scope

ItemReason
NTLM password hashesNever exposed over LDAP
Kerberos TGT key bytesEncrypted under KDC master key — not extractable via LDAP
NICI private key unwrappingProprietary Novell format — no open specification or SDK
Certificate template analysisBlueprints only — classification is CBOM server's job
Password policy objectsPolicy rules, not key material

13 — Performance benchmarks

Measured on a local OpenLDAP container (osixia/openldap) seeded with 10,002 entries (one userCertificate each → 2 CBOM docs per entry = 20,004 crypto docs + 1 status doc). Host: Windows 11 dev laptop, Python 3.13, page_size=1000.

MetricRun 1Run 2
Total wall time562.75 s574.79 s
Throughput35.5 docs/s34.8 docs/s
LDAP search calls77
LDAP entries streamed10,00210,002
Total docs returned20,00520,005
— certificate10,00210,002
— key10,00210,002
— status11
RSS before scan47.3 MB48.5 MB
RSS after scan80.9 MB77.7 MB
RSS peak (sampled 100 ms)137.0 MB134.5 MB
Python heap peak (tracemalloc)16.0 MB15.9 MB
Net RSS delta+33.5 MB+29.2 MB

Phase breakdown (Run 1):

PhaseBase DNmsdocssearchesentries
server_tls—117000
stored_certsdc=test,dc=local480,18620,004210,002
shared_ssh_genericdc=test,dc=local2010
custom_attrsdc=test,dc=local73040

Notes:

  • ~95% of wall time is inside stored_certs — dominated by the LDAP wire round-trips and cert parsing, not Python overhead

  • Python heap stays flat at ~16 MB regardless of result set size — ListOnDisk (SQLite-backed) keeps docs off the heap

  • RSS peak of ~137 MB includes the SQLite page cache; the net delta of ~33 MB is the actual sensor footprint

  • page_size=1000 issues 2 searches for 10,002 entries in stored_certs (CRL scan + cert scan); no full accumulation in RAM

2026-05-15 18:18:20,291 INFO discover_ldap.ldap_sensor [Discover_LDAP] scan complete host=localhost total=20005 errors=0

============================================================================== LDAP SENSOR -- BENCHMARK REPORT Host : localhost Base DN : dc=test,dc=local ==============================================================================

TIMING -------------------------------------------------------------------------- Total wall time 562.75 s Throughput 35.5 docs / sec Total LDAP search calls 7 Total LDAP entries streamed 10002

OUTPUT -------------------------------------------------------------------------- Total docs returned 20005 certificate 10002 key 10002 status 1

PHASES -------------------------------------------------------------------------- Phase Base DN ms docs searches entries -------------------------------------------------------------------------- server_tls 117 0 0 0 stored_certs dc=test,dc=local 480186 20004 2 10002 shared_ssh_generic dc=test,dc=local 2 0 1 0 custom_attrs dc=test,dc=local 73 0 4 0 -------------------------------------------------------------------------- TOTAL 480378 20004 7 10002

MEMORY -------------------------------------------------------------------------- Process RSS before scan 47.3 MB Process RSS after scan 80.9 MB Process RSS peak 137.0 MB (sampled every 100 ms) Python heap peak 16.0 MB (tracemalloc) Net RSS delta +33.5 MB

==============================================================================

8 minutes to emit, 20K records | 10K certs and 10K keys with a pageSize of 1K records

14 — Python dependencies

ldap3           >= 2.9
cryptography    >= 41.0
EC_Libs.parser  (internal — auto-detect + parse all crypto formats)
diskcache       (internal — ListOnDisk SQLite-backed result store)

Optional (enables JKS parsing in custom_protected_attributes):

pyjks

Required for ldap_profiler.py (performance benchmarking tool):

psutil
pyyaml

Required if Kerberos phase is reinstated (§6d):

pyasn1

14 — Remaining work

PriorityItemNotes
🔴 HighFreeIPA reinstatement + live testRestore OTP / RADIUS / vault phases (§6c) and test on a real FreeIPA server — these were implemented against schema spec only, never run on live infrastructure
🔴 HighKerberos phase reinstatement + live testRestore _shared_krb_keys_freeipa.py (§6d) — ASN.1 blob parsing needs validation on real FreeIPA / MIT KDC data
🟡 MediumOracle Internet DirectoryRestore _vendor_oracle_pki.py (§6a) when a customer OID engagement surfaces
🟡 MediumeDirectory / NetIQRestore _vendor_edirectory_nici.py (§6b) if a legacy eDirectory environment is in scope
🟡 MediumSchema validation on all emitted docsValidate every emitted doc against Schema.json before appending to results
🟢 LowIBM Security Directory ServerLikely covered by common phases — verify with a real SDS instance
🟢 LowAD LDS evaluationDetermine if AD LDS should share phase code with discover_activedirectory
🟢 LowAD LDS evaluationDetermine if AD LDS should share phase code with discover_activedirectory
Completed
ItemDone
ca_cert_path config key✅ Added to _connection.py, ldap_sensor.py, Schema.json, and §10 docs — allows pinning an internal CA cert for verify_ssl: true without importing into OS trust store
FIDO / U2F SSH key support✅ [email protected] and [email protected] now handled — whitelist gate in _parse_ssh_entry() removed; _parse_ssh_wire() is the authority
CRL revocation issuer-scoped✅ revoked_serials stores (issuer_dn, serial) tuples — RFC 5280 §5.1 compliance; prevents false positives when two CAs share a serial number
type hint for custom_protected_attributes✅ Protected path now reads and forwards type_hint to parse() — required for raw symmetric key bytes; algorithm/key_size overrides were unreachable without it
Null-safety for YAML null values✅ page_size, connect_timeout, search_timeout use or-fallback idiom; passwords: null in custom_protected_attributes normalised to [None]
Phase architecture✅ Sensor split into _phase_server_tls, _phase_stored_certs, _phase_ssh_keys, _phase_custom_attrs; orchestrated by ldap_sensor.py
Base DN auto-discovery✅ _connection.get_naming_contexts() reads Root DSE; filters out AD infrastructure partitions
EC_Libs.parser integration✅ _phase_custom_attrs routes all custom attribute bytes through the shared parser library
SSH generic phase✅ _phase_ssh_keys.py covers altSecurityIdentities, sshPublicKey, ipaSshPubKey in one LDAP search
PKCS#12 password support✅ Passwords merged from pkcs12_passwords + custom_protected_attributes[userPKCS12]
Lab seeding scripts✅ lab-setup/seed-openldap.py, seed-openldap-ssh.ps1, seed-custom-attrs.py
Generator streaming in search()✅ _connection.search() is a generator — entries yielded one at a time; no full result-set accumulation in RAM regardless of directory size
ListOnDisk write performance✅ PRAGMA synchronous=OFF on the SQLite store — eliminates per-write fsyncs; turns ~480 s for 20 K docs into seconds
Configurable page_size✅ page_size config key (default 1000) controls LDAP Simple Paged Results page size; threaded through all phases and exposed in Schema.json
Confidence scale 0–10✅ EC_Libs.parser.confidence rescaled: PARSED_DIRECTLY=10 (never emitted), CERT_CONFIRMED=8, SIZE_HEURISTIC=5, ENCRYPTED_CERT_SIZE=2, EXISTS_ONLY=0
Performance profiler✅ ldap_profiler.py — benchmarks wall time, per-phase timing, LDAP search calls, entries streamed, RSS peak, Python heap peak; outputs human-readable report or --json
doDiscover() returns ListOnDisk✅ Disk-backed sealed reader; DiscoveryManager iterates and calls _close() to delete temp dir
FreeIPA OTP / RADIUS / vault phases✅ Implemented against spec — removed 2026-05-14; research preserved in §6c
Kerberos key phase✅ Implemented with pyasn1 + fallback byte scan — removed 2026-05-14; research preserved in §6d
Oracle + eDirectory cleanup✅ Removed 2026-05-14; research preserved in §6a, §6b
FreeIPA + Kerberos cleanup✅ Removed 2026-05-14; research preserved in §6c, §6d
On this Page:
Support Privacy Policy Terms of Service

Copyright 2026 Encryption Consulting LLC. All rights reserved.