Repository navigation
feat(scanner): add the evaluate() contract foundation (#369) - #381
parthrohit22 wants to merge 4 commits into
Conversation
Add fallible list_storage_accounts()/list_key_vaults() inventory calls, shared evaluation helpers and reason codes, and run rules that expose evaluate() through evaluate() only. AZ-KV-006 now reports ERROR when the Key Vault list fails. Add a contract test every migrated rule must pass. Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
TFT444
left a comment
There was a problem hiding this comment.
The foundation is solid and the design is well thought through. One item needs addressing before merge.
Warning: failed_rule_ids does not include rules that return ERROR/INVENTORY_UNAVAILABLE
scanner/engine.py:260-271: when evaluate() returns an ERROR/INVENTORY_UNAVAILABLE evaluation (the Azure list call failed and returned None), _run_evaluate marks the rule as completed and does NOT append it to failed_rule_ids. A consumer reading failed_rule_ids alone (for example get_compliance_score()) would treat that rule as having run cleanly, even though it never inspected any resources.
Please either:
- Append to
failed_rule_idswhen any returned evaluation hasstatus == EvaluationStatus.ERROR, so the two signals stay consistent; or - Add a clear docstring/comment at
failed_rule_idsstating it only tracks evaluator crashes, and that ERROR evaluations must be read fromevaluationsdirectly, and updateget_compliance_score()to check both.
Everything else looks good: backward compat on get_* vs list_*, the exclusive dispatch guard, FAIL-without-finding logging, _run_evaluate isinstance validation, and the contract test invariants are all correct. Happy to re-review once the above is addressed.
A rule whose evaluate() returned normally but reported an ERROR evaluation, such as INVENTORY_UNAVAILABLE, was treated as completed and left out of failed_rule_ids, so the two signals disagreed. List the rule whenever evaluate() crashes, returns malformed data, or reports any ERROR evaluation. Compliance scoring was not affected: an ERROR evaluation row already rolls up to ERROR. Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
|
@TFT444 Thanks for the review. Fixed in 5b132cc, using option 1: a rule is now added to For context on scoring: |
TFT444
left a comment
There was a problem hiding this comment.
Thanks for the quick fix on failed_rule_ids / ERROR semantics. Two more issues on the updated head before this is ready.
1. _run_evaluate all-or-nothing validation silently discards valid evaluations
scanner/engine.py:287-291
The item validation loop raises TypeError on the first non-RuleEvaluation item, which causes the except block to discard the entire list and emit a single EVALUATOR_EXCEPTION ERROR. If evaluate() returns [valid_eval, valid_eval, bad_dict], the two valid evaluations are thrown away and the rule is reported as completely broken. That misrepresents what the rule actually found.
Please either filter and log bad items individually (keeping the valid ones), or validate the full list before processing it and surface exactly which item was malformed. The contract test only ever feeds a fully malformed list, so the mixed case is not currently covered.
2. any(ERROR) in failed_rule_ids overstates rule failures
scanner/engine.py:199
A rule that evaluated 8 resources successfully and got EVIDENCE_UNAVAILABLE on 2 is listed in failed_rule_ids alongside rules that completely crashed. A caller reading failed_rule_ids (for example a compliance dashboard) would treat it as "rule did not complete" even though it did most of its job.
Please either raise the threshold (e.g. only list the rule when ALL evaluations are ERROR, or when the rule crashed entirely), or add a clear docstring on failed_rule_ids in the scan result schema stating it means "at least one ERROR evaluation occurred" so callers know not to treat it as a binary completed/failed signal. If the intent is truly to flag any partial failure, that decision should be explicit and tested.
…d items (#369) One malformed item made the engine discard every evaluation the rule returned, including valid FAIL evaluations, so real findings vanished from the scan. Drop only the malformed items, keep the rest, and add one MALFORMED_EVALUATION error naming their positions so the rule is still listed in failed_rule_ids. Document that failed_rule_ids lists rules with at least one ERROR evaluation, and that a per-resource gap is UNKNOWN and does not list the rule. Replace a test that paired ERROR with EVIDENCE_UNAVAILABLE, which contradicted the reason-code table, and cover the per-resource UNKNOWN and mixed malformed cases. Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
cbe3209 to
3827e15
Compare
|
Hi @TFT444 - I have pushed the requested fixes and would appreciate a re-review. |
ritiksah141
left a comment
There was a problem hiding this comment.
Approving. Reviewed the current head (3827e15) locally: full diff read, based on the current dev tip, both items from the last review round fully addressed and tested (malformed items are dropped individually while valid evaluations and findings survive, and failed_rule_ids now uses the stricter all-ERROR semantics with the decision documented and explicitly tested). The eight named suites pass 175/175, the full suite passes with 1537 passed / 93 skipped / 0 failed, and ruff is clean.
One small ask before merge: the CHANGELOG "Changed" entry still says a rule reporting "at least one ERROR evaluation" is listed in failed_rule_ids, which contradicts the final all-ERROR behaviour. Suggested replacement for that sentence:
"A rule whose evaluate() crashes, returns malformed data, or returns only ERROR evaluations (such as an inventory-wide INVENTORY_UNAVAILABLE failure with no usable outcomes) is listed in failed_rule_ids; a partial ERROR alongside usable outcomes does not list the rule, and a per-resource gap is UNKNOWN."
Otherwise this is a clean foundation for #380 and good to land.
What does this PR do?
Adds the foundation for migrating scanner rules to the
evaluate()coverage contract: inventory calls that report failure, shared evaluation helpers, a single engine path for migrated rules, and a contract test every migrated rule must pass.Changes
scanner/azure_client.py: newlist_storage_accounts()andlist_key_vaults()returnNonewhen the list call fails, soevaluate()can tell a failure from an empty subscription.get_storage_accounts()/get_key_vaults()wrap them and still return[], so legacyscan()rules are unchanged.scanner/evaluation.py: standard reason codes (INVENTORY_UNAVAILABLE,NO_RESOURCES_FOUND,EVIDENCE_UNAVAILABLE,MISSING_PROPERTIES,POLICY_NOT_REQUIRED,APPROVED_EXCEPTION) and helpersinventory_unavailable(),no_resources_found()andfail_findings().scanner/engine.py:evaluate()is run throughevaluate()only, and its findings come from itsFAILevaluations. This removes duplicate Azure list calls and the risk ofscan()andevaluate()drifting apart.failed_rule_idswhen itsevaluate()crashes, returns malformed data, or reports anyERRORevaluation (for exampleINVENTORY_UNAVAILABLE), sofailed_rule_idsand the evaluations always agree. A per-resource gap isUNKNOWN, notERROR, and does not list the rule.RuleEvaluationobjects are dropped individually instead of failing result serialisation for the whole scan. The rule's valid evaluations and FAIL findings are kept, and oneERROR / MALFORMED_EVALUATIONrow names the malformed positions.FAILwithout a finding is logged.scanner/rules/az_kv_006.py: a failed vault list reportsERROR / INVENTORY_UNAVAILABLEinstead ofNOT_APPLICABLE.scan()is now a thin wrapper overevaluate(), and evaluations recordenable_rbac_authorizationas evidence. Findings are unchanged.tests/test_rule_evaluation_contract.py(new): discovers every rule exposingevaluate()and checks that:ERROR, neverPASS/NOT_APPLICABLE;NOT_APPLICABLE;FAILcarries the rule's finding;scan()returns exactly theFAILfindings.Each migrated rule must register a fixture. Against the previous
AZ-KV-006this test fails, as intended.docs/adding-a-rule.md: new rules should implementevaluate(), with the reason-code table and a template.CHANGELOG.md: Added / Changed / Fixed entries.Type of change
Also a scanner engine change (evaluate() contract foundation).
Rule details (if applicable)
Testing
Local results:
test_rule_evaluation_contract,test_rule_evaluations,test_rules_keyvault,test_azure_client_management,test_engine_integration,test_compliance_scoring,test_rules_storage,test_pqc_rules.ruff check .andruff format --check .are clean.Related issue
Closes #369
Part of #380. Unblocks #370 and the good first issues #376, #377, #378 and #379.
Checklist
Signed-off-bytrailer (git commit -s; seedocs/dco.md)