Skip to content

Repository files navigation

Azure and EntraID IAM Scanner

CI CodeQL License: MIT PowerShell 5.1+ Node.js 18+ Read-only

all-azure-access is the package and scanner command identity for Azure and EntraID IAM Scanner, a console-runnable Azure and Microsoft Entra access discovery scanner. It is designed to inventory broad access across Azure control plane, Azure data plane role assignments, Entra directory roles, group-derived access, service principal ownership, Key Vault access policies, and optional/best-effort access surfaces such as PIM, billing, reservations, and classic subscription administrators.

The scanner is intentionally resilient: each access area is collected independently, failures are recorded in the output, and the run continues wherever possible.

Safety Notes

  • The scanner performs read-only discovery through Azure CLI, Microsoft Graph, and Azure Resource Manager APIs.
  • The scanner uses your existing local az login session. It does not ask for or store passwords, client secrets, certificates, refresh tokens, or private keys.
  • Real scan outputs can contain sensitive access data, including privileged users, groups, service principals, ownership relationships, subscriptions, resources, and role assignments.
  • Do not commit real scan outputs to a public repository.
  • Real scan outputs are written outside the repository by default under the OS temp folder, for example %TEMP%\azure-scanners\all-azure-access\<timestamp>\ on Windows.
  • Bearer tokens and other secret-shaped values are never written to the logs. A support bundle contains no access or role-assignment rows unless you explicitly pass -IncludeData. See Diagnostics And Support.

Support Policy

This v1 package is Windows-first. It is tested on both Windows PowerShell 5.1 and PowerShell 7. Cross-platform Linux/macOS support is not the current compatibility target.

Primary Command

Clone the repository and install the Node dependency used for workbook generation:

git clone https://github.com/zmustafa/AzureEntraIDIAMScanner.git
cd AzureEntraIDIAMScanner
npm install

Sign in with Azure CLI, then run the scanner:

az login
powershell -ExecutionPolicy Bypass -File .\run.ps1 -ContinueOnError

A practical bounded validation run:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -LimitResources 25 -SkipReservations -ContinueOnError

Target one or more subscriptions:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -SubscriptionIds <subscription-id-1>,<subscription-id-2> -ContinueOnError

Choose a custom output root:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -OutputRoot D:\AzureScannerOutput -LimitResources 25 -SkipReservations -ContinueOnError

You can also set a default output root for your shell session:

$env:AZURE_IAM_SCANNER_OUTPUT_ROOT = 'D:\AzureScannerOutput'
powershell -ExecutionPolicy Bypass -File .\run.ps1 -LimitResources 25 -SkipReservations -ContinueOnError

What It Discovers

Microsoft Entra ID RBAC

Collects Entra directory role assignments through Microsoft Graph role management APIs.

Examples of roles represented in this surface:

  • Global Administrator
  • Privileged Role Administrator
  • User Administrator
  • Application Administrator
  • Groups Administrator
  • Security Administrator
  • Exchange Administrator
  • Teams Administrator
  • SharePoint Administrator
  • Intune Administrator
  • Compliance and security portal roles exposed through Entra role APIs

Captured details include role name, principal id/type, assignment state, directory scope, app scope when present, assignment id, and source API metadata.

Azure RBAC

Collects Azure Resource Manager role assignments across visible Azure subscriptions and scopes.

Supported scope levels include:

  • tenant root scope, best effort
  • management groups, best effort
  • subscriptions
  • resource groups
  • resources
  • selected child-resource scopes such as storage containers and file shares

Examples of Azure RBAC roles represented in this surface:

  • Owner
  • Contributor
  • Reader
  • User Access Administrator
  • Network Contributor
  • Virtual Machine Contributor
  • Key Vault Contributor
  • Storage Account Contributor
  • custom Azure roles

The scanner preserves scope, scope type, role definition id, assignment id, assignment timestamps, conditions, condition version, inherited flag when returned, subscription name/id, resource group, resource type, and resource name.

Azure Data-Plane RBAC

Azure data-plane roles are usually assigned through Azure RBAC but grant access to data inside services rather than only management of the ARM resource shell.

The scanner classifies role definitions and observed assignments as control-plane, data-plane, or mixed where possible.

Examples:

  • Storage Blob Data Reader
  • Storage Blob Data Contributor
  • Storage Blob Data Owner
  • Storage File Data SMB Share Reader
  • Storage File Data SMB Share Contributor
  • Key Vault Secrets User
  • Key Vault Crypto Officer
  • Event Hubs Data Receiver
  • Event Hubs Data Sender
  • Service Bus Data Sender
  • Service Bus Data Receiver

Storage Child Scopes

The scanner attempts child-scope discovery for common Storage RBAC data scopes, including:

  • blob containers
  • file shares

When those scopes are visible and supported by the Azure CLI in the current environment, role assignments are queried at the child scope and included in the normalized output.

Group and Nested Group Expansion

For assignments granted to Entra groups, the scanner attempts to expand transitive group membership through Microsoft Graph.

It records:

  • the assigned group
  • effective members resolved through transitive membership
  • access path as GroupTransitive
  • group chain text where available
  • source group id/name

This lets the report answer both questions:

  • Who was directly assigned?
  • Who effectively receives access through group membership?

Every assignment is multiplied by every member, so one very large group can dominate the output. Use these flags to bound or skip expansion:

-NoGroupExpansion
-MaxGroupDepth <1-10>
-MaxGroupMembers <int>

-MaxGroupDepth defaults to 10, meaning no limit, which Graph answers in a single transitiveMembers call per group. A lower value walks direct members level by level instead. -MaxGroupMembers stops expanding a group after the given number of members and records the truncation in errorsWarnings.csv.

Service Principal and Application Ownership

For service principals observed in access assignments, the scanner attempts to retrieve service principal owners through Microsoft Graph.

Owner rows are emitted as effective ownership access because owners may be able to control credentials, federated credentials, or operational identity configuration depending on tenant policy.

Output includes:

  • service principal id/name
  • owner principal id/type/name
  • owner UPN when available
  • access path Owner
  • assignment type Owner

Key Vault Access Policies

The scanner handles Key Vault legacy access policies separately from Azure RBAC.

This matters because older Key Vaults may still grant data-plane permissions using access policies rather than Azure RBAC roles.

Captured details include:

  • vault scope
  • policy object id
  • serialized key/secret/certificate/storage permissions
  • subscription/resource group/vault metadata

PIM / Eligible Access

The scanner attempts to collect eligible assignments where permitted.

Best-effort collectors include:

  • Entra directory role eligibility through Graph
  • Azure role eligibility through ARM authorization APIs

PIM is not treated as a separate permission model. Rows are marked with assignment states such as Eligible so the workbook can distinguish active access from eligible/JIT access.

Billing, Reservations, and Cost-Adjacent Access

Billing and reservation APIs often require separate permissions. These collectors are best-effort and record status rather than blocking the run.

Best-effort surfaces include:

  • billing accounts
  • billing scopes
  • reservation orders
  • reservation scopes
  • role assignments at billing/reservation scopes where the Azure CLI/API supports them

For console stability, reservation collection can be skipped:

-SkipReservations

Classic Subscription Administrators

The scanner attempts to include classic administrator style assignments where the Azure CLI returns them.

Examples:

  • Account Administrator
  • Service Administrator
  • Co-Administrator

Classic admin rows are labeled with the ClassicAzureAdmin access model.

Failure Handling

The scanner is built around independent collectors. If one collector fails, the failure is recorded and the scan continues.

Collector statuses include:

  • Succeeded
  • Skipped
  • Failed
  • Unauthorized
  • Throttled
  • PartiallyCollected

Exit codes:

  • 0: completed successfully
  • 2: completed with warnings, partial failures, or unauthorized optional collectors
  • 1: startup-level failure, such as missing Azure CLI, no login, or output folder failure

Errors and warnings are written to:

output\collectorStatus.csv
output\errorsWarnings.csv
output\coverageSummary.json
errors\<collector>.json
logs\events.jsonl
logs\errorSummary.json
logs\all-azure-access.log

collectorStatus.csv, errorsWarnings.csv, coverageSummary.json, and counts.json are written even when the run is interrupted with Ctrl+C or fails partway through. The event log is flushed as it is written, so it stays readable even if the process is killed outright.

Repeated failures are collapsed: a failure affecting 50,000 scopes produces five examples plus a count in logs\errorSummary.json, not 50,000 log lines.

Output Location

Each run writes beneath:

%TEMP%\azure-scanners\all-azure-access\<timestamp>\

The run folder contains:

output\
logs\
errors\
cache\
raw\
reports\
support-bundle-<timestamp>.zip

support-bundle-<timestamp>.zip is present when the run was started with -SupportBundle, or whenever the run did not succeed.

Main Output Files

