Skip to main content
CID222 Docs

Attribute traffic to directory users

Deploy the User-ID agent so inspected traffic names the person at a workstation instead of its IP address.

  • Version: 0.4
  • Feature: icap
  • Role: admin_user
  • Type: task

An inspected request arrives from an IP address. User-ID attribution turns that address into a directory username, so a detection in All Detections names a person rather than 10.0.5.42. The lookup is served by cid-userid-agent, a small Go service fed by logon events from your domain controllers.

What do I need?

Licence
Any
Role
admin_user

Prerequisites

  • Traffic already reaches CID222 through the inline proxy or a third-party secure web gateway. This page attributes inspected traffic; it does not intercept any.
  • At least one Windows domain controller whose Security event log records interactive logons (event 4624).
  • A service account or scheduled-task identity on that controller that may read the Security event log.
  • TCP 8090 open from the domain controller to the appliance, or to wherever you run the agent.
  • A high-entropy shared token you generate yourself — the agent has no default credential.

How the appliance decides who sent a request

AttributionService resolves the sender in a fixed order and takes the first hit:

OrderSourceWhere it comes from
1Gateway-supplied usernameX-Authenticated-User on ICAP, authenticatedUser on the HTTP callout
2User-ID agentThis page. Real-time logon data from your domain controllers
3Manual attribution tableCIDR-to-label rows you maintain in the dashboard; longest prefix wins
4Enrolled extension fleetA browser device that heartbeated from the same address in the last hour

When all four miss, the event keeps its source address and shows as an em dash in the feed. Step 2 is the only one that names the person who is logged in right now, which is why it outranks the table you maintain by hand.

Warning

The agent answers for an address, not for a session. Shared workstations, NAT and CGNAT all break address-as-identity, and a laptop on a home network has no useful address at all. Attribution is evidence-grade only on a network where one address means one person.

Deploy the agent

Generate the shared token

Produce a random string and keep it somewhere your configuration management can read:

openssl rand -hex 32

This one value authenticates both directions — the domain-controller script writing logon events, and the appliance reading lookups.

Start the agent on the appliance

Add the token to the appliance .env, then start the service under its compose profile:

USERID_AGENT_TOKEN=<token> \
USERID_AGENT_PROTECT_READS=true \
  docker compose --profile userid-agent up -d cid-userid-agent

The container listens on 8090, published on the loopback interface only. Confirm it started:

curl -s localhost:8090/healthz

Warning

With AGENT_PROTECT_READS set to true the agent refuses to serve reads until a token is configured — it answers 503 rather than falling back to an open port. Set the token in the same change.

Point the gateway at the agent

Set these on nestjs-core and restart it:

USERID_AGENT_URL=http://cid-userid-agent:8090
USERID_AGENT_TOKEN=<the same token>
USERID_AGENT_TIMEOUT_MS=250

On boot the gateway logs UserIdAgent enabled with the URL and timeout. Without USERID_AGENT_URL the client is inert and never makes a network call, and the log line says so instead.

Prove the path with a static entry

Before involving Active Directory, seed a known answer. The agent reads a JSON file of address-to-user rows and re-reads it every 30 seconds:

[
  { "ip": "10.0.5.42", "username": "jdoe", "domain": "CORP", "ttlSeconds": 86400 }
]

Point USERID_AGENT_STATIC_FILE at the file, then ask the agent directly:

curl -s -H "Authorization: Bearer <token>" \
  "http://localhost:8090/lookup?ip=10.0.5.42"

The response carries the username and the source that supplied it. A miss returns 404.

Install the collector on a domain controller

Copy examples/dc-agent.ps1 from the agent's distribution to the controller — for example to C:\ProgramData\cid-core\dc-agent.ps1 — and register it as a scheduled task that starts at boot, runs under an identity that may read the Security event log, and restarts on failure every minute.

The script subscribes to Security event 4624 and forwards interactive, network, unlock and remote-interactive logons only. Machine accounts and the built-in service identities are filtered out, so a busy controller does not flood the agent.

Give the collector its token

Set CID_USERID_AGENT_TOKEN in the scheduled task's environment and set the script's agent URL to your appliance:

http://<appliance-fqdn>:8090/events/login

The script exits immediately with an error when the token is missing, so a misconfigured task fails visibly rather than running silently unauthenticated.

Choose how long a logon counts

Leave the time-to-live at its default of 28800 seconds unless your Kerberos ticket lifetime differs.

Eight hours matches the default ticket lifetime, so a cached entry expires at roughly the same time the logon it describes stops being meaningful. A shorter value produces more unattributed events; a longer one attributes a shared desk to whoever used it first.

Verify

  1. Sign a domain user in at a workstation, then query the agent for that workstation's address. It returns that user's name and a remaining time-to-live.
  2. Send AI traffic from the same workstation through the inline proxy. Open All Detections and confirm the row names the user rather than showing an em dash.
  3. Query the agent's /status endpoint. It lists the active providers and the cache size, so you can tell "the controller is not pushing" from "the appliance is not asking".
  4. Revoke the mapping for one address with a DELETE to /cache/<ip> and confirm the next event for that address falls through to the manual attribution table.

If it fails

  • Every event is still unattributed, and the gateway log says UserIdAgent disabledUSERID_AGENT_URL is unset on nestjs-core. The client makes no network call at all in that state.
  • The agent answers 503 to every readAGENT_PROTECT_READS is enabled with no AGENT_AUTH_TOKEN. This is deliberate fail-closed behaviour: set the token or turn the protection off.
  • Lookups return 404 for addresses you know are logged in — the domain-controller script is not delivering. Check the task is running, that the token matches, and that the controller can reach TCP 8090 on the appliance.
  • Attribution works for some users and not others — the script forwards only logon types 2, 3, 7 and 10. Service, batch and cached logons are filtered out by design and produce no entry.
  • A whole subnet resolves to one name — you have a manual attribution row that is wider than you meant. The agent outranks the table, so this appears only where the agent has no answer.
  • The lookup adds latency to inspection — it cannot. The client aborts at USERID_AGENT_TIMEOUT_MS (250 ms by default) and falls through; an agent failure never blocks a verdict.

Known gaps

  • The LDAP provider is a stub. USERID_AGENT_LDAP_URL and its companions connect to the directory and validate the bind, but the agent does not yet enrich cached entries with display name or department. Real-time attribution comes from the domain-controller script, because Active Directory does not expose live address-to-session bindings over LDAP.
  • No mobile or BYOD attribution. There is no logon to observe on an unmanaged device.
  • The domain-controller script is a skeleton. Rate limiting, retry with backoff and structured logging are deliberately left out to keep the example readable. Harden it before unattended production use.

Next steps

  • Inline proxy — the interception path whose events this attributes.
  • Endpoint agent — attribution that follows a device off the corporate network, at the cost of an installed agent.
  • Configure LDAP / Active Directory — separate from this page: it authenticates dashboard sign-in, it does not attribute traffic.

Last updated on

On this page

Download PDF