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
usernames2useridexists (common library,default: true, groupasset). - Parsers emit
user.name(or another field listed underusernames2useridin/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 (
curllist/get via Remote Control Vault API as your process allows). - Confirm
secrets:andpasswords: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
displayNameor another AD attribute you choose asapply.key). - Attribute
user.id: the canonical identity Parsec writes when normalization succeeds (oftensAMAccountName, orsAMAccountName@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://…orldaps://…) and searches on eachperiod.password: "{{…}}": bind password from Vault / Feeds[passwords](see Use a Vault password placeholder); avoid plaintext passwords in shared Library YAML.baseandfilter: 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. HeredisplayNamematches logs that carry the user’s display name inuser.name.apply.user.id: value stored on the lookup row. The example buildssAMAccountName@<tenant>with!JOINand!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¶
- Wait for one feed
period(or trigger a feed run if your operations process allows it). - Open Lookups → usernames2userid and confirm keys (display names) and
user.idvalues. - Send or wait for an event where
user.nameequals a lookup key anduser.idis empty. - In Discover, confirm
user.idis 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):
- Reads
user.namefrom the event. - Looks up that string in
usernames2userid. - If the row has
user.id, writes it onto the event whenuser.idwas empty. - 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\uservs display name). Mismatched keys silently miss. - Prefer a read only AD bind account limited to the needed OUs.
- Keep
periodshort enough for joiners and leavers (often1h); very large directories may need a longer period or a narrower filter. - One feed should own
usernames2useridcontent 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.
Related reading¶
- Threat intelligence feeds: feed declarations, including AD SID →
sids2userid - Integrate an LDAP server: Vault secrets for SeaCat Auth LDAP (same Vault idea)
- Asset management: Assets UI for analysts
- Parsec: identity normalization: how lookups fill
user.id - Lookups in detections: using lookups in correlation rules
- Lookups reference: lookup types and deployment