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
Metadata
Release files for az-rbac-watch 0.10.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| az_rbac_watch-0.10.1.tar.gz | 238.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| az_rbac_watch-0.10.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 304.7 kB
Release files / az_rbac_watch-0.10.1.tar.gz
| Download URL | az_rbac_watch-0.10.1.tar.gz |
|---|---|
| Size | 238.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
451f1d1e85fd933dbcef88fc654e6aaad5ad9d4d608ce3e4d804a865e928ba19
|
|
BLAKE2b-256 checksum How to use checksums |
83969737294d6aafcb2b17348412c63425ce3c3bc7f9055e4ae5df870e647ca8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 11, 2026.
Transparency logRelease files / az_rbac_watch-0.10.1-py3-none-any.whl
| Download URL | az_rbac_watch-0.10.1-py3-none-any.whl |
|---|---|
| Size | 66.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
842684cadd55fa0c78eec46f7ac71f57e367090181320d637cc32d9c6977d777
|
|
BLAKE2b-256 checksum How to use checksums |
01b36984e75b940743923cbc6b5e04dc262d5b2737b0ed6968131235ab3867c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 11, 2026.
Transparency log