Skip to content

Microsoft Entra ID Sensor ​

What it ingests ​

The org chart of a Microsoft tenant, from the Entra ID directory through Microsoft Graph:

Entra ID objectOntology resultSignal
UserPerson (identity + role properties)user
User's departmentOrganization, with the Person MEMBER_OF ituser
User's managerPerson REPORTS_TO Personuser
Group (Microsoft 365, security, distribution)Organization, with each direct member MEMBER_OF itgroup

A user is identity: it resolves to a Person by its Entra object id (entraid:<object id>) and its email, so the same person already seen in Teams, Outlook, SharePoint or GitHub becomes one Person rather than two. Each Person carries job_title, department, office_location, company_name, employee_id, user_type and account_enabled.

A department and a group are both Organization, told apart by source_type (department or group). A group also carries group_kind: microsoft365, security, mail_enabled_security, or distribution.

REPORTS_TO is the manager edge. It runs from the direct report to the manager, so walking it outward from anyone climbs the chain of command.

Read-only and poll-based. This is Entra ID alone: it does not read mail, calendars, Teams messages or files. Those are their own sources.

Credential ​

References the same microsoft_graph credential as the Microsoft Teams, SharePoint / OneDrive, Outlook Calendar and Outlook sources — a JSON object describing an Entra ID app registration:

json
{
  "tenant_id": "72f988bf-86f1-41af-91ab-2d7cd011db47",
  "client_id": "a1b2c3d4-e5f6-4789-8abc-def012345678",
  "client_secret": "abc8Q~…"
}
bash
curl -sS -X POST https://your-corveil-host/api/credentials \
  -H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
  -d '{"type":"microsoft_graph","name":"contoso-graph","secret":"{\"tenant_id\":\"…\",\"client_id\":\"…\",\"client_secret\":\"…\"}"}'

Already running another Microsoft source? Reuse that credential.

Point this source at the same vault row. You need no second app registration, only two more consented permissions on the existing one (below).

Config fields ​

KeyRequiredNotes
cloudnoglobal (default) for commercial Microsoft 365 — including GCC, which is served by the commercial endpoints — usgov for GCC-High (L4), or usgovdod for DoD (L5).
include_groupsnofalse to ingest users, departments and managers only. On by default. Groups need GroupMember.Read.All.
include_guestsnotrue to also ingest B2B guest accounts (userType Guest). Off by default: guests are usually people outside your organization.

There is no default_entity_type. This source emits Person and Organization, so a single override would mistype one of them.

Provider-side setup ​

1. Use (or create) the app registration ​

If you already configured another Microsoft source, reuse its app registration and skip to step 2. Otherwise follow the Teams guide's app-registration steps. The registration is identical; only the permissions differ.

In the Microsoft Entra admin center → App registrations → your app → API permissions → Add a permission → Microsoft Graph → Application permissions, add these and click Grant admin consent for <tenant>:

PermissionWhy
User.Read.AllRead every user, their properties and their manager (/users/delta)
GroupMember.Read.AllRead groups and their members (/groups/delta). Not needed when include_groups is false

Both must be Application permissions, not Delegated, and both need admin consent. Without consent, Test connection fails with Authorization_RequestDenied.

Group.Read.All also works in place of GroupMember.Read.All. It grants more than this source needs, so prefer GroupMember.Read.All.

3. GCC-High / DoD tenants ​

L4 and L5 are different hosts. Set cloud to usgov for GCC-High and usgovdod for DoD:

cloudGraph hostToken scope
usgovgraph.microsoft.ushttps://graph.microsoft.us/.default
usgovdoddod-graph.microsoft.ushttps://dod-graph.microsoft.us/.default

Both authenticate against login.microsoftonline.us. The cloud value has to match the tenant. Plain GCC (not GCC-High) uses the commercial endpoints, so set cloud to global there.

Wire-up ​

POST /api/sensors

json
{
  "sensor_type": "entraid",
  "name": "Contoso directory",
  "config": {},
  "credentials": { "graph_credential": "<microsoft_graph credential id>" }
}

Users and managers only, on GCC-High:

json
{
  "sensor_type": "entraid",
  "name": "Agency directory",
  "config": { "cloud": "usgov", "include_groups": "false" },
  "credentials": { "graph_credential": "<microsoft_graph credential id>" }
}

Verify ​

Run Test connection. It lists one user and, unless include_groups is false, one group, so a missing consent fails here rather than on the first poll.

Once polling, the per-poll summary log reports users_collected, groups_collected, pages and lookups. In the ontology, a Person from this source has source: entraid, and Graph shows its REPORTS_TO edge to its manager and its MEMBER_OF edges to its department and groups.

How sync works ​

  • First poll: the whole directory. The source walks Graph's /users/delta and /groups/delta from the start. A tenant larger than one poll's page budget drains over several polls: the source stores the point it reached and continues from there.
  • After that: only changes. Once a feed reaches the end, Graph hands back a delta link and every later poll returns only the users and groups that changed since the last one.
  • Changed objects arrive complete. For each changed user or group, Graph's change feed returns every property this source asks for, not only the ones that changed. A title change therefore doesn't erase the department.
  • Managers and members are named by lookup. The change feeds give a manager or member only as an object id. Ids the source hasn't already seen in the same poll are looked up in batches, within a per-poll budget.
  • Expired change tokens resync automatically. After a long gap Graph can invalidate a delta link (HTTP 410). The source re-enumerates that feed.
  • Throttling pauses, it doesn't fail. On a 429 or 503 the source keeps what it already processed and resumes from that point on the next poll. Missing consent stops the source with an auth error, since retrying can't fix it.
  • Cadence. The source runs on the sensor worker's poll interval. A directory changes slowly, so after the first sync most polls return little or nothing.

Notes and limits ​

Deletions are not removed. A deleted or soft-deleted user or group is skipped: nothing is emitted for it, and the entity already in the ontology stays. Disabled accounts are ingested with account_enabled: false.

Edges are added, never retracted. When someone changes manager or leaves a group, the new edge is added but the old REPORTS_TO or MEMBER_OF edge remains.

Direct members only. A group's MEMBER_OF edges come from its direct user members. Nested groups and non-user members (devices, service principals) are not modeled.

Large groups are split. A group with more than 500 members is ingested as several events of up to 500 members each. Every event carries the group's full properties.

Departments are free text, matched exactly. A department is whatever string the directory holds, so Engineering, engineering and Eng are three different departments. Clean up department values in Entra ID if they vary. A department and a group with exactly the same name are the same Organization in the ontology.

Guest accounts are off by default. With include_guests off, guests are not ingested as Persons, linked as group members, or named as anyone's manager.

Not included: sign-in and audit logs, devices, applications and service principals, administrative units, licenses, and photos.