Skip to content

LDAP / Active Directory feed for user identity normalization

Logs often carry display names, login names, or other string identities that do not match a single canonical user.id. An LDAP (Active Directory) feed can sync accounts into the lookup usernames2userid. Parsec then uses that lookup during asset identity normalization so user.name on events resolves to a stable user.id.

This is the same feed → lookup path as threat intelligence feeds, but the goal is identity consistency for Assets, Discover, and detections, not IOC watchlists.

What you need (LDAP input)

Before you write the feed, collect these values. They go into the feed’s input: section:

Field Required Example Meaning
source yes ldap://10.91.44.128 or "{{LDAP_URL}}" LDAP URL of the domain controller. Use ldap://host or ldap://host:389. For TLS use ldaps://host or ldaps://host:636. Feeds copies this URL into the LDAP connection uri. May use a Vault placeholder (Feeds v26.12.07+, LogMan.io v26.12.12).
username yes adldap@domain.int or "{{LDAP_BIND_USER}}" Bind account (UPN or DOMAIN\user). Needs read access to the users you search. May use a Vault placeholder (Feeds v26.12.07+, LogMan.io v26.12.12).
password yes "{{LDAP_USER_PASSWORD}}" Bind password. Prefer a Vault placeholder (Feeds v26.12.07+, LogMan.io v26.12.12); plaintext only for lab setups.
base yes dc=DOMAIN,dc=int LDAP base DN where the search starts (usually the domain root or a specific OU).
filter yes (&(objectCategory=person)(objectClass=user)) LDAP search filter that selects user accounts (excludes computer objects).
attributes yes sAMAccountName displayName Space separated AD attributes to load. Every attribute used in schema / apply must appear here.
period yes 1h How often the feed re-runs the search (for example 1h, 1d).
type yes ldap Must be ldap so LogMan.io Feeds uses the LDAP source.

Network requirement: the LogMan.io Feeds service must reach the host and port in source (firewall / DNS). Prefer a dedicated read only bind account limited to the OUs you need.

Also required on the LogMan.io side:

  • Lookup usernames2userid exists (common library, default: true, group asset).
  • Parsers emit user.name (or another field listed under usernames2userid in /Schemas/Mappings/Asset_<schema>.yaml).

For the SID based variant (sids2userid), see Threat intelligence feeds: Active Directory SID to user ID. For how Parsec applies asset lookups, see Asset management and activity stream.

Use a Vault password placeholder

Availability

Resolving {{…}} / ${passwords:…} placeholders from [passwords] in feed declarations requires LogMan.io Feeds v26.12.07 or newer (LogMan.io v26.12.12).

Do not put secrets into a shared Library feed YAML as plaintext. Put a placeholder in fields such as source / url, username, password, or HTTP headers; LogMan.io Feeds reads the real value from its service config ([passwords]), which ASAB Maestro fills from the Vault.

Typical split of work:

Step Who What
1 Admin / operator Put the secret into the Vault
2 Admin / operator Map the secret onto lmio-feeds in /Site/model.yaml and apply
3 Analyst Use placeholders in the feed declaration (Library /Feeds/…)

1. Store the password in the Vault

On the LogMan.io host (adjust the secret name if you use another id):

curl -X PUT localhost:8891/vault/LDAP_USER_PASSWORD --data 'supersecret'

Secret names are case insensitive. Use a stable uppercase id such as LDAP_USER_PASSWORD or LDAP_FEEDS_BIND_PASSWORD.

The same Vault pattern is used for SeaCat Auth LDAP; see Integrate an LDAP server.

2. Map the secret into Feeds configuration

In /Site/model.yaml, declare the secret and inject it into the Feeds [passwords] section:

services:
  lmio-feeds:
    asab:
      config:
        passwords:
          LDAP_USER_PASSWORD: "{{LDAP_USER_PASSWORD}}"

secrets:
  LDAP_USER_PASSWORD: {}

Then apply the model (Library Apply on /Site, or on the host ./gov.sh up). After apply, the generated /conf/lmio-feeds.conf contains something like:

[passwords]
LDAP_USER_PASSWORD=<value from vault>

Restart / recreate the Feeds instance if your site process does not pick up conf changes automatically.

3. Reference the secret in the feed

In the feed input section set password to the placeholder (same name as in Vault / model):

input:
  source: ldap://10.91.44.128
  type: ldap
  username: adldap@domain.int
  password: "{{LDAP_USER_PASSWORD}}"
  base: dc=DOMAIN,dc=int
  filter: (&(objectCategory=person)(objectClass=user))
  attributes: sAMAccountName displayName
  period: 1h

ASAB style is equivalent:

password: ${passwords:LDAP_USER_PASSWORD}

Plain password: … still works for lab setups; do not commit real passwords into shared Library repos.

HTTP headers (Authorization and similar)

The same placeholders work inside HTTP header values, including JSON headers strings used by TI and other HTTP feeds:

input:
  source: https://ti.example.com/api/v1/ips
  type: json
  headers: '{"Authorization": "Bearer {{API_TOKEN}}"}'

Map API_TOKEN into Feeds [passwords] the same way as LDAP (Vault + model.yaml). You can also use a YAML mapping:

headers:
  Authorization: "Bearer {{API_TOKEN}}"
  X-Api-Key: "{{API_TOKEN}}"

URL fields (source, url, uri, oauth2_token_url, proxy_url) support the same placeholders, for example source: "{{LDAP_URL}}".

