Microsoft configuration#
Prerequisites#
Create an app registration in App Registrations in the Azure portal.
Authentication#
Create a client secret for the app registration. Store the secret in an environment variable and note the Microsoft tenant ID and application client ID.
Application authentication is the recommended mode because it provides explicit, repeatable permissions and complete cleanup semantics.
Required permissions#
Grant the app registration these Microsoft Graph application permissions:
AdministrativeUnit.Read.All: Read all administrative units.Application.Read.All: Read all applications.Directory.Read.All: Read directory data.Group.Read.All: Read all groups.GroupMember.Read.All: Read all group memberships.User.Read.All: Read all users’ full profiles.
Optional permissions#
Grant these application permissions when ingesting the indicated data:
DeviceManagementManagedDevices.Read.All: Intune managed devices and detected apps.DeviceManagementConfiguration.Read.All: Intune device configuration and compliance policies.RoleManagement.Read.Directory: Entra directory role definitions and assignments.AuditLog.Read.All: Entra user sign-in activity and MFA registration; also requires an Entra ID P1 or P2 license.IdentityRiskyUser.Read.All: Entra ID Protection user risk; also requires an Entra ID P2 license.IdentityRiskyServicePrincipal.Read.All: Entra ID Protection service principal risk; also requires a Workload Identities Premium license.
User sign-in activity#
Cartography requests signInActivity with the existing user inventory query,
using pages of up to 500 users and no per-user requests. If the first request
returns 403 Forbidden, it logs a warning and retries the inventory without
activity. Failure of that retry or any later page still propagates and prevents
cleanup. A successful fallback sets sign_in_activity_available to false and
clears the activity timestamps, since the sync could not observe them. It still
refreshes basic user inventory and allows its normal cleanup. Availability
describes the request, not whether each user has signed in. A successful
activity-enabled request sets it to true even for users with no activity record;
their timestamps are null.
The three activity properties on EntraUser are native UTC datetimes:
last_successful_sign_in_date_time: last successful interactive or non-interactive sign-in. Use this for inactivity queries.last_sign_in_date_time: last interactive attempt, including failures.last_non_interactive_sign_in_date_time: last non-interactive attempt, including failures.
For example, find enabled accounts with a known successful sign-in older than 90 days:
MATCH (u:EntraUser)
WHERE u.account_enabled = true
AND u.sign_in_activity_available = true
AND u.last_successful_sign_in_date_time < datetime() - duration('P90D')
RETURN u.user_principal_name, u.last_successful_sign_in_date_time
ORDER BY u.last_successful_sign_in_date_time
When sign_in_activity_available is true, a null timestamp means Graph has no
recorded sign-in for that user, not proof that the account never signed in.
Microsoft does not backfill lastSuccessfulSignInDateTime before December 1, 2023.
To review enabled accounts with no recorded successful sign-in, separately
from accounts with a known sign-in older than 90 days:
MATCH (u:EntraUser)
WHERE u.account_enabled = true
AND u.sign_in_activity_available = true
AND u.last_successful_sign_in_date_time IS NULL
RETURN u.user_principal_name
This is a review list, not proof of 90 days of inactivity: it can include newly
created accounts and accounts without backfilled history. Graph can omit
signInActivity entirely for users who never signed in or last signed in before
April 2020.
UserAccount._ont_lastactivity maps to last_successful_sign_in_date_time, a
native datetime that is null whenever activity could not be read. Other providers
may use different timestamp representations, so normalize them before comparing.
These timestamps measure sign-in recency, not usage frequency or active human use; background clients can produce successful non-interactive sign-ins.
See Microsoft’s signInActivity reference and inactive-account guidance.
MFA registration and identity risk#
Cartography reads three optional reports once per sync and merges them into the inventory:
Report |
Permission |
License |
Properties |
|---|---|---|---|
|
|
Entra ID P1 or P2 |
|
|
|
Entra ID P2 |
|
|
|
Workload Identities Premium |
The same risk properties on |
If a report returns 403 Forbidden, Cartography logs a warning, clears its
properties, and still syncs users and service principals. Other errors
propagate. Clearing follows the sign-in activity behavior above: a value the
sync could not observe is not kept, so a revoked permission cannot leave a stale
atRisk state in place. Each report has an availability flag that is false when
it was denied, so a null property then means unknown rather than absent:
EntraUser.mfa_registration_availablefor the registration reportEntraUser.risk_availablefor the riskyUsers reportEntraServicePrincipal.risk_availablefor the riskyServicePrincipals report
When a risk report was read, users and service principals that are not in it
have null risk properties and risk_available set to true.
EntraUser.user_type (Member or Guest) and external_user_state come
from the base user inventory and need no extra permission.
The registration report lags by up to 36 hours and leaves out disabled users, so their MFA properties stay null. It also leaves out soft-deleted users, which the user inventory doesn’t include either.
Federated domains#
The registration report only covers methods registered in Entra ID. A user
whose domain is federated to another identity provider, such as AD FS or Okta,
completes MFA there, so the report shows no methods for them. For that reason
is_mfa_registered is not mapped to UserAccount._ont_has_mfa.
Cartography syncs the tenant’s domains as EntraDomain nodes, using the
Directory.Read.All permission it already requires, and links each user to the
domain of its user principal name with (:EntraUser)-[:IN_DOMAIN]->(:EntraDomain).
authentication_type is Managed when Entra ID signs the domain’s users in and
Federated when another identity provider does.
For example, find enabled members of managed domains with no registered MFA:
MATCH (u:EntraUser)-[:IN_DOMAIN]->(d:EntraDomain {authentication_type: 'Managed'})
WHERE u.account_enabled = true
AND u.user_type = 'Member'
AND u.mfa_registration_available = true
AND u.is_mfa_registered = false
RETURN u.user_principal_name, d.name
Defender for Endpoint#
Defender for Endpoint machines and unresolved alerts sync by default when the tenant grants access. To include them, grant admin consent for these application permissions on the same app:
Dataset |
API |
Permission |
Data |
|---|---|---|---|
|
WindowsDefenderATP |
|
Defender for Endpoint machine inventory |
|
Microsoft Graph |
|
Unresolved Defender for Endpoint alerts |
Machine inventory covers the tenant’s configured Defender retention period. Only unresolved Defender for Endpoint alerts are ingested; resolved alerts and alerts outside the retention window are removed on the next successful sync. Repeated records or pagination cursors mean the provider’s data changed mid-read, so the sync aborts before writes; rerun once those changes settle.
Each dataset is skipped, without cleanup, when the tenant can’t provide it:
Defender for Endpoint isn’t provisioned (token error AADSTS500011), its
permission is missing (401 or 403), or, for machines, Microsoft returns 404
because there are no recently seen machines. A skip logs its cause, naming the
permission to grant when a request is denied, and preserves previously synced
Defender data, like the optional Entra permissions above. The connector uses the
commercial cloud endpoints and does not support sovereign cloud endpoints.
Machine tokens use https://api.securitycenter.microsoft.com/.default, as required
by Microsoft even though requests go to https://api.security.microsoft.com.
Alert tokens use https://graph.microsoft.com/.default. Both are obtained through
the existing Microsoft service principal credential. No write API permissions are
required.
Configure Cartography#
Provide these options:
--microsoft-tenant-id: Microsoft tenant ID.--microsoft-client-id: App registration client ID.--microsoft-client-secret-env-var: Name of the environment variable containing the client secret.
These credentials apply to all ingestion in the microsoft module, including
Entra ID, Intune, O365, and Defender for Endpoint ingestion.
The deprecated --entra-tenant-id, --entra-client-id, and --entra-client-secret-env-var aliases remain accepted until Cartography v1.0.0. Do not mix --microsoft-* and --entra-* credential flags in one invocation.
Run Cartography#
export MICROSOFT_CLIENT_SECRET='<client-secret>'
cartography \
--selected-modules microsoft \
--microsoft-tenant-id '<tenant-id>' \
--microsoft-client-id '<client-id>' \
--microsoft-client-secret-env-var MICROSOFT_CLIENT_SECRET
Defender machine inventory requests use pages of up to 10,000 records; Microsoft documents limits
of 100 machine requests per minute and 1,500 per hour. Alerts use Graph
/v1.0/security/alerts_v2 with
serviceSource eq 'microsoftDefenderForEndpoint', one query each for the
unresolved statuses new and inProgress, pages
of 100, and @odata.nextLink pagination. Requests retry HTTP 429 and transient 5xx
responses up to five times and cap each Retry-After delay at eight seconds.
Each collection is limited to 10,000 pages; exceeding that limit aborts the
snapshot before graph writes or cleanup.
Sync selected resources#
Use --microsoft-requested-syncs to sync only some Microsoft resources, for
example to isolate a slow or failing dataset in a large tenant:
cartography \
--selected-modules microsoft \
--microsoft-tenant-id '<tenant-id>' \
--microsoft-client-id '<client-id>' \
--microsoft-client-secret-env-var MICROSOFT_CLIENT_SECRET \
--microsoft-requested-syncs users,groups
Valid values are domains, users, groups, administrative_units, applications,
service_principals, app_role_assignments, directory_roles, federation,
intune, o365, defender_machines, and defender_alerts. Intune and O365
run as whole units.
The Entra tenant
always syncs because every other Microsoft node relates to it.
A dataset that isn’t requested is neither synced nor cleaned up, so its existing graph data stays as it was. Relationships to resources that aren’t requested are created only when those resources are already in the graph, so run the full sync at least once before syncing selected resources.
Experimental delegated user authentication#
Use delegated authentication only when you can’t use an app registration and you need a best-effort snapshot of the Entra data visible to a signed-in user. Delegated authentication doesn’t grant the user additional Microsoft Graph permissions. It isn’t a replacement for application authentication.
Limitations#
Only Entra datasets are attempted. Intune, O365, and Defender ingestion are skipped.
Microsoft Graph may return partial results without an authorization error.
If Microsoft Graph returns
403 Forbidden, Cartography stops the affected dataset, reports it, and continues with the next dataset. After attempting all datasets, Cartography exits with a nonzero status so automation can’t mistake the partial inventory for a complete sync. Records from earlier pages of the affected dataset can remain in the graph.Because the command exits with a nonzero status after a denied dataset, run delegated Microsoft ingestion separately from other selected modules.
If Microsoft Graph returns
401 Unauthorized, or an error other than403, Cartography stops the run.Cartography disables cleanup and derived federation analysis. A delegated run doesn’t delete existing Entra data, so records that the user can’t see can remain in the graph.
With
--microsoft-delegated-authalone, Cartography reads the local Azure CLI token cache. Use that only for an attended, one-time run on a trusted workstation. Don’t use it for hosted or unattended inventory collection.With
--microsoft-access-token-env-var, the token can’t be refreshed, and the sync fails once it expires. Token lifetime depends on the issuing client and tenant policy; Cartography logs the remaining lifetime when the sync starts. Mint the token immediately before the run.
A run that reports no denied datasets can still be incomplete. Microsoft Graph
can filter results based on the signed-in user’s effective visibility without
returning 403 Forbidden.
Sign in and run Cartography#
Use a dedicated, non-privileged test user and a fresh disposable Neo4j database when you evaluate this mode.
Sign in to the target tenant. You don’t need an Azure subscription.
az login --tenant '<TENANT_ID>' --allow-no-subscriptionsIf a browser can’t open in your environment, add
--use-device-code. Your tenant’s Conditional Access policy might not allow device-code authentication.Run Cartography.
cartography \ --selected-modules microsoft \ --microsoft-tenant-id '<TENANT_ID>' \ --microsoft-delegated-auth
Do not pass --microsoft-client-id or
--microsoft-client-secret-env-var with delegated authentication.
Run with an access token#
Instead of the local Azure CLI session, Cartography can use a pre-issued
Microsoft Graph access token, for example one minted on another machine or by
your own delegated sign-in flow. --microsoft-access-token-env-var implies
--microsoft-delegated-auth, with the same limitations.
The token must be issued for Microsoft Graph (https://graph.microsoft.com)
and for the tenant passed in --microsoft-tenant-id. An Azure Resource Manager
token is rejected: use --azure-access-token-env-var for the Azure module.
az login --tenant '<TENANT_ID>' --allow-no-subscriptions
export MICROSOFT_ACCESS_TOKEN="$(az account get-access-token \
--resource https://graph.microsoft.com \
--query accessToken --output tsv)"
cartography \
--selected-modules microsoft \
--microsoft-tenant-id '<TENANT_ID>' \
--microsoft-access-token-env-var MICROSOFT_ACCESS_TOKEN
Troubleshooting#
If Defender machines or alerts are missing, check the sync log for a skipped
Defender dataset and its cause. A denied request (401 or 403) names the
permission to grant; admin consent must be granted again after adding a
permission to the app. AADSTS500011 means the tenant has no Defender for
Endpoint, and a machine 404 means Defender reports no recently seen machines. An exhausted retry, malformed
page, or incomplete pagination aborts the Defender sync before graph writes or
cleanup, preserving previous Defender data. A successful empty collection is
eligible for cleanup.