A scanner and an infrastructure baseline for untangling who can access what across a multi-subscription Azure estate: shared secrets out, managed identities in; direct privileged grants out, group-based RBAC with PIM in; Key Vault access policies out, RBAC authorization in. The scanner reads the estate, evaluates it against the baseline, and emits findings with remediations — as JSON for the dashboard or SARIF for code-scanning UIs.
Reference implementation. Real, working engineering written to demonstrate the approach — not a client deliverable. The estate in the fixtures and sample report is illustrative; the rules, scanner and infrastructure are not. Scanning a live estate needs Reader on the target subscriptions.
| Rule | Check | Severity |
|---|---|---|
BAS-001 |
Credentials in app settings (passwords, account keys — Key Vault references exempt) | Critical |
BAS-002 |
Long-lived or expired service-principal secrets (federated credentials exempt) | High |
BAS-003 |
Owner/Contributor at subscription scope granted directly to a user or SP | High |
BAS-004 |
Compute without a managed identity | Medium |
BAS-005 |
Key Vault still on access policies instead of RBAC | Medium |
BAS-006 |
Storage account still allows shared-key access | High |
BAS-007 |
More than a break-glass number of subscription Owners | Medium |
Each rule is one pure class over an estate snapshot (src/Bastion.Rules) with its own tests — adding a check is a class and a fixture, not a refactor.
dotnet run --project src/Bastion.Scanner -- rules # list the baseline
dotnet run --project src/Bastion.Scanner -- scan # scan visible subscriptions (JSON)
dotnet run --project src/Bastion.Scanner -- scan --format sarif --output findings.sarif
dotnet run --project src/Bastion.Scanner -- scan --save-snapshot estate.jsonscan walks every subscription visible to DefaultAzureCredential (read-only), builds a
snapshot, runs the rules, and exits non-zero if anything High or Critical is open — so a scheduled
pipeline fails on drift. Open the JSON report in the dashboard (npm run dev).
A captured snapshot can be analysed offline — no credentials, no cloud:
dotnet run --project src/Bastion.Scanner -- analyze samples/estate.json --format markdownsamples/estate.json is a deliberately messy two-subscription estate; the
command produces the one-page access review (13 findings) and exits 2 because Critical findings
are open. Service-principal credentials come from Microsoft Graph via
GraphServicePrincipalReader when the
scanning identity holds Application.Read.All.
infra/ contains the Bicep that is the remediation:
managed-identity.bicep— the identity that replaces shared secretsworkload-identity-federation.bicep— GitHub Actions deploys via OIDC, no client secretkey-vault-rbac.bicep— RBAC-mode vault, purge protection, no access policiesgroup-rbac.bicep— privileged roles to groups, never individuals
Prerequisites, a clean-machine setup, the permissions a live scan needs and
troubleshooting are in INSTALL.md. Short version:
dotnet test Bastion.sln # 15 rule-engine tests
cd web && npm ci && npm run buildBoth run in CI on every push, plus az bicep build over every module.
Design and code by Nick Zivkovic.