output\allAzureAccess.csv
output\effectiveAccess.csv
output\directAssignments.csv
output\groupDerivedAccess.csv
output\nestedGroupAccess.csv
output\servicePrincipalOwners.csv
output\principalResolution.csv
output\groupExpansion.csv
output\roleDefinitions.csv
output\scopeInventory.csv
output\collectorStatus.csv
output\coverageSummary.json
output\counts.json
output\errorsWarnings.csv
output\results.json
output\results.zip
reports\all-azure-access-report-<timestamp>.xlsx
reports\all-azure-access-summary-<timestamp>.json

output\allAzureAccess.json is written only when -EmitJson is supplied. It contains the same data as allAzureAccess.csv, and serialising it is slow and memory-hungry in large tenants.

Diagnostic files are written under logs\:

logs\events.jsonl
logs\errorSummary.json
logs\apiTiming.csv
logs\apiSummary.json
logs\environment.json
logs\parameters.json
logs\all-azure-access.log

Workbook Sheets

The generated workbook is designed for review and pivot-style discovery.

Sheets include:

  • Executive Summary
  • Collector Status
  • Coverage
  • All Access Raw
  • Effective Access
  • Privileged Access
  • Data Plane Access
  • Service Principal Owners
  • Group Access
  • Entra Roles
  • Azure RBAC
  • Management Groups
  • Subscriptions
  • Resource Groups
  • Resources
  • Storage Child Scopes
  • Key Vault Access
  • PIM
  • Billing Reservations
  • Classic Admins
  • Role Definitions
  • Principal Directory
  • Errors Warnings
  • Source Metadata
  • Pivots

Pivot-Style Summaries

The workbook includes precomputed summary sections such as:

  • Access by Surface
  • Access by Role
  • Access by Principal Type
  • Access by Principal
  • Access by Subscription
  • Access by Management Group
  • Access by Scope Type
  • Privileged Roles by Principal
  • Data Plane Roles by Resource Type
  • Group-Derived Access by Group
  • PIM Eligible vs Active
  • Failures by Collector
  • Resources Discovered by Type

Normalized Access Columns

The primary normalized output uses a wide schema so different access models can be compared in one workbook.

Important columns include:

surface
accessModel
collector
assignmentState
assignmentType
principalId
principalType
principalDisplayName
principalUserPrincipalName
principalAppId
effectivePrincipalId
effectivePrincipalType
effectivePrincipalName
effectivePrincipalUserPrincipalName
accessPath
groupChain
sourceGroupId
sourceGroupName
roleName
roleDefinitionId
roleCategory
roleIsPrivileged
roleHasDataActions
scope
scopeType
scopeDisplayName
tenantId
managementGroupId
managementGroupName
subscriptionId
subscriptionName
resourceGroup
resourceType
resourceName
childResourceType
childResourceName
assignmentId
assignmentCreatedOn
assignmentUpdatedOn
condition
conditionVersion
isInherited
sourceApi
collectionStatus
errorCode
errorMessage

Useful Flags

For collectors that have both, -Include* forces the collector on, -Skip* forces it off, and -Include* wins if both are supplied. Every collector listed below is on by default.

-SubscriptionIds <string[]>

Limit scan to specific subscriptions.

-ManagementGroupIds <string[]>

Limit management group RBAC collection to specific management groups.

-ResourceTypes <string[]>

Limit resource inventory to specific ARM resource types.

-LimitResources <int>

Limit resources per subscription during resource-level discovery. Useful for validation runs.

-SkipResourceLevel

Skip resource-level role assignment checks.

-SkipChildDataScopes

Skip storage child-scope discovery such as blob containers and file shares.

-SkipPim

Skip PIM eligibility collectors.

-SkipBilling

Skip billing collectors.

-SkipReservations

Skip reservation collectors. Recommended for faster console runs unless reservation access is explicitly needed.

-SkipClassicAdmins

Skip classic subscription administrator checks.

-SkipKeyVaultAccessPolicies

Skip legacy Key Vault access policy collection.

-NoGroupExpansion

Do not expand group assignments into effective transitive user/service principal rows.

-FullRoleDefinitionScan

Collect role definitions from every visible subscription and management group. Without this flag, the scanner collects role definitions from a responsive subscription and infers metadata from observed assignments to keep console runs fast.

-IncludeOtherTenants

Also scan subscriptions homed in a tenant other than the signed-in one. These are skipped by default because token acquisition fails for them (AADSTS50020); skipped subscriptions are listed in errorsWarnings.csv.

-DeepScopeScan