If the feed fails to bind

  • Confirm the Vault entry exists (curl list/get via Remote Control Vault API as your process allows).
  • Confirm secrets: and passwords: in the model use the same name as inside {{…}}.
  • Confirm the model was applied so Feeds conf was regenerated.
  • In Feeds logs, a missing mapping looks like: secret was not found in the [passwords] section.

What you get

Piece Role
LDAP / AD Source of truth for account names (displayName, sAMAccountName, and similar)
Feed (lmio/feed, type: ldap) Periodically reads AD and writes lookup rows
Lookup usernames2userid Maps a username string (key) to user.id (attribute)
Parsec asset mapping On each event, looks up user.name in usernames2userid and fills user.id when missing
Active Directory  →  LDAP feed  →  usernames2userid  →  Parsec identity normalization  →  user.id on events / Assets

1. Lookup: usernames2userid

Common library declaration (/Lookups/usernames2userid.yaml):

---
define:
  type: lookup/string
  name: usernames2userid
  label: User names to User ID
  description_title: List of user names or other string values to user IDs
  group: asset
  default: true

keys:
  - name: name
    type: str

fields:
  user.id:
    type: str
  • Key: the string as it appears in logs (for example displayName or another AD attribute you choose as apply.key).
  • Attribute user.id: the canonical identity Parsec writes when normalization succeeds (often sAMAccountName, or sAMAccountName@tenant).

ECS asset mapping wires this lookup to user.name:

identity:
  usernames2userid:
    - user.name

If user.id is already set on the event, Parsec does not overwrite it. The lookup still runs for asset context. When no lookup hits, optional fallback on user.name (for example lowercase_and_domain_removal) may still produce an id.

2. Create the LDAP feed

Add a feed declaration under /Feeds/ in the Library (or create it from the Lookups UI with a feed path). Fill every required input field from What you need.

---
define:
  type: lmio/feed
  name: LDAP User Name Extractor

  schema:
    sAMAccountName: str
    displayName: str

input:
  source: ldap://10.91.44.128
  type: ldap
  username: adldap@domain.int
  password: "{{LDAP_USER_PASSWORD}}"
  base: dc=DOMAIN,dc=int
  filter: (&(objectCategory=person)(objectClass=user))
  attributes: sAMAccountName displayName
  period: 1h

apply:
  key: !ITEM EVENT displayName
  user.id:
    !JOIN
    delimiter: "@"
    items:
      - !ITEM EVENT sAMAccountName
      - !ARG TENANT

output:
  lookup: usernames2userid

How this maps:

  • source / type: ldap: LogMan.io Feeds opens an LDAP connection (ldap://… or ldaps://…) and searches on each period.
  • password: "{{…}}": bind password from Vault / Feeds [passwords] (see Use a Vault password placeholder); avoid plaintext passwords in shared Library YAML.
  • base and filter: limit the search to the OU / user objects you care about.
  • attributes: only the AD attributes listed here are loaded into the feed schema.
  • apply.key: becomes the lookup key. Here displayName matches logs that carry the user’s display name in user.name.
  • apply.user.id: value stored on the lookup row. The example builds sAMAccountName@<tenant> with !JOIN and !ARG TENANT. For a plain account name, use:
apply:
  key: !ITEM EVENT displayName
  user.id:
    !CAST
    what: !ITEM EVENT sAMAccountName
    type: str

Choose the key attribute to match what your parsers put into user.name. If logs use sAMAccountName already, set both key and user.id from that attribute (or skip the feed for those sources).

Optional: emails → user.id

The same pattern fills useremails2userid from AD mail, which Parsec maps from user.email. See the Feeds test library feed LDAP User Email Extractor for the shape; output lookup must be useremails2userid.

3. Verify the lookup

  1. Wait for one feed period (or trigger a feed run if your operations process allows it).
  2. Open Lookups → usernames2userid and confirm keys (display names) and user.id values.
  3. Send or wait for an event where user.name equals a lookup key and user.id is empty.
  4. In Discover, confirm user.id is filled. In Assets, the user principal should align with that id.

4. How automatic normalization uses the data

On the parse path, after enrichment, AssetIdentityProcessor (from /Schemas/Mappings/Asset_ECS.yaml):

  1. Reads user.name from the event.
  2. Looks up that string in usernames2userid.
  3. If the row has user.id, writes it onto the event when user.id was empty.
  4. Continues with useremails2userid / fallbacks as configured.

That normalized user.id drives Assets inventory, activity stream principals, and consistent correlation on identity.

Related email lookup order: usernames first, then emails, then fallback. Details: Parsec asset management.

Practical tips

  • Prefer a Vault placeholder in password (for example "{{LDAP_USER_PASSWORD}}"); see Use a Vault password placeholder.
  • Align case and format of feed keys with parsed user.name (spaces, diacritics, DOMAIN\user vs display name). Mismatched keys silently miss.
  • Prefer a read only AD bind account limited to the needed OUs.
  • Keep period short enough for joiners and leavers (often 1h); very large directories may need a longer period or a narrower filter.
  • One feed should own usernames2userid content unless you have a clear merge strategy.
  • For Windows events that only carry SIDs, use a separate LDAP feed into sids2userid (and Windows SID enrichers), not this lookup.
  • UI alternate names on an asset can complement lookups; the feed keeps directory scale mappings current automatically.