Microsoft Entra ID¶
Private Beta
Cloud Security is currently in Private Beta. Features, APIs, and configuration formats described here may change before general availability. Contact us if you would like access.
A directory-only connection for organizations that use Entra ID or Microsoft 365 but have no Azure infrastructure to enumerate. It collects the tenant-global identity surface over Microsoft Graph: users, groups and membership, service principals and app registrations (with their long-lived credentials), directory roles and PIM eligibility, Conditional Access policies, and administrative units.
Auth model: an Entra ID app registration (service principal) with a client secret and Microsoft Graph application permissions. There is no ARM/subscription setup at all.
Already connecting Azure?
The Azure provider collects this same directory as part of its sweep. You only need a standalone Entra record when there is no Azure subscription to connect — or when you want the directory collected independently of the infrastructure connection. Holding both for one tenant is supported and safe: the Azure connection detects the standalone record and defers its tenant-global directory collectors to it, so the directory is never collected twice.
Prerequisites¶
- Permission to create an app registration (Application Developer or higher).
- Permission to grant tenant-wide admin consent (Privileged Role Administrator or Global Administrator).
- Your tenant ID (Entra ID → Overview).
Required permissions¶
| Grant | Type | Why | Preflight check |
|---|---|---|---|
| Directory.Read.All | Microsoft Graph — Application | Users, groups, service principals, app registrations, domains, directory roles | graph_directory |
Optional permissions¶
| Grant | Unlocks | Preflight check |
|---|---|---|
| AuditLog.Read.All | Last-sign-in / dormancy enrichment. Requires Entra ID P1 or P2 | signin_activity |
| Policy.Read.All | Conditional Access policy posture | (collected during the sweep) |
| RoleManagement.Read.Directory | Directory role assignments and PIM eligibility | (collected during the sweep) |
| Application.Read.All | Fuller app-registration / service-principal credential detail | (collected during the sweep) |
| AdministrativeUnit.Read.All | Administrative-unit scoping | (collected during the sweep) |
| AgentIdentity.Read.All | Source-asserted AI-agent identities in the directory | (collected during the sweep) |
Create the app registration¶
TENANT_ID=$(az account show --query tenantId -o tsv)
APP_ID=$(az ad app create --display-name lc-entra --query appId -o tsv)
az ad sp create --id "$APP_ID"
az ad app credential reset --id "$APP_ID" --years 2 --append \
--display-name lc-entra --query password -o tsv # capture this once
GRAPH=00000003-0000-0000-c000-000000000000
az ad app permission add --id "$APP_ID" --api "$GRAPH" --api-permissions \
7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role # Directory.Read.All
az ad app permission add --id "$APP_ID" --api "$GRAPH" --api-permissions \
b0afded3-3588-46d8-8b3d-9842eff778da=Role # AuditLog.Read.All (optional)
az ad app permission add --id "$APP_ID" --api "$GRAPH" --api-permissions \
246dd0d5-5bd0-4def-940b-0421030a5b68=Role # Policy.Read.All (optional)
az ad app permission admin-consent --id "$APP_ID"
In the portal
Microsoft Entra ID → App registrations → New registration → Certificates & secrets → New client secret (copy the Value) → API permissions → Add a permission → Microsoft Graph → Application permissions → add the permissions above → Grant admin consent for \<tenant>.
credential reset clears existing secrets
Without --append, az ad app credential reset removes every existing
password and certificate on the app before adding the new one.
Application permissions, not delegated
Graph permissions must be Application permissions. Delegated
permissions need a signed-in user and leave graph_directory failing even
after consent.
Create the credentials secret¶
Create the provider record¶
provider.yaml:
provider_type: entra
entra_tenant_id: "<tenant-id>"
entra_client_id: "<application-client-id>"
credentials: hive://secret/entra-sp
internal_domains: [example.com, example.onmicrosoft.com]
refresh: 6h
The client ID may be carried either on the record (entra_client_id) or inside
the secret (client_id); the record wins when both are present.
In the web app: Add provider → Entra ID, then set Tenant ID, Client ID, Credentials, and Refresh interval.
Verify¶
| Check | Required | Meaning if it fails |
|---|---|---|
auth |
✅ | The client ID/secret pair was rejected, or the secret expired. |
graph_directory |
✅ | Directory.Read.All not consented — no identity inventory. |
signin_activity |
— | Last-sign-in and dormancy enrichment unavailable (usually a missing Entra ID P1/P2 licence). |
Troubleshooting¶
provider test result |
Cause | Fix |
|---|---|---|
auth fails with invalid_client |
Stored the secret ID instead of its Value, or the secret expired | Re-mint the secret and update the hive secret |
graph_directory fails after consent |
Permissions added as Delegated, or consent not actually granted | Add them under Application permissions and grant tenant-wide admin consent |
signin_activity fails with a licence error |
Sign-in activity needs Entra ID P1/P2 | Accept the degrade, or add the licence |
| Renewal reminder | Client secrets expire; when one does, every check fails at auth |
Re-mint before expiry and update the secret record — nothing else changes |