entra_id
Identity and directory management for Microsoft Entra ID (formerly Azure Active Directory), derived from the Microsoft Graph v1.0 API.
total services: 39
total resources: 849
See also:
[SHOW] [DESCRIBE] [REGISTRY]
Installation
To pull the latest version of the entra_id provider, run the following command:
REGISTRY PULL entra_id;
To view previous provider versions or to pull a specific provider version, see here.
Authentication
The entra_id provider authenticates to Microsoft Graph using the OAuth2 client credentials (app-only) grant. Register an application in Microsoft Entra ID, grant it the required Microsoft Graph application permissions (and admin-consent them), then create a client secret.
The following system environment variables are used for authentication by default:
AZURE_TENANT_ID- your Entra ID tenant ID (GUID or verified domain), used in the token endpointAZURE_CLIENT_ID- the application (client) ID of your app registrationAZURE_CLIENT_SECRET- a client secret for the app registration
These variables are sourced at runtime (from the local machine or as CI variables/secrets).
Using different environment variables
To use different environment variables (instead of the defaults), use the --auth flag of the stackql program. For example:
AUTH='{ "entra_id": { "type": "oauth2", "grant_type": "client_credentials", "client_id_env_var": "MY_CLIENT_ID", "client_secret_env_var": "MY_CLIENT_SECRET", "token_url": "https://login.microsoftonline.com/{{ .__env__MY_TENANT_ID }}/oauth2/v2.0/token", "scopes": ["https://graph.microsoft.com/.default"] }}'
stackql shell --auth="${AUTH}"
Example Queries
Try the following queries using stackql shell, or run them from a script or CI pipeline with stackql exec.
Users with account status and UPN
Every user in the tenant with its sign-in status, user type and organisational attributes, sorted by name:
SELECT id, displayName, userPrincipalName, accountEnabled, userType,
mail, jobTitle, department, createdDateTime
FROM entra_id.users.users
ORDER BY displayName;
Groups and their members
All groups with their type, visibility and dynamic membership rule, then the direct user members of one group (the members resource returns directory object IDs, so the join to users supplies the names):
SELECT id, displayName, mailEnabled, securityEnabled, groupTypes,
membershipRule, visibility, createdDateTime
FROM entra_id.groups.groups;
SELECT u.displayName, u.userPrincipalName, u.mail
FROM entra_id.groups.members m
JOIN entra_id.users.users u ON u.id = m.id
WHERE m.group_id = '{{ group_id }}';
App registrations with credentials
App registrations that hold at least one client secret or certificate, with the number of each and the expiry of the first listed secret:
SELECT displayName, appId, signInAudience,
json_array_length(passwordCredentials) AS secret_count,
json_extract(passwordCredentials, '$[0].endDateTime') AS first_secret_expiry,
json_array_length(keyCredentials) AS certificate_count
FROM entra_id.applications.applications
WHERE json_array_length(passwordCredentials) > 0
OR json_array_length(keyCredentials) > 0;
Service principals
Enterprise applications, managed identities and other service principals in the tenant, with the principal type and whether sign-in is enabled:
SELECT id, appId, displayName, servicePrincipalType, accountEnabled,
appOwnerOrganizationId, appRoleAssignmentRequired
FROM entra_id.service_principals.service_principals;
Directory roles and their members
The directory roles activated in the tenant, then the users that hold one of them:
SELECT id, displayName, description, roleTemplateId
FROM entra_id.directory_roles.directory_roles;
SELECT u.displayName, u.userPrincipalName, u.mail
FROM entra_id.directory_roles.members m
JOIN entra_id.users.users u ON u.id = m.id
WHERE m.directory_role_id = '{{ directory_role_id }}';
Conditional access policies
Enabled policies that require multifactor authentication for all users, with the applications each one covers:
SELECT displayName, state,
json_extract(conditions, '$.applications.includeApplications') AS include_applications,
json_extract(grantControls, '$.builtInControls') AS built_in_controls
FROM entra_id.identity.conditional_access_policies
WHERE state = 'enabled'
AND json_extract(conditions, '$.users.includeUsers') LIKE '%All%'
AND json_extract(grantControls, '$.builtInControls') LIKE '%mfa%';
Failed sign-ins for one user
Sign-ins by one user that did not succeed, newest first, with the application, client, conditional access outcome and failure reason (the user predicate is pushed to Graph as $filter and the sort as $orderby):
SELECT createdDateTime, appDisplayName, clientAppUsed, ipAddress,
conditionalAccessStatus,
json_extract(status, '$.errorCode') AS error_code,
json_extract(status, '$.failureReason') AS failure_reason
FROM entra_id.audit_logs.sign_ins
WHERE userPrincipalName = 'alice@example.com'
AND json_extract(status, '$.errorCode') <> 0
ORDER BY createdDateTime DESC;
Guest users
External (B2B) accounts in the tenant with their invitation state (the predicate is pushed to Graph as $filter=userType eq 'Guest'):
SELECT id, displayName, userPrincipalName, mail, externalUserState, createdDateTime
FROM entra_id.users.users
WHERE userType = 'Guest';
Device posture by operating system
Registered devices counted by operating system, compliance state and management state:
SELECT operatingSystem, isCompliant, isManaged, COUNT(*) AS device_count
FROM entra_id.devices.devices
GROUP BY operatingSystem, isCompliant, isManaged
ORDER BY device_count DESC;
Group provisioning
Create a security group, add a member to it (relationship writes take the target object ID as directoryObjectId), update its description and finally delete it:
INSERT INTO entra_id.groups.groups (displayName, mailNickname, mailEnabled, securityEnabled, description)
SELECT 'Platform Engineering', 'platform-engineering', false, true, 'Platform engineering team';
INSERT INTO entra_id.groups.members (group_id, directoryObjectId)
SELECT '{{ group_id }}', '{{ directoryObjectId }}';
UPDATE entra_id.groups.groups
SET description = 'Platform engineering team, owned by the CTO office'
WHERE group_id = '{{ group_id }}';
DELETE FROM entra_id.groups.groups
WHERE group_id = '{{ group_id }}';
Services
agreements
application_templates
applications
audit_logs
authentication_method_configurations
authentication_methods_policy
certificate_based_auth_configuration
contracts
data_policy_operations
devices
directory
directory_objects
directory_role_templates
directory_roles
domain_dns_records
domains
group_lifecycle_policies
group_setting_templates
group_settings