Microsoft Entra ID¶
Connecting from the web app?
Follow the prerequisites and credential creation instructions below, then return to Cloud Security → Settings → Providers → Add provider. Enter the provider IDs under Configuration and save the credential using New secret under Permissions. Run Test Provider, fix required failures, and save. The LimaCharlie CLI examples below are an alternative. First-time setup and verification explains the full journey.
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, administrative units, and the tenant's federated domains — the external identity providers (ADFS and other SAML/WS-Fed trusts) that can assert identities into the tenant.
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 (including federated-domain / external-IdP posture), directory roles and their active assignments | 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 | PIM eligibility — who can activate a privileged role just-in-time. Also requires Entra ID P1 or P2: without the licence the PIM endpoints refuse the call no matter what is consented | (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) |
Skipping the PIM grant is safe
Without RoleManagement.Read.Directory (or without the P1/P2 licence) the
directory-role graph is still collected from active assignments, which
ride Directory.Read.All. You lose just-in-time eligibility edges, not
the roles themselves.
Create the app registration¶
- Sign in to the Microsoft Entra admin center and select the directory to inspect.
- Open Entra ID → App registrations → New registration. Name the app
LimaCharlie Cloud Security, select accounts in this directory only, and register it. - From Overview, copy Application (client) ID and Directory (tenant) ID. These identify the app and the directory in LimaCharlie.
- Open Certificates & secrets → Client secrets → New client secret. Set an expiry per your organization's policy and copy the Value immediately, not the Secret ID. Record the expiry for future rotation.
- Open API permissions → Add a permission → Microsoft Graph → Application permissions. Add Directory.Read.All and the optional permissions you need from the table above.
- Have an authorized administrator select Grant admin consent for the directory. Confirm consent is granted, then save the credential below.
See Microsoft's app registration guide. No Azure subscription or subscription Reader assignment is needed for this provider.
Use Azure Cloud Shell with Bash, or sign in to the Azure CLI with az login.
Confirm the directory with az account show, then run:
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"
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¶
In the LimaCharlie wizard, use New secret under Permissions to save the following JSON with your client ID and secret value. The command below is an alternative for CLI setup.
jq -Rs '{secret: .}' entra-secret.json \
| limacharlie secret set --key entra-sp --enabled \
&& rm -f entra-secret.json
jq -Rs builds the secret record's {"secret": "..."} envelope without
putting the credential in process arguments. The temporary file is removed only
after a successful write.
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 secret record |
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 |