Skip to main content

Active Directory Inventory

Overview​

Active Directory Inventory is a pull-based source that takes a periodic snapshot of the computer objects in an Active Directory domain and sends one record per computer into a Praxis pipeline: every Windows server, workstation and domain controller joined to the domain, plus non-Windows machines joined through Samba, Centrify and similar tools. These records are asset context: they describe what exists, not what happened. A SIEM uses them to enrich events with the machine's operating system, role and account status.

DataHow it is readRecord attribute
Computer accounts: name, DNS host name, operating system and version, security identifier, GUID, group memberships, service principal names, last logon, account statusOne paged LDAP search for (objectCategory=computer) against a domain controllerad.inventory.resource = computer

Each record is classified as domain_controller, server, workstation or unknown (ad.computer.category). Active Directory stores nothing about the hardware, so laptops and desktops are both workstation; tell them apart downstream from a naming convention, an OU, or an endpoint-management source.

The collector reads the directory over LDAP, so it can run anywhere that reaches a domain controller: a Linux host, a member server, or the domain controller itself. It does not need to be domain-joined unless you use Windows integrated authentication.

Like other sources, Active Directory Inventory does not set a Google SecOps log type. See Sending to Google SecOps.

Supported types: Logs

Supported platforms: Linux, Windows, macOS (sspi authentication: Windows only)

Minimum collector version: 0.5.74

Service account​

Read-only. Any domain user can read computer objects with the default Active Directory permissions, so a plain, non-privileged account is enough. Do not use a Domain Admin. Recommended: a dedicated account with a long random password and Password never expires, denied interactive logon through Group Policy. Where the collector runs on Windows, a group Managed Service Account or the collector's own machine identity avoids storing a password at all (see Windows integrated authentication).

Authentication​

Authentication picks how the collector proves the account to the domain controller. The credential attached to the node supplies the account.

AuthenticationCredential to attachHow it worksRuns on
Simple bind (default)Basic Auth: username as DOMAIN\user, user@domain or a DN; passwordThe password itself is sent, so use an ldaps:// domain controller or StartTLS. Modern domain controllers refuse a simple bind over plain ldap://.Anywhere
NTLMBasic Auth: username as DOMAIN\user, or user with NetBIOS Domain setNTLMv2 challenge/response; the password never crosses the wire.Anywhere
KerberosBasic Auth (password), or No Auth with Keytab Path and PrincipalA built-in Kerberos client gets a ticket from a KDC, then an LDAP service ticket for the domain controller. No system Kerberos libraries, kinit or domain join needed.Anywhere
Windows integrated (SSPI)No Auth (or Basic Auth for an explicit account)Windows authenticates with the account the praxis-collector service runs as. Nothing secret in the configuration.Windows only

NTLM and Kerberos failures count against the account like a wrong password does, so the collector stops trying other domain controllers after any rejected bind rather than risk a lockout.

Kerberos​

  • List the domain controllers by their registered FQDN, not by IP or alias: the service ticket is requested for ldap/<host>. Service Principal Name (Advanced) overrides it.

  • Realm is the AD DNS domain in upper case, e.g. CORP.EXAMPLE.COM. It can be left empty when the credential username is user@REALM.

  • Every writable domain controller is a KDC, so KDCs is only needed when the KDCs differ from the domain controllers you read from. Kerberos uses TCP 88.

  • For an unattended service, a keytab is the usual choice. Create it on a domain controller:

    ktpass /princ [email protected] /mapuser CORP\svc-praxis /crypto AES256-SHA1 /ptype KRB5_NT_PRINCIPAL /pass * /out svc-praxis.keytab

    Copy it to the collector host readable by the collector only, set Keytab Path and Principal ([email protected]), and attach a No Auth credential. Rotating the account password invalidates the keytab.

  • Clocks must be within 5 minutes of the domain controller.

Windows integrated authentication (SSPI)​

Choose Windows integrated (SSPI) with a No Auth credential when the collector runs on a domain-joined Windows host or on a domain controller. The bind then uses the Windows account the collector service runs as:

