What problem does this solve?
The 10 storage rules (AZ-STOR-001 to AZ-STOR-010) only implement scan(), so the engine reports their coverage as UNKNOWN / LEGACY_RULE_NOT_MIGRATED. Storage controls map to many CIS, NIST CSF, ISO 27001 and SOC 2 controls, so this family has the largest effect on report quality. It also serves as the reference implementation that later family migrations copy.
Describe the solution
Migrate every rule below to the evaluate() contract, using AzureClient.list_storage_accounts() and the shared helpers from the foundation issue.
| Rule |
Non-trivial branches that need an explicit status |
az_stor_001 public blob access |
— |
az_stor_002 HTTPS-only |
— |
az_stor_003 lifecycle policy |
get_storage_lifecycle_policy() returns None → UNKNOWN; missing id / name / resource group → UNKNOWN |
az_stor_004 diagnostic logging |
get_storage_service_logging() returns None → UNKNOWN |
az_stor_005 geo-redundant replication |
skipped accounts → explicit status |
az_stor_006 shared key access |
None is treated as enabled, so it stays FAIL (documented Azure default) |
az_stor_007 minimum TLS |
missing value → UNKNOWN |
az_stor_008 customer-managed key |
tag not set → NOT_APPLICABLE / POLICY_NOT_REQUIRED; approved exception → APPROVED_EXCEPTION |
az_stor_009 blob immutability |
as 008, plus get_blob_containers() returns None → UNKNOWN |
az_stor_010 private endpoint |
public access disabled → NOT_APPLICABLE / PUBLIC_ACCESS_DISABLED; connections unavailable → UNKNOWN |
Status mapping
Every path that scan() currently handles with a silent continue must become an explicit status:
| Situation |
Status |
reason_code |
Inventory call failed (returned None) |
ERROR |
INVENTORY_UNAVAILABLE |
| Inventory succeeded but is empty |
NOT_APPLICABLE |
NO_RESOURCES_FOUND |
| Setting is compliant |
PASS |
— |
| Setting is a violation |
FAIL + the existing finding dict |
— |
A per-resource detail call returned None, or a required field is missing |
UNKNOWN |
EVIDENCE_UNAVAILABLE / MISSING_PROPERTIES |
Opt-in policy tag not set (policy_required(...) is false) |
NOT_APPLICABLE |
POLICY_NOT_REQUIRED |
| Approved exception tag set |
NOT_APPLICABLE |
APPROVED_EXCEPTION |
A failed inventory call must never produce PASS or NOT_APPLICABLE.
Acceptance criteria
Alternatives considered
Migrating storage alongside the foundation PR was considered. It is kept separate so the engine and contract changes can be reviewed on their own.
Additional context
What problem does this solve?
The 10 storage rules (
AZ-STOR-001toAZ-STOR-010) only implementscan(), so the engine reports their coverage asUNKNOWN / LEGACY_RULE_NOT_MIGRATED. Storage controls map to many CIS, NIST CSF, ISO 27001 and SOC 2 controls, so this family has the largest effect on report quality. It also serves as the reference implementation that later family migrations copy.Describe the solution
Migrate every rule below to the
evaluate()contract, usingAzureClient.list_storage_accounts()and the shared helpers from the foundation issue.az_stor_001public blob accessaz_stor_002HTTPS-onlyaz_stor_003lifecycle policyget_storage_lifecycle_policy()returnsNone→UNKNOWN; missing id / name / resource group →UNKNOWNaz_stor_004diagnostic loggingget_storage_service_logging()returnsNone→UNKNOWNaz_stor_005geo-redundant replicationaz_stor_006shared key accessNoneis treated as enabled, so it staysFAIL(documented Azure default)az_stor_007minimum TLSUNKNOWNaz_stor_008customer-managed keyNOT_APPLICABLE / POLICY_NOT_REQUIRED; approved exception →APPROVED_EXCEPTIONaz_stor_009blob immutability008, plusget_blob_containers()returnsNone→UNKNOWNaz_stor_010private endpointNOT_APPLICABLE / PUBLIC_ACCESS_DISABLED; connections unavailable →UNKNOWNStatus mapping
Every path that
scan()currently handles with a silentcontinuemust become an explicit status:reason_codeNone)ERRORINVENTORY_UNAVAILABLENOT_APPLICABLENO_RESOURCES_FOUNDPASSFAIL+ the existing finding dictNone, or a required field is missingUNKNOWNEVIDENCE_UNAVAILABLE/MISSING_PROPERTIESpolicy_required(...)is false)NOT_APPLICABLEPOLICY_NOT_REQUIREDNOT_APPLICABLEAPPROVED_EXCEPTIONA failed inventory call must never produce
PASSorNOT_APPLICABLE.Acceptance criteria
evaluate(azure_client, subscription_id) -> List[RuleEvaluation]using the shared helpers inscanner/evaluation.py.scan()becomes a thin wrapper returning the findings attached toFAILevaluations.scan()returns exactly the same findings as before the change.FAIL/PASSevaluation records the inspected value(s) inevidence, for example{"allow_blob_public_access": true}.tests/test_rule_evaluation_contract.pyand it passes.pytest,ruff check .andruff format --check .pass.CHANGELOG.mdupdated underUnreleased.Alternatives considered
Migrating storage alongside the foundation PR was considered. It is kept separate so the engine and contract changes can be reviewed on their own.
Additional context
tests/test_rules_storage.py.