A validation harness that re-checks Prowler's Azure findings with independent
evidence so you can separate real issues from false positives. It mirrors the
gcp_validator design: for every Prowler check it runs up to five methods and
reports each verdict side-by-side plus a consensus and a false-positive /
false-negative flag.
| # | Method | What it does |
|---|---|---|
| 1 | API | A direct ARM read of the exact property (via Resource Graph / ARM REST). |
| 2 | Prowler replica | Re-implements Prowler's exact pass/fail rule, linked to the Prowler Hub page. |
| 3 | Logs | Corroborates from Azure Monitor Activity Logs (recent write operations on the resource). |
| 4 | Alternate | A different signal (e.g. networkAcls.defaultAction=Deny vs publicNetworkAccess=Disabled). |
| 5 | Exposure | For public-exposure checks only: attempts to reach the resource from the internet. |
A method that doesn't apply returns N/A with a reason instead of a fabricated verdict.
Consensus (excluding method 2) is compared to the Prowler finding →
AGREE / LIKELY_FALSE_POSITIVE / LIKELY_FALSE_NEGATIVE / INCONCLUSIVE.
cd azure_validator
python -m venv .venv && . .venv/bin/activate # (Windows: .venv\Scripts\activate)
pip install -r requirements.txtThe tool only needs read access. Credentials are resolved in this order:
# 1. Explicit service principal (flags or env vars):
python run_all.py -s SUB --tenant-id T --client-id C --client-secret S --prowler out.json
# ...or a certificate instead of a secret:
python run_all.py -s SUB --tenant-id T --client-id C --cert-path /path/sp.pem
# ...or the standard env vars:
export AZURE_TENANT_ID=... AZURE_CLIENT_ID=... AZURE_CLIENT_SECRET=...
# 2. Otherwise DefaultAzureCredential (managed identity, az login, etc.):
az login
export AZURE_SUBSCRIPTION_ID=<sub-guid>Recommended roles: Reader + Reader and Data Access, plus Monitoring
Reader (method 3). Entra checks additionally need Microsoft Graph read
permissions on the principal (e.g. Directory.Read.All, Policy.Read.All).
# All implemented checks, compared to a Prowler OCSF/CSV export:
python run_all.py --subscription SUB_ID --prowler prowler-output.ocsf.json -o report.xlsx
# Only Storage:
python run_all.py -s SUB_ID --service storage --prowler out.ocsf.json
# Enable the method-5 internet probe (makes real outbound connections):
python run_all.py -s SUB_ID --service storage --enable-exposure-probe
# List what's implemented:
python run_all.py --list
# Run ONE control by itself (from the azure_validator directory):
python -m checks.storage.storage_account_public_network_access_disabled -s SUB_ID --prowler out.ocsf.json--prowler accepts Prowler's OCSF JSON or CSV. Without it, the tool
validates live Azure state and reports each method's verdict.
A .xlsx with a Validation sheet (one row per resource per check) and a
Summary sheet, identical in shape to the GCP tool. LIKELY_FALSE_POSITIVE
rows are highlighted.
Add --evidence-dir DIR (alias --screenshots DIR) to write a per-finding PNG
evidence card — the check, resource, every method's PASS/FAIL, the actual
config values read from the API, a UTC capture timestamp, and an Azure Portal
deep-link. The Excel also gains a hyperlinked Console Link column.
python run_all.py -s SUB --prowler out.ocsf.json --evidence-dir ./evidence
# only the exceptions worth attaching to findings:
python run_all.py -s SUB --prowler out.ocsf.json --evidence-dir ./evidence --evidence-findings-onlyImages are generated headless (Pillow) from data the tool already collected, so they carry provenance (API source + timestamp) suitable as audit evidence; the deep-link lets you also grab a live Portal screenshot if a reviewer wants one.
All 191 Azure checks across 21 services are implemented.
| Service | n | Service | n | Service | n |
|---|---|---|---|---|---|
| defender | 26 | app | 20 | entra | 19 |
| storage | 19 | monitor | 15 | vm | 12 |
| sqlserver | 12 | network | 11 | postgresql | 10 |
| keyvault | 10 | aks | 8 | cosmosdb | 7 |
| mysql | 6 | databricks | 4 | containerregistry | 3 |
| iam | 3 | recovery | 2 | apim/aisearch/policy/appinsights | 1 each |
Discovery uses Azure Resource Graph (one KQL endpoint returning full
resource properties), so most property checks are a few lines; sub-resource
settings (blob/file service, SQL auditing, App Service config, flexible-server
parameters) are fetched via ARM REST on demand; Entra uses Microsoft Graph.
Some controls can't be proven from a single read-only ARM/Graph property, so
they run what they can and mark the rest MANUAL with a reason rather than
guessing. These include: Defender assessment-based checks (endpoint protection,
system updates, container image scan/vulns), vm_backup_* / vm_jit_access /
recovery_vault_* (Recovery Services data-plane), runtime-version "is latest"
checks (app_*_version_is_latest, app_function_latest_runtime_version),
function app-settings checks (App Insights, access keys, identity roles), NSG
flow-log checks, apim_threat_detection_llm_jacking, and the Entra
MFA / Conditional-Access / sign-in checks. Each check's docstring states its
exact predicate and Hub link — worth a review against your Prowler version.
- Create
checks/<service>/<check_id>.py. - Subclass
azval.resource_check.ResourceCheck(or a service base), setcheck_id, implementdiscover_resources()+evaluate()(and any of the five methods that apply). run_all.pydiscovers it automatically;python -m checks.<service>.<check_id>runs it alone.