Service runs asIdentity usedWorks?
LocalSystem (the installer default) or NetworkServiceThe computer account (CORP\DC01$, CORP\WS01$), which reads the directory like any domain memberYes
A domain account or a group Managed Service AccountThat accountYes, and this is the least-privilege choice
A local accountNone: a local account has no domain identityNo. Use Kerberos, NTLM or Simple bind with a domain account

On the domain controller itself, point Domain Controllers at the DC's own FQDN (ldaps://dc01.corp.example.com:636), not localhost: the TLS certificate and the Kerberos service principal are issued for the FQDN. No account, password or keytab is created or stored.

Signing and channel binding policies​

Domain controller policySimple bindNTLMKerberosSSPI
LDAP server signing requirements = Require signingNeeds TLS (ldaps:// or StartTLS)Needs TLSNeeds TLSNeeds TLS
LDAP channel binding = Always (TLS only)UnaffectedRejectedRejectedAccepted (the collector sends the channel binding token when using the service identity)

With Microsoft's defaults (signing Negotiate, channel binding Never) every combination works. With the strictest hardening, use Simple bind over TLS from any host, or SSPI over TLS from a Windows host.

Basic configuration​

ParameterTypeRequiredDefaultDescription
endpointslistYesDomain Controllers as LDAP URLs, tried in order: ldaps://dc01.corp.example.com:636 or ldap://dc01.corp.example.com:389. The first one that accepts the connection serves the whole snapshot.
search_basestringNowhole domainDN to start from, e.g. OU=Servers,DC=corp,DC=example,DC=com.
include_disabledboolNotrueKeep disabled computer accounts (marked accountEnabled: false).
auth_mechanismsimple | ntlm | kerberos | sspiNosimpleSee Authentication.
domainstringNoNetBIOS domain for NTLM and SSPI when the credential username has no DOMAIN\ prefix.
kerberos.realmstringKerberosfrom user@REALMKerberos realm.
kerberos.kdcslistNothe domain controllers, port 88KDCs as host or host:port.
kerberos.keytabpathNoKeytab on the collector host, instead of a password (with a No Auth credential).
kerberos.principalstringwith keytabAccount in the keytab, user or user@REALM.
start_tlsboolNofalseUpgrade ldap:// connections to TLS before binding.
tls.ca_filepathNohost trust storeEnterprise CA certificate (PEM) that issued the domain controller certificates.
tls.insecure_skip_verifyboolNofalseAccept any certificate. Lab use only.
modescheduled | manualNoscheduledscheduled: snapshot every interval. manual: only with Fetch Inventory Now.
intervalinteger (seconds)No21600 (6h)Time between snapshots. Minimum 900.
sync_requestintegerNo0Fetch Inventory Now: manual mode only.

Advanced​

ParameterTypeDefaultDescription
filterstringExtra LDAP filter ANDed with the computer filter, e.g. (operatingSystem=*Server*).
attributeslistLDAP attributes to read on top of the default set.
timeoutinteger (seconds)60Limit for the connection and for each LDAP operation.
page_sizeinteger1000Computers per LDAP page, each page one batch. AD caps it at its MaxPageSize policy (1000 by default).
kerberos.spnstringldap/<host>LDAP service principal override, for Kerberos and SSPI.
kerberos.ccachepathA kinit credential cache instead of a password or keytab. Not renewed; for tests.
kerberos.config_filepathA krb5.conf to use instead of the built-in realm and KDC settings.

Scheduled and manual fetching​

Works the same way as the AWS Inventory and Okta sources.

Fetch ModeWhen snapshots run
scheduled (default)At start-up, then every Snapshot Interval. The time of the last snapshot is saved on the collector, so a restart waits out the rest of the interval instead of fetching again.
manualOnly when you raise Fetch Inventory Now. Nothing runs on a timer.

To fetch on demand: set Fetch Mode to manual, enter a number higher than the current one in Fetch Inventory Now, and deploy. The completed number is saved on the collector, so a restart does not fetch again; a failed snapshot stays pending and is retried.

Record format​

Each computer becomes one log record whose body is the computer's LDAP attributes as JSON, keyed by attribute name as the domain controller returns it.

Default attributes: cn, name, sAMAccountName, dNSHostName, distinguishedName, objectGUID, objectSid, objectClass, operatingSystem, operatingSystemVersion, operatingSystemServicePack, description, location, managedBy, userAccountControl, primaryGroupID, lastLogonTimestamp, pwdLastSet, accountExpires, whenCreated, whenChanged, logonCount, servicePrincipalName, memberOf, msDS-SupportedEncryptionTypes, isCriticalSystemObject.

Attribute kindIn the body
objectGUIDGUID text, 01234567-89ab-cdef-0123-456789abcdef
objectSidSID text, S-1-5-21-...-1000
Timestamps (lastLogonTimestamp, pwdLastSet, accountExpires, whenCreated, whenChanged, ...)RFC 3339 UTC; AD's "never" values are left out
Integers (userAccountControl, logonCount, ...)Numbers
servicePrincipalName, memberOf, objectClassArrays
Anything elseA string, or an array when multi-valued

Three derived fields are added: accountEnabled, deviceCategory (domain_controller, server, workstation, unknown) and collectionTime.

FieldOTel destination
(computer JSON)LogRecord.Body (string)
(snapshot start time)LogRecord.Timestamp
(constant)LogRecord.Attributes["ad.inventory.resource"] = "computer"
DNS host name, SAM account name, operating system, GUIDLogRecord.Attributes["ad.computer.dns_host_name"], ["ad.computer.sam_account_name"], ["ad.computer.operating_system"], ["ad.computer.object_guid"]
Category, enabledLogRecord.Attributes["ad.computer.category"], ["ad.computer.enabled"]
Domain, forest, serving domain controllerResource.Attributes["ad.domain"], ["ad.forest"], ["ad.domain_controller"]

Sending to Google SecOps​

Between the source and the Google SecOps destination, add a Google SecOps Standardization processor with the condition log.attributes["ad.inventory.resource"] == "computer" and the log type your tenant uses for Active Directory computer context (WINDOWS_AD is the common choice). Check the parser's expected field names against the body above.

Example configuration​

{
"endpoints": ["ldaps://dc01.corp.example.com:636", "ldaps://dc02.corp.example.com:636"],
"search_base": "", // default: the whole domain
"include_disabled": true, // default: true
"auth_mechanism": "kerberos", // default: simple
"kerberos": { "realm": "CORP.EXAMPLE.COM" },
"mode": "scheduled", // default: scheduled (or "manual")
"interval": 21600, // default: 21600 (6h), minimum 900
"sync_request": 0 // manual mode: raise and deploy to fetch now
}

With a Basic Auth credential holding svc-praxis / its password.

Operational notes​

  • Snapshots, not streaming. Every snapshot re-sends the whole inventory, one batch per LDAP page.
  • Failures. A snapshot is a single search, so any failure (no domain controller reachable, bind rejected, a page failing) aborts it and it is retried after 5 minutes and then with backoff up to 1 hour. Pages already sent are not withdrawn.
  • Scale. A 50,000-computer domain is 50 pages of 1,000 at roughly 2 KB per record; the collector holds one page at a time.
  • Multiple domains. One source reads one domain. Add a source per domain. A global catalog (ldaps://dc:3269) returns a partial attribute set for the whole forest.

Metrics​

Metric NameDescription
prx_src_records_received_total{signal="logs"}Log records emitted, one per computer.
prx_src_bytes_received_totalBytes of the emitted record bodies.
prx_src_pipeline_latency_secondsArrival → downstream handoff.
prx_src_errors_total{reason}Connection, bind or search failures by reason: auth (bind rejected, signing or channel binding required, Kerberos pre-authentication failed), permission_denied, timeout, io.
prx_src_records_dropped_total{reason="backpressure"}Records dropped because the downstream pipeline refused them.