Query every resource group, resource, and storage child scope individually instead of relying on the subscription-wide enumeration that already returns assignments at all nested scopes. This is exhaustive but issues one Azure CLI call per scope, so it is very slow in large tenants.

-Parallel <int>

Number of Azure CLI processes to run concurrently for per-subscription work (subscription RBAC, scope inventory, Key Vault policies, classic admins, storage child scopes). Defaults to 1. Values of 4 to 8 are a good starting point; going wider risks ARM throttling, which costs more time than it saves.

-MaxGroupDepth <1-10>

How many levels of nested group membership to expand. The default 10 means "no limit" and is answered by a single Graph transitiveMembers call per group. Any lower value walks direct members level by level, which is slower but bounds the expansion.

-MaxGroupMembers <int>

Stop expanding a group after this many members (0 = unlimited). Because every assignment is multiplied by every member, one very large group can dominate the entire output. Truncated groups are recorded in errorsWarnings.csv.

-EmitJson

Also write allAzureAccess.json. Off by default because serialising every row is slow and memory-hungry in large tenants; the CSVs contain the same data.

-NoHttp

Disable direct HTTPS calls and route every Graph/ARM request through az rest instead. Much slower; use only if the in-process HTTP path is blocked by your environment.

-ContinueOnError

Continue on collector failures. This is the expected operating mode for broad tenant discovery.

-FailFast

Stop on the first non-optional collector failure.

-OutputRoot <path>

Write run outputs beneath a custom root. If omitted, the scanner uses AZURE_IAM_SCANNER_OUTPUT_ROOT when set, otherwise the OS temp folder plus azure-scanners.

Console output

-Quiet
-NoColor
-NoProgress

Suppress the progress display, colour, or the progress bar. Colour and progress are disabled automatically when output is redirected or when CI, TF_BUILD, or GITHUB_ACTIONS is set, and NO_COLOR is honoured. The machine-readable RUN_DIR=, ALL_ACCESS_CSV=, FINAL_STATUS= lines are never coloured or suppressed.

The completion estimate is weighted by measured collector cost rather than by collector count, and each run writes timings to <OutputRoot>\.calibration\all-azure-access.json. From the second run against the same environment onward, the percentage and ETA are based on your own measured timings.

Diagnostics

-LogLevel Error|Warn|Info|Debug|Trace

Defaults to Info. Debug records every Azure CLI and HTTP call with its duration, exit code, and response size.

-RedactLogs

Replace user principal names, GUIDs, and resource names in the logs with stable pseudonyms (user-xxxxxxxx@redacted, id-xxxxxxxx, name-xxxxxxxx). Identical values always map to identical pseudonyms, so redacted logs remain correlatable. The reversal map is written to logs\redaction-map.LOCAL-ONLY.json and is never included in a support bundle.

Bearer tokens, JWTs, and secret-shaped values are always stripped, regardless of this switch.

-SupportBundle
-IncludeData

Produce support-bundle-<runid>.zip in the run directory. A bundle is created automatically whenever the run does not succeed. It contains the event log, error summary, API timings, environment details, the exact parameters used, collector status, and row counts. It contains no access or role-assignment rows unless -IncludeData is specified.

-NoTranscript
-MaxLogMegabytes <int>

Skip the PowerShell transcript, or change the event-log size cap (default 50 MB).

Diagnostics And Support

Every run writes the following under logs\:

File Contents
events.jsonl One JSON object per line. Repeated failures are collapsed to five examples plus a count, so a failure affecting 50,000 scopes produces a handful of lines rather than 50,000.
errorSummary.json Every distinct failure class with counts, first/last occurrence, and examples.
apiTiming.csv Per-call timing for every Azure CLI and HTTP request.
apiSummary.json Call counts and durations per API, plus the 50 slowest calls.
environment.json OS, PowerShell, Azure CLI (and extensions), and Node versions.
parameters.json The exact parameters the run was started with.

collectorStatus.csv, errorsWarnings.csv, coverageSummary.json, and counts.json are written even if the run is interrupted with Ctrl+C or fails partway through.

To report a problem, re-run with -LogLevel Debug -RedactLogs -SupportBundle and send the resulting support-bundle-<runid>.zip.

Recommended Run Modes

Fast validation:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -LimitResources 25 -SkipReservations -ContinueOnError

Single subscription validation:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -SubscriptionIds <subscription-id> -LimitResources 10 -SkipReservations -ContinueOnError

Broader tenant run:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -SkipReservations -ContinueOnError

Deep run with full role definition coverage:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -FullRoleDefinitionScan -ContinueOnError

