Collects Microsoft Entra ID and Azure security posture metrics
epack install collector microsoft-cloud
Adds to epack.yaml, resolves dependencies, downloads binary.
Run all configured collectors and build a pack:
epack collect
Runs all collectors in epack.yaml and outputs an evidence pack.
Or add manually to epack.yaml:
collectors:
microsoft-cloud:
source: https://github.com/locktivity/epack-collector-microsoft-cloud
Then run epack install to lock and sync.
The Microsoft Cloud collector gathers Microsoft Entra ID and Azure security posture metrics for evidence packs. It is designed for continuous monitoring and posture drift detection across a Microsoft tenant and a configured set of Azure subscriptions.
The collector emits aggregate evidence only. It does not emit usernames, email addresses, raw sign-in rows, resource names, raw tags, policy bodies, tokens, secrets, or credential values.
Every successful run emits the detailed Entra artifact:
artifacts/microsoft-cloud.entra.jsonWhen normalized identity posture fields are available, the collector also emits:
artifacts/microsoft-cloud.idp-posture.jsonevidencepack/idp-posture@v1When subscription_ids are configured and at least one Azure subscription can be read, the collector also emits:
artifacts/microsoft-cloud.azure.jsonartifacts/microsoft-cloud.cloud-posture.jsonevidencepack/cloud-posture@v1The detailed schema files live in:
docs/schema/microsoft-cloud.entra.v1.0.0.jsondocs/schema/microsoft-cloud.azure.v1.0.0.jsonThe optional level config controls collection depth. Levels are cumulative.
| Level | Adds | Does not emit |
|---|---|---|
trust |
Tenant identity, MFA registration coverage, Conditional Access posture, security defaults, privileged role counts, app credential hygiene, identity secure score, Azure RBAC, Defender, Policy, storage, key vault, SQL, compute, backup, network, and logging posture. | Usernames, emails, app names, resource names, sign-in rows, raw policy bodies. |
audit |
Trust plus Azure resource inventory aggregates and ownership tag coverage. | Raw resource names, raw tag values, resource IDs. |
internal |
Audit plus aggregate Entra directory activity and sign-in monitoring counts for the last 7 days. | Raw audit events, raw sign-in rows, usernames, emails, IP addresses, device details. |
If level is omitted, the collector runs at trust.
The collector uses Microsoft Graph v1.0 only. Beta endpoints are not allowed in v1. The route contract is documented in docs/graph-endpoints.md.
Azure posture is collected through Azure Resource Manager management-plane APIs. The collector does not call Azure data-plane APIs and does not read customer content.
Some Microsoft surfaces require Entra ID P1, P2, or Entra ID Governance licensing. If a licensed surface is unavailable, the collector does not fail the whole run. It emits null for affected metrics where appropriate and records the unavailable capability in diagnostics.license.capabilities_unavailable.
Common examples:
signInActivity unavailable without the right Entra licensing.The collector is built around aggregate posture evidence. It intentionally avoids:
Subscription IDs and tenant IDs are emitted because they identify the scoped Microsoft environment for the evidence pack.
This guide walks through the Microsoft-side setup and the epack.yaml configuration for the Microsoft Cloud collector.
In the Azure portal:
epack-collector-microsoft-cloud.Record these values:
tenant_id.client_id.The collector supports two auth modes.
Use this mode for local testing or environments where OIDC is not available.
In the app registration:
Pass the secret as AZURE_CLIENT_SECRET.
Use this mode for GitHub Actions so the workflow does not store a long-lived Azure secret.
In the app registration:
For this mode, set auth_mode: oidc.
Common OIDC setup failure: the federated credential subject is too narrow. For example, a credential for the main branch will not work on pull request workflows unless a matching pull request or environment subject is also configured.
In the app registration:
Required Microsoft Graph application permissions:
| Permission | Used for |
|---|---|
Organization.Read.All |
Tenant identity and verified domains. |
User.Read.All |
User posture denominator and account status. |
AuditLog.Read.All |
MFA registration report, sign-in activity, sign-in monitoring, and directory activity. |
Policy.Read.All |
Conditional Access policies and security defaults. |
RoleManagement.Read.Directory |
Directory role assignments. |
RoleEligibilitySchedule.Read.Directory |
PIM eligibility schedules. |
Application.Read.All |
Enterprise app and app credential hygiene posture. |
SecurityEvents.Read.All |
Identity secure score. |
If a permission or license is missing for a non-blocking surface, the collector records a warning and continues.
Azure collection requires the service principal to have Azure Reader on every subscription listed in subscription_ids.
In the Azure portal:
Repeat this for each subscription in subscription_ids.
Client secret mode:
collectors:
microsoft-cloud:
source: locktivity/epack-collector-microsoft-cloud@^0.1.0
config:
tenant_id: 00000000-0000-0000-0000-000000000000
client_id: 11111111-1111-1111-1111-111111111111
auth_mode: client_secret
subscription_ids:
- 22222222-2222-2222-2222-222222222222
level: trust
secrets:
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
GitHub Actions OIDC mode:
collectors:
microsoft-cloud:
source: locktivity/epack-collector-microsoft-cloud@^0.1.0
config:
tenant_id: 00000000-0000-0000-0000-000000000000
client_id: 11111111-1111-1111-1111-111111111111
auth_mode: oidc
subscription_ids:
- 22222222-2222-2222-2222-222222222222
level: audit
| Key | Required | Description |
|---|---|---|
tenant_id |
Yes | Concrete Microsoft Entra tenant GUID. Do not use common or organizations. |
client_id |
Yes | Application client ID for the app registration. |
auth_mode |
Yes | client_secret or oidc. |
subscription_ids |
Yes | Azure subscription GUIDs to collect. |
level |
No | trust, audit, or internal. Defaults to trust. |
azure_environment |
No | Reserved for future sovereign cloud support. |
For local testing with a client secret:
export AZURE_CLIENT_SECRET="..."
cat > /tmp/microsoft-cloud-config.json <<'JSON'
{
"tenant_id": "00000000-0000-0000-0000-000000000000",
"client_id": "11111111-1111-1111-1111-111111111111",
"auth_mode": "client_secret",
"subscription_ids": ["22222222-2222-2222-2222-222222222222"],
"level": "trust"
}
JSON
EPACK_COLLECTOR_CONFIG=/tmp/microsoft-cloud-config.json ./epack-collector-microsoft-cloud
| Symptom | Likely cause | Fix |
|---|---|---|
Authorization_RequestDenied from Graph |
Missing application permission or admin consent. | Re-check API permissions and grant admin consent. |
| Azure artifact missing, with subscription errors | Service principal lacks Reader on the subscription. | Assign Azure Reader to the app service principal at the subscription scope. |
PIM metrics are null |
Missing Entra ID P2 or Entra ID Governance license. | Accept the diagnostic or enable the required license. |
MFA registration metrics are null |
MFA registration report is unavailable for the tenant or app. | Confirm AuditLog.Read.All, admin consent, and licensing. |
OIDC works on main but fails on PRs |
Federated credential subject only matches main. |
Add a federated credential for the pull request or environment subject. |
These examples show common configurations and abbreviated output shapes.
Use trust for aggregate evidence suitable for routine third-party assurance.
collectors:
microsoft-cloud:
source: locktivity/epack-collector-microsoft-cloud@^0.1.0
config:
tenant_id: 00000000-0000-0000-0000-000000000000
client_id: 11111111-1111-1111-1111-111111111111
auth_mode: client_secret
subscription_ids:
- 22222222-2222-2222-2222-222222222222
level: trust
secrets:
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
Abbreviated artifacts:
[
{
"path": "artifacts/microsoft-cloud.entra.json"
},
{
"path": "artifacts/microsoft-cloud.idp-posture.json",
"schema": "evidencepack/idp-posture@v1"
},
{
"path": "artifacts/microsoft-cloud.azure.json"
},
{
"path": "artifacts/microsoft-cloud.cloud-posture.json",
"schema": "evidencepack/cloud-posture@v1"
}
]
Use audit when you need ownership-tag and resource-inventory aggregates for Azure posture drift detection.
collectors:
microsoft-cloud:
source: locktivity/epack-collector-microsoft-cloud@^0.1.0
config:
tenant_id: 00000000-0000-0000-0000-000000000000
client_id: 11111111-1111-1111-1111-111111111111
auth_mode: client_secret
subscription_ids:
- 22222222-2222-2222-2222-222222222222
level: audit
secrets:
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
Abbreviated Azure inventory output:
{
"accounts": [
{
"account_id": "22222222-2222-2222-2222-222222222222",
"inventory": {
"resources_count": 128,
"resource_groups_count": 12,
"regions_count": 3,
"owner_tag_coverage_pct": 86,
"environment_tag_coverage_pct": 91,
"production_resources_pct": 42,
"top_resource_types": [
{
"type": "Microsoft.Compute/virtualMachines",
"count": 18
}
]
}
}
]
}
The inventory output contains resource types and counts only. It does not emit resource names, resource IDs, or raw tags.
Use internal for deeper aggregate monitoring of Entra directory activity and sign-in posture.
collectors:
microsoft-cloud:
source: locktivity/epack-collector-microsoft-cloud@^0.1.0
config:
tenant_id: 00000000-0000-0000-0000-000000000000
client_id: 11111111-1111-1111-1111-111111111111
auth_mode: oidc
subscription_ids:
- 22222222-2222-2222-2222-222222222222
level: internal
Abbreviated sign-in monitoring output:
{
"sign_in_monitoring": {
"lookback_hours": 168,
"sign_ins_count": 2400,
"failure_count": 75,
"unique_users_count": 420,
"unique_apps_count": 38,
"legacy_client_sign_ins_count": 0,
"risky_sign_ins_count": 3,
"conditional_access_success_count": 2100,
"conditional_access_failure_count": 22,
"conditional_access_not_applied_count": 278,
"applied_policies_count": 9,
"applied_policy_evaluations_count": 6100,
"applied_policy_success_count": 5800,
"applied_policy_failure_count": 22,
"applied_policy_not_applied_count": 278,
"applied_policy_report_only_fail_count": 5,
"top_failure_codes": [
{
"code": 50126,
"count": 21
}
]
}
}
This output is aggregate only. It does not emit individual sign-ins, users, IP addresses, devices, or application display names.
If a tenant lacks a required Entra license, the collector still emits the available posture and records the unavailable capability.
{
"posture": {
"mfa_coverage": null,
"denominator_enabled_member_users": 2
},
"diagnostics": {
"warnings": [
"Entra ID P1/P2 not licensed, mfa registration report reported as not_collected"
],
"license": {
"entra_tier": "unknown",
"capabilities_unavailable": [
"mfa_registration_report"
]
}
}
}
If the app registration can read Graph but lacks Azure Reader on a listed subscription, the collector emits Entra artifacts and records a subscription error. Assign Reader to the app service principal at the subscription scope and rerun.
{
"diagnostics": {
"subscription_errors": [
"22222222-2222-2222-2222-222222222222: arm request /subscriptions/[subscription_id] failed: status=403 code=AuthorizationFailed message=denied"
]
}
}
**Full Changelog**: https://github.com/locktivity/epack-collector-microsoft-cloud/compare/v0.1.0...v0.1.1