Azure RBAC compliance monitoring — detect permission drift and governance violations
Project description
az-rbac-watch
CLI for Azure RBAC drift detection, governance guardrails, and change tracking.
What it does
Six focused commands in one tool:
discover— generate a baseline policy YAML from current RBAC assignmentsscan— detect drift from your declared baselineaudit— detect forbidden patterns and governance violationsvalidate— check policy syntax offlinesnapshot— capture current RBAC state as JSONdiff— compare snapshots to track changes over time
Neither OPA nor Azure Policy can natively scan Azure RBAC assignments. az-rbac-watch fills that gap with a CLI designed for local use, scheduled checks, and CI/CD pipelines.
Installation
# One-liner bootstrap (installs uv if needed, then puts az-rbac-watch on your PATH)
curl -fsSL https://raw.githubusercontent.com/maxvanp/az-rbac-watch/main/scripts/install.sh | sh
Manual install with uv:
uv tool install az-rbac-watch
uv tool update-shell
Fallback with pipx:
pipx install az-rbac-watch
The bootstrap script can install a compatible Python automatically through uv. You still need Azure CLI for authentication.
Shell completion
Enable tab completion for bash, zsh, or fish:
az-rbac-watch --install-completion
Restart your shell after installation.
Quick start
1. Authenticate
az login
2. Discover existing assignments
# Single subscription
az-rbac-watch discover -t <tenant-id> -s <subscription-id> -o my_policy.yaml
# All accessible scopes
az-rbac-watch discover -t <tenant-id> -o my_policy.yaml
3. Scan for drift (RBAC as Code)
The scan command compares actual RBAC assignments against your baseline rules. Any assignment not covered by a baseline rule is reported as DRIFT.
# Console output
az-rbac-watch scan -p my_policy.yaml
# HTML report
az-rbac-watch scan -p my_policy.yaml -o report.html
# JSON (CI/CD)
az-rbac-watch scan -p my_policy.yaml --format json
4. Audit guardrails (Policy as Code)
The audit command checks governance rules (forbidden patterns). Any assignment matching a governance rule is reported as a violation.
# Console output
az-rbac-watch audit -p my_policy.yaml
# HTML report
az-rbac-watch audit -p my_policy.yaml -o report.html
# JSON (CI/CD)
az-rbac-watch audit -p my_policy.yaml --format json
A starter kit with common governance rules is available in examples/deny_rules_starter.yaml.
4b. Framework compliance (CIS Benchmark)
# Audit with CIS Azure Foundations Benchmark mapping
az-rbac-watch audit -p my_policy.yaml --framework CIS -o compliance.html
# Console summary only
az-rbac-watch audit -p my_policy.yaml --framework CIS
5. Validate policy syntax (offline)
az-rbac-watch validate -p my_policy.yaml
6. Capture RBAC snapshots
# From a policy file
az-rbac-watch snapshot -p my_policy.yaml -o snapshot_2026-03-09.json
# From explicit scopes
az-rbac-watch snapshot -t <tenant-id> -s <subscription-id> -o snapshot.json
7. Compare snapshots (change tracking)
# Console output
az-rbac-watch diff snapshot_old.json snapshot_new.json
# JSON (CI/CD)
az-rbac-watch diff snapshot_old.json snapshot_new.json --format json -o changes.json
Core workflow
Use discover once to capture your current baseline, then run scan and audit continuously. Add snapshot and diff when you want explicit change tracking between two points in time.
Two axes, one tool
| Axis | Command | Rule type | Finding | Question answered |
|---|---|---|---|---|
| RBAC as Code | scan |
baseline |
DRIFT |
"Is there something I didn't declare?" |
| Policy as Code | audit |
governance |
GOVERNANCE_VIOLATION |
"Is there something forbidden?" |
| Change tracking | snapshot + diff |
n/a | Added / Removed / Modified | "What changed since last time?" |
| Framework compliance | audit --framework |
governance |
GOVERNANCE_VIOLATION + score |
"How do we score against CIS?" |
You can use both in the same policy file. Each command focuses on its rule type and ignores the other.
Scope modes
The policy model supports two scope modes:
scope: explicit(default) — only scans subscriptions and management groups listed in the YAMLscope: all— auto-discovers all accessible scopes at scan time, with optional exclusions
scope: all
exclude_subscriptions:
- "22222222-2222-2222-2222-222222222222"
exclude_management_groups:
- "mg-sandbox"
CLI exclusions (--exclude-subscription, --exclude-management-group) apply on top of YAML exclusions.
CLI reference
az-rbac-watch scan
Detects RBAC drift — compares actual state against baseline rules.
| Option | Description |
|---|---|
-p, --policy PATH |
Policy model YAML (required) |
-t, --tenant-id ID |
Tenant ID (ad-hoc mode) |
-s, --subscription ID |
Subscription to scan, repeatable (ad-hoc mode) |
-m, --management-group ID |
Management group to scan, repeatable (ad-hoc mode) |
-o, --output PATH |
HTML report output path |
-f, --format FORMAT |
console (default) or json |
--orphans-only |
Scan only for orphaned assignments (requires --tenant-id) |
--dry-run |
Show scan plan without making API calls |
--exclude-subscription ID |
Exclude subscription (repeatable) |
--exclude-management-group ID |
Exclude management group (repeatable) |
-v, --verbose |
Debug logging |
--debug |
Show full traceback on error |
az-rbac-watch audit
Checks governance guardrails — evaluates governance rules against actual state.
| Option | Description |
|---|---|
-p, --policy PATH |
Policy model YAML (required) |
-t, --tenant-id ID |
Tenant ID (ad-hoc mode) |
-s, --subscription ID |
Subscription to scan, repeatable (ad-hoc mode) |
-m, --management-group ID |
Management group to scan, repeatable (ad-hoc mode) |
-o, --output PATH |
HTML report output path |
-f, --format FORMAT |
console (default) or json |
--dry-run |
Show scan plan without making API calls |
--framework NAME |
Map findings to compliance framework (e.g. 'CIS' or path to YAML) |
--exclude-subscription ID |
Exclude subscription (repeatable) |
--exclude-management-group ID |
Exclude management group (repeatable) |
-v, --verbose |
Debug logging |
--debug |
Show full traceback on error |
az-rbac-watch discover
| Option | Description |
|---|---|
-t, --tenant-id ID |
Tenant ID |
-s, --subscription ID |
Subscription to scan (repeatable) |
-m, --management-group ID |
Management group to scan (repeatable) |
-o, --output PATH |
Output YAML (default: discovered_policy.yaml) |
az-rbac-watch validate
| Option | Description |
|---|---|
-p, --policy PATH |
Policy model YAML to validate (required) |
az-rbac-watch snapshot
Captures a full RBAC snapshot (assignments + role definitions) as JSON.
| Option | Description |
|---|---|
-p, --policy PATH |
Policy model YAML (uses its scopes) |
-t, --tenant-id ID |
Tenant ID |
-s, --subscription ID |
Subscription to scan (repeatable) |
-m, --management-group ID |
Management group to scan (repeatable) |
--exclude-subscription ID |
Exclude subscription (repeatable) |
--exclude-management-group ID |
Exclude management group (repeatable) |
-o, --output PATH |
Output JSON file (required) |
-v, --verbose |
Debug logging |
--debug |
Show full traceback on error |
az-rbac-watch diff
Compares two snapshots and shows RBAC changes (added, removed, modified assignments).
| Option | Description |
|---|---|
OLD_SNAPSHOT |
Path to the older snapshot JSON file (required) |
NEW_SNAPSHOT |
Path to the newer snapshot JSON file (required) |
-f, --format FORMAT |
console (default), json, or html |
-o, --output PATH |
Output file path |
HTML format is auto-detected when --output ends with .html.
Exit codes
| Code | Meaning |
|---|---|
0 |
Compliant — no findings detected |
1 |
Non-compliant — findings detected |
2 |
Error — authentication failure, API error, invalid YAML |
The diff command returns 0 for no changes, 1 for changes detected.
Example CI/CD usage:
# Check drift
az-rbac-watch scan -p policy.yaml --format json -o drift.json
scan_exit=$?
# Check guardrails
az-rbac-watch audit -p policy.yaml --format json -o audit.json
audit_exit=$?
if [ "$scan_exit" -eq 1 ] || [ "$audit_exit" -eq 1 ]; then
echo "Non-compliant — review reports"
elif [ "$scan_exit" -eq 2 ] || [ "$audit_exit" -eq 2 ]; then
echo "Scan error — check credentials and permissions"
fi
Troubleshooting
Authentication error
- Run
az loginto refresh your credentials - Verify
DefaultAzureCredentialcan authenticate (checkAZURE_*env vars or managed identity)
Access denied
- The scanning principal needs
Microsoft.Authorization/roleAssignments/readon each scoped subscription/MG - Check with:
az role assignment list --scope /subscriptions/<id> --assignee <principal-id>
Names not resolved (UUIDs shown)
- The App Registration (or user) needs
Directory.Read.Allpermission on Microsoft Graph - Run with
--verboseto see Graph API errors in the logs
Throttling
- Azure ARM API rate-limits parallel requests
- Reduce parallelism: the default is 4 workers (configurable in code via
max_workers) - Wait a few minutes and retry
Full traceback
- Use
--debugto see the complete Python traceback on any error
GitHub Actions
Use the composite action to integrate az-rbac-watch into your CI/CD pipelines.
Basic usage
- uses: maxvanp/az-rbac-watch@v0.5.0
with:
mode: scan
policy: policy.yaml
format: json
output: report.json
Available modes
| Mode | Description | Azure auth required |
|---|---|---|
scan |
Detect RBAC drift against baseline rules | Yes |
audit |
Check governance guardrails | Yes |
validate |
Validate policy YAML syntax (offline) | No |
snapshot |
Capture RBAC snapshot | Yes |
diff |
Compare two snapshots | No |
Example workflows
Ready-to-use workflow templates are available in examples/workflows/:
rbac-scheduled-scan.yml— Daily scan + audit with artifact reportsrbac-pr-check.yml— Validate policy files on pull requestsrbac-snapshot-diff.yml— Weekly snapshot comparison with change detection
Copy the desired workflow to .github/workflows/ in your repository and configure the required secrets.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
mode |
yes | — | Command: scan, audit, validate, snapshot, diff |
policy |
no | — | Path to policy YAML file |
tenant-id |
no | — | Azure tenant ID |
subscriptions |
no | — | Subscription IDs (comma-separated) |
management-groups |
no | — | Management group IDs (comma-separated) |
format |
no | console |
Output format: console or json |
output |
no | — | Output file path |
old-snapshot |
no | — | Old snapshot path (diff mode) |
new-snapshot |
no | — | New snapshot path (diff mode) |
python-version |
no | 3.12 |
Python version |
extra-args |
no | — | Additional CLI arguments |
Outputs
| Output | Description |
|---|---|
exit-code |
0 = compliant, 1 = findings/changes, 2 = error |
report-path |
Path to generated report (if output is set) |
Rule match operators
All comparisons are case-insensitive. Conditions are combined with AND logic.
| Operator | Type | Description |
|---|---|---|
scope |
str |
Exact scope match |
scope_prefix |
str |
Scope starts with value |
role |
str |
Exact role name |
role_in |
list |
Role is in list |
role_not_in |
list |
Role is NOT in list |
role_type |
str |
BuiltInRole or CustomRole |
principal_type |
str |
User, Group, or ServicePrincipal |
principal_type_in |
list |
Principal type is in list |
principal_id |
str |
Exact principal ID |
principal_name_prefix |
str |
Display name starts with |
principal_name_not_prefix |
str |
Display name does NOT start with |
principal_name_contains |
str |
Display name contains |
principal_name_not_contains |
str |
Display name does NOT contain |
Name-based operators require Graph API access. If unavailable, they evaluate to false (no false positives).
Required Azure permissions
Microsoft.Authorization/roleAssignments/readon scanned scopesDirectory.Read.All(Graph API, for display name resolution)
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file az_rbac_watch-0.10.1.tar.gz.
File metadata
- Download URL: az_rbac_watch-0.10.1.tar.gz
- Upload date:
- Size: 238.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
451f1d1e85fd933dbcef88fc654e6aaad5ad9d4d608ce3e4d804a865e928ba19
|
|
| MD5 |
f771bf0c973ddc2e441ce7524547597d
|
|
| BLAKE2b-256 |
83969737294d6aafcb2b17348412c63425ce3c3bc7f9055e4ae5df870e647ca8
|
Provenance
The following attestation bundles were made for az_rbac_watch-0.10.1.tar.gz:
Publisher:
publish.yml on maxvanp/az-rbac-watch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
az_rbac_watch-0.10.1.tar.gz -
Subject digest:
451f1d1e85fd933dbcef88fc654e6aaad5ad9d4d608ce3e4d804a865e928ba19 - Sigstore transparency entry: 1279017107
- Sigstore integration time:
-
Permalink:
maxvanp/az-rbac-watch@31b92fb21e363476c87df014f511d8f96b1ee32b -
Branch / Tag:
refs/tags/v0.10.1 - Owner: https://github.com/maxvanp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@31b92fb21e363476c87df014f511d8f96b1ee32b -
Trigger Event:
push
-
Statement type:
File details
Details for the file az_rbac_watch-0.10.1-py3-none-any.whl.
File metadata
- Download URL: az_rbac_watch-0.10.1-py3-none-any.whl
- Upload date:
- Size: 66.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
842684cadd55fa0c78eec46f7ac71f57e367090181320d637cc32d9c6977d777
|
|
| MD5 |
0752af4792490d3ae8f24251026a88e6
|
|
| BLAKE2b-256 |
01b36984e75b940743923cbc6b5e04dc262d5b2737b0ed6968131235ab3867c6
|
Provenance
The following attestation bundles were made for az_rbac_watch-0.10.1-py3-none-any.whl:
Publisher:
publish.yml on maxvanp/az-rbac-watch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
az_rbac_watch-0.10.1-py3-none-any.whl -
Subject digest:
842684cadd55fa0c78eec46f7ac71f57e367090181320d637cc32d9c6977d777 - Sigstore transparency entry: 1279017130
- Sigstore integration time:
-
Permalink:
maxvanp/az-rbac-watch@31b92fb21e363476c87df014f511d8f96b1ee32b -
Branch / Tag:
refs/tags/v0.10.1 - Owner: https://github.com/maxvanp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@31b92fb21e363476c87df014f511d8f96b1ee32b -
Trigger Event:
push
-
Statement type: