Skip to content

Commit c06a775

Browse files
authored
[core][feat] account security score details (#2162)
1 parent dcd08d9 commit c06a775

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

fixcore/fixcore/report/__init__.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ def to_json(self) -> Json:
9292
}
9393

9494

95+
@define
96+
class BenchmarkScore:
97+
score: int
98+
failed_checks: Dict[ReportSeverity, int]
99+
failed_resources: Dict[ReportSeverity, int]
100+
101+
def to_json(self) -> Json:
102+
return {
103+
"score": self.score,
104+
"failed": {
105+
severity.value: {"checks": count, "resources": fr}
106+
for severity, count in self.failed_checks.items()
107+
if (fr := self.failed_resources.get(severity, 0)) and count > 0
108+
},
109+
}
110+
111+
95112
@define
96113
class Remediation:
97114
kind: ClassVar[str] = "fix_core_report_check_remediation"
@@ -368,23 +385,27 @@ def visit_check_collection(collection: CheckCollectionResult) -> None:
368385
visit_check_collection(self)
369386
return result
370387

371-
def score_for(self, account: str) -> int:
372-
failing: Dict[ReportSeverity, int] = defaultdict(int)
388+
# score, failed_checks, failed_resources
389+
def score_for(self, account: str) -> BenchmarkScore:
390+
failing_checks: Dict[ReportSeverity, int] = defaultdict(int)
391+
failing_resources: Dict[ReportSeverity, int] = defaultdict(int)
373392
available: Dict[ReportSeverity, int] = defaultdict(int)
374393

375394
def available_failing(check: CheckCollectionResult) -> None:
376395
for result in check.checks:
396+
fr = result.number_of_resources_failing_by_account.get(account, 0)
377397
available[result.check.severity] += 1
378-
failing[result.check.severity] += (
379-
1 if result.number_of_resources_failing_by_account.get(account, 0) else 0
380-
)
398+
failing_checks[result.check.severity] += 1 if fr else 0
399+
failing_resources[result.check.severity] += fr
381400
for child in check.children:
382401
available_failing(child)
383402

384403
available_failing(self) # walk the benchmark hierarchy
385-
missing = sum(severity.score * count for severity, count in failing.items())
404+
missing = sum(severity.score * count for severity, count in failing_checks.items())
386405
total = sum(severity.score * count for severity, count in available.items())
387-
return int((max(0, total - missing) * 100) // total) if total > 0 else 100
406+
return BenchmarkScore(
407+
int((max(0, total - missing) * 100) // total) if total > 0 else 100, failing_checks, failing_resources
408+
)
388409

389410

390411
class Inspector(ABC):

fixcore/fixcore/report/inspector_service.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,21 @@ async def perform_benchmarks(
263263
result = {
264264
name: self.__to_result(benchmark, check_lookup, results, context) for name, benchmark in benchmarks.items()
265265
}
266+
all_checks = {cr.check.id: cr for res in result.values() for cr in res.check_results()}
267+
268+
def account_failing(account_id: str) -> Json:
269+
failing_checks: Dict[ReportSeverity, int] = defaultdict(int)
270+
failing_resources: Dict[ReportSeverity, int] = defaultdict(int)
271+
for cr in all_checks.values():
272+
if fr := cr.resources_failing_by_account.get(account_id, []):
273+
failing_checks[cr.check.severity] += 1
274+
failing_resources[cr.check.severity] += len(fr)
275+
return {
276+
sev.value: {"checks": count, "resources": failing_resources[sev]}
277+
for sev, count in failing_checks.items()
278+
if count > 0 and failing_resources[sev] > 0
279+
}
280+
266281
if sync_security_section:
267282
model = await self.model_handler.load_model(graph)
268283
# In case no run_id is provided, we invent a report run id here.
@@ -278,9 +293,13 @@ async def perform_benchmarks(
278293
if (node_id := value_in_path(acc, NodePath.node_id)) and (
279294
acc_id := value_in_path(acc, NodePath.reported_id)
280295
):
281-
bench_score = {br.id: br.score_for(acc_id) for br in result.values()}
282-
account_score = sum(bench_score.values()) / len(bench_score) if bench_score else 100
283-
patch = {"score": account_score, "benchmark": bench_score}
296+
scores = {br.id: br.score_for(acc_id) for br in result.values()}
297+
account_score = sum(a.score for a in scores.values()) / len(scores) if scores else 100
298+
patch = {
299+
"score": account_score,
300+
"failed": account_failing(acc_id),
301+
"benchmark": {br: score.to_json() for br, score in scores.items()},
302+
}
284303
async for _ in db.update_nodes_metadata(model, patch, [node_id]):
285304
pass
286305
return result

fixcore/tests/fixcore/report/inspector_service_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ def assert_result(results: Dict[str, BenchmarkResult]) -> None:
105105
db = inspector_service.db_access.get_graph_db(graph_name)
106106
async with await db.search_list(QueryModel(Query.by("account"), foo_model)) as crsr:
107107
async for elem in crsr:
108+
expected = {"critical": {"checks": 1, "resources": 10}, "medium": {"checks": 1, "resources": 10}}
108109
assert elem["metadata"]["score"] == 0
109-
assert elem["metadata"]["benchmark"]["test"] == 0
110+
assert elem["metadata"]["failed"] == expected
111+
assert elem["metadata"]["benchmark"]["test"] == {"score": 0, "failed": expected}
110112

111113

112114
async def test_perform_benchmark_ignored(

0 commit comments

Comments
 (0)