Large tenant:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -Parallel 8 -MaxGroupMembers 5000 -SkipReservations -SkipBilling -ContinueOnError

Reproducing a problem for support:

powershell -ExecutionPolicy Bypass -File .\run.ps1 -LogLevel Debug -RedactLogs -SupportBundle -ContinueOnError

Requirements

  • Windows PowerShell 5.1 or PowerShell 7; Windows is the supported v1 operating system
  • Git, to clone the repository
  • Azure CLI available as az
  • Valid az login
  • Sufficient Azure RBAC permissions to list assignments and resources
  • Microsoft Graph permissions available through the signed-in Azure CLI session for Entra role, principal, group, and owner lookups
  • Node.js 18+ for XLSX workbook generation through report.mjs
  • Local Node dependency install with npm install, which installs exceljs from package.json

Validate The Package

Clone the repository and run the built-in package validation from the repository root:

git clone https://github.com/zmustafa/AzureEntraIDIAMScanner.git
cd AzureEntraIDIAMScanner
npm run validate

The validation script parses every PowerShell file, verifies that no tokens, user principal names, GUIDs, or resource names can leak into a shareable log file, installs npm dependencies when needed, regenerates the dummy sample workbook, and verifies the sample summary contains rows. It needs no Azure connection.

Sample Workbook With Dummy Data

A dummy-only sample workbook is provided at:

samples\all-azure-access-sample-ALL-dummy.xlsx

The sample workbook is generated only from synthetic inputs under:

samples\dummy-input\

The sample uses fake IDs, fake subscriptions, and example.invalid identities. It does not contain real tenant data.

To regenerate the sample workbook from the dummy inputs:

git clone https://github.com/zmustafa/AzureEntraIDIAMScanner.git
cd AzureEntraIDIAMScanner
npm install

node .\report.mjs `
  --run-dir .\samples\dummy-input `
  --access-csv .\samples\dummy-input\allAzureAccess.csv `
  --effective-csv .\samples\dummy-input\effectiveAccess.csv `
  --direct-csv .\samples\dummy-input\directAssignments.csv `
  --group-csv .\samples\dummy-input\groupDerivedAccess.csv `
  --service-principal-owners-csv .\samples\dummy-input\servicePrincipalOwners.csv `
  --collector-status-csv .\samples\dummy-input\collectorStatus.csv `
  --scope-inventory-csv .\samples\dummy-input\scopeInventory.csv `
  --role-definitions-csv .\samples\dummy-input\roleDefinitions.csv `
  --principal-resolution-csv .\samples\dummy-input\principalResolution.csv `
  --errors-csv .\samples\dummy-input\errorsWarnings.csv `
  --coverage-json .\samples\dummy-input\coverageSummary.json `
  --output .\samples\all-azure-access-sample-ALL-dummy.xlsx `
  --summary-json .\samples\all-azure-access-sample-ALL-dummy-summary.json

Current Limitations

  • Workload-specific access models such as Exchange RBAC, SharePoint item permissions, Teams membership, Intune RBAC, Azure DevOps permissions, SQL database permissions, and AKS Kubernetes RBAC are not fully implemented in this scanner. They require separate workload APIs and data models.
  • Some billing, reservation, PIM, and management group APIs may return no data or unauthorized responses depending on the signed-in account.
  • Large tenants take time. Use -Parallel, and narrow the run with -LimitResources, -ResourceTypes, -SkipResourceLevel, or -SkipChildDataScopes. -DeepScopeScan is exhaustive but issues one Azure CLI call per scope and is very slow.
  • By default, resource groups and resources are not queried individually, because the subscription-wide enumeration already returns assignments at every nested scope. -DeepScopeScan restores per-scope queries if you need to verify this.
  • Effective access is expanded for groups where Graph transitive membership is available. If Graph denies membership reads, direct assignment rows still remain in the report and the collector status records the issue.
  • Excel caps a worksheet at 1,048,576 rows. report.mjs keeps the first --max-sheet-rows rows per sheet (250,000 by default) and records any truncation in the report summary; the CSV outputs are always complete.
  • Subscriptions homed in another tenant are skipped by default, because a token cannot be acquired for them. Use -IncludeOtherTenants to attempt them anyway.
  • v1 is Windows-first. Cross-platform PowerShell support may require additional testing and small process-launch changes.

About

Read-only Azure & Microsoft Entra ID access discovery scanner. Inventories RBAC, directory roles, group-derived access, service principal ownership, Key Vault policies, PIM, and more — with CSV, JSON & XLSX reporting.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages