feat(security): expose scanner failures in focused cockpit
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
This commit is contained in:
@@ -150,6 +150,28 @@ _SIEM_STAGES = (
|
||||
"verify_learn",
|
||||
)
|
||||
|
||||
_PUBLIC_DISCOVERY_COLLECTOR_IDS = frozenset(
|
||||
{
|
||||
"k8s_replicasets",
|
||||
"k8s_nodes",
|
||||
"k8s_pods",
|
||||
"k8s_deployments",
|
||||
"k8s_statefulsets",
|
||||
"k8s_daemonsets",
|
||||
"k8s_services",
|
||||
"k8s_configmaps",
|
||||
"k8s_persistentvolumeclaims",
|
||||
"k8s_ingresses",
|
||||
"k8s_cronjobs",
|
||||
"prometheus_targets",
|
||||
"database_catalog",
|
||||
"ai_catalog",
|
||||
"gitea_bundle_backup",
|
||||
"gitea_ci",
|
||||
"domain_tls_inventory",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _value(row: Any, name: str, default: Any = None) -> Any:
|
||||
if row is None:
|
||||
@@ -162,6 +184,28 @@ def _value(row: Any, name: str, default: Any = None) -> Any:
|
||||
return getattr(row, name, default)
|
||||
|
||||
|
||||
def _public_discovery_failure(error: Any) -> tuple[str | None, list[str]]:
|
||||
"""Project only fixed collector identities, never raw scanner errors."""
|
||||
if not isinstance(error, str) or not error:
|
||||
return None, []
|
||||
|
||||
marker = "asset_collector_failures="
|
||||
if marker not in error:
|
||||
return "asset_scan_failed_public_details_unavailable", []
|
||||
|
||||
candidates = error.split(marker, 1)[1].split(",")
|
||||
collector_ids = sorted(
|
||||
{
|
||||
candidate.strip()
|
||||
for candidate in candidates
|
||||
if candidate.strip() in _PUBLIC_DISCOVERY_COLLECTOR_IDS
|
||||
}
|
||||
)
|
||||
if not collector_ids:
|
||||
return "asset_scan_failed_public_details_unavailable", []
|
||||
return "asset_collector_failures", collector_ids
|
||||
|
||||
|
||||
def _count(rows: Iterable[Any], key: str, value: str, count_key: str = "cnt") -> int:
|
||||
return sum(
|
||||
int(_value(row, count_key, 0) or 0)
|
||||
@@ -684,8 +728,7 @@ def build_iwooos_security_asset_control_plane(
|
||||
for row in inventory_source
|
||||
)
|
||||
package_digest_unpinned_count = sum(
|
||||
int(_value(row, "unpinned_package_count", 0) or 0)
|
||||
for row in inventory_source
|
||||
int(_value(row, "unpinned_package_count", 0) or 0) for row in inventory_source
|
||||
)
|
||||
certificate_live_unverified_count = sum(
|
||||
int(_value(row, "unverified_certificate_count", 0) or 0)
|
||||
@@ -726,10 +769,14 @@ def build_iwooos_security_asset_control_plane(
|
||||
discovery_fresh = (
|
||||
discovery_age is not None and discovery_age <= _DISCOVERY_FRESHNESS_SECONDS
|
||||
)
|
||||
discovery_status = str(
|
||||
_value(discovery_row, "latest_status", "missing") or "missing"
|
||||
)
|
||||
failure_reason_code, failed_collector_ids = _public_discovery_failure(
|
||||
_value(discovery_row, "latest_error") if discovery_status == "failed" else None
|
||||
)
|
||||
discovery = {
|
||||
"latest_status": str(
|
||||
_value(discovery_row, "latest_status", "missing") or "missing"
|
||||
),
|
||||
"latest_status": discovery_status,
|
||||
"latest_scan_depth": str(
|
||||
_value(discovery_row, "latest_scan_depth", "unknown") or "unknown"
|
||||
),
|
||||
@@ -747,6 +794,10 @@ def build_iwooos_security_asset_control_plane(
|
||||
"age_seconds": discovery_age,
|
||||
"freshness_slo_seconds": _DISCOVERY_FRESHNESS_SECONDS,
|
||||
"fresh": discovery_fresh,
|
||||
"failure_reason_code": failure_reason_code,
|
||||
"failed_collector_count": len(failed_collector_ids),
|
||||
"failed_collector_ids": failed_collector_ids,
|
||||
"raw_error_returned": False,
|
||||
}
|
||||
|
||||
coverage = _rows_by_dimension(coverage_source, _COVERAGE_DIMENSIONS)
|
||||
@@ -897,8 +948,7 @@ def build_iwooos_security_asset_control_plane(
|
||||
github_supply_chain_compliant_percent=_percent(
|
||||
max(
|
||||
0,
|
||||
by_type.get("package", 0)
|
||||
- forbidden_github_supply_chain_asset_count,
|
||||
by_type.get("package", 0) - forbidden_github_supply_chain_asset_count,
|
||||
),
|
||||
by_type.get("package", 0),
|
||||
),
|
||||
@@ -909,8 +959,7 @@ def build_iwooos_security_asset_control_plane(
|
||||
certificate_live_verified_percent=_percent(
|
||||
max(
|
||||
0,
|
||||
by_type.get("certificate", 0)
|
||||
- certificate_live_unverified_count,
|
||||
by_type.get("certificate", 0) - certificate_live_unverified_count,
|
||||
),
|
||||
by_type.get("certificate", 0),
|
||||
),
|
||||
@@ -963,8 +1012,7 @@ def build_iwooos_security_asset_control_plane(
|
||||
)
|
||||
)
|
||||
supply_chain_policy_gap_count = (
|
||||
forbidden_github_supply_chain_asset_count
|
||||
+ package_digest_unpinned_count
|
||||
forbidden_github_supply_chain_asset_count + package_digest_unpinned_count
|
||||
)
|
||||
if supply_chain_policy_gap_count > 0:
|
||||
work_items.append(
|
||||
@@ -1151,9 +1199,7 @@ def build_iwooos_security_asset_control_plane(
|
||||
"forbidden_github_supply_chain_asset_count": (
|
||||
forbidden_github_supply_chain_asset_count
|
||||
),
|
||||
"certificate_live_unverified_count": (
|
||||
certificate_live_unverified_count
|
||||
),
|
||||
"certificate_live_unverified_count": (certificate_live_unverified_count),
|
||||
"certificate_live_unhealthy_count": certificate_live_unhealthy_count,
|
||||
"automation_coverage_green_percent": _percent(
|
||||
coverage_green, coverage_total
|
||||
@@ -1241,13 +1287,11 @@ def build_iwooos_security_asset_control_plane(
|
||||
"certificate_asset_count": by_type.get("certificate", 0),
|
||||
"live_tls_verified_count": max(
|
||||
0,
|
||||
by_type.get("certificate", 0)
|
||||
- certificate_live_unverified_count,
|
||||
by_type.get("certificate", 0) - certificate_live_unverified_count,
|
||||
),
|
||||
"live_tls_evidenced_count": max(
|
||||
0,
|
||||
by_type.get("certificate", 0)
|
||||
- certificate_live_unverified_count,
|
||||
by_type.get("certificate", 0) - certificate_live_unverified_count,
|
||||
),
|
||||
"live_tls_healthy_count": max(
|
||||
0,
|
||||
@@ -1272,6 +1316,7 @@ def build_iwooos_security_asset_control_plane(
|
||||
"aggregate_only": True,
|
||||
"raw_asset_identity_returned": False,
|
||||
"raw_event_payload_returned": False,
|
||||
"raw_discovery_error_returned": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"live_scan_triggered": False,
|
||||
"runtime_action_triggered": False,
|
||||
@@ -1340,6 +1385,10 @@ def build_unavailable_iwooos_security_asset_control_plane(
|
||||
"age_seconds": None,
|
||||
"freshness_slo_seconds": _DISCOVERY_FRESHNESS_SECONDS,
|
||||
"fresh": False,
|
||||
"failure_reason_code": reason_code,
|
||||
"failed_collector_count": 0,
|
||||
"failed_collector_ids": [],
|
||||
"raw_error_returned": False,
|
||||
},
|
||||
"asset_scopes": [
|
||||
{
|
||||
@@ -1532,6 +1581,7 @@ def build_unavailable_iwooos_security_asset_control_plane(
|
||||
"aggregate_only": True,
|
||||
"raw_asset_identity_returned": False,
|
||||
"raw_event_payload_returned": False,
|
||||
"raw_discovery_error_returned": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"live_scan_triggered": False,
|
||||
"runtime_action_triggered": False,
|
||||
@@ -1656,11 +1706,12 @@ class IwoooSSecurityAssetControlPlaneService:
|
||||
latest.new_assets AS latest_new_assets,
|
||||
latest.modified_assets AS latest_modified_assets,
|
||||
latest.disappeared_assets AS latest_disappeared_assets,
|
||||
latest.error AS latest_error,
|
||||
successful.ended_at AS latest_success_ended_at
|
||||
FROM (SELECT 1) seed
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT status, scan_depth, started_at, ended_at, total_assets,
|
||||
new_assets, modified_assets, disappeared_assets
|
||||
new_assets, modified_assets, disappeared_assets, error
|
||||
FROM asset_discovery_run
|
||||
ORDER BY started_at DESC
|
||||
LIMIT 1
|
||||
|
||||
@@ -26,10 +26,15 @@ def _row(**values):
|
||||
return SimpleNamespace(**values)
|
||||
|
||||
|
||||
def _ready_payload(source_health: list[dict] | None = None) -> dict:
|
||||
def _ready_payload(
|
||||
source_health: list[dict] | None = None,
|
||||
*,
|
||||
discovery_status: str = "success",
|
||||
discovery_error: str | None = None,
|
||||
) -> dict:
|
||||
now = datetime(2026, 7, 11, 4, 0, tzinfo=UTC)
|
||||
discovery = _row(
|
||||
latest_status="success",
|
||||
latest_status=discovery_status,
|
||||
latest_scan_depth="full",
|
||||
latest_started_at=now - timedelta(minutes=31),
|
||||
latest_ended_at=now - timedelta(minutes=30),
|
||||
@@ -38,6 +43,7 @@ def _ready_payload(source_health: list[dict] | None = None) -> dict:
|
||||
latest_new_assets=2,
|
||||
latest_modified_assets=1,
|
||||
latest_disappeared_assets=0,
|
||||
latest_error=discovery_error,
|
||||
)
|
||||
inventory = [
|
||||
_row(asset_type="host", cnt=3, unowned_count=1, stale_count=0),
|
||||
@@ -150,6 +156,10 @@ def test_builds_live_asset_siem_audit_and_ai_control_plane_without_false_closure
|
||||
assert payload["completion"]["strict_runtime_closure_percent"] == 0
|
||||
assert payload["completion"]["dimension_count"] == 6
|
||||
assert payload["discovery"]["fresh"] is True
|
||||
assert payload["discovery"]["failure_reason_code"] is None
|
||||
assert payload["discovery"]["failed_collector_count"] == 0
|
||||
assert payload["discovery"]["failed_collector_ids"] == []
|
||||
assert payload["discovery"]["raw_error_returned"] is False
|
||||
assert payload["ai_automation"]["same_run_closed_loop_proven"] is False
|
||||
assert payload["ai_automation"]["same_run_closed_loop_count"] == 0
|
||||
assert len(payload["nist_csf_functions"]) == 6
|
||||
@@ -187,6 +197,45 @@ def test_builds_live_asset_siem_audit_and_ai_control_plane_without_false_closure
|
||||
assert 'secret_value_collection_allowed": true' not in public_text.lower()
|
||||
|
||||
|
||||
def test_discovery_failure_exposes_only_allowlisted_collector_ids() -> None:
|
||||
raw_error = (
|
||||
"RuntimeError: asset_collector_failures=domain_tls_inventory,"
|
||||
"prometheus_targets,postgresql://operator:raw-secret@private-host/runtime"
|
||||
)
|
||||
payload = _ready_payload(
|
||||
discovery_status="failed",
|
||||
discovery_error=raw_error,
|
||||
)
|
||||
|
||||
assert payload["discovery"]["failure_reason_code"] == "asset_collector_failures"
|
||||
assert payload["discovery"]["failed_collector_count"] == 2
|
||||
assert payload["discovery"]["failed_collector_ids"] == [
|
||||
"domain_tls_inventory",
|
||||
"prometheus_targets",
|
||||
]
|
||||
assert payload["discovery"]["raw_error_returned"] is False
|
||||
assert payload["boundaries"]["raw_discovery_error_returned"] is False
|
||||
|
||||
public_text = json.dumps(payload, ensure_ascii=False)
|
||||
assert "raw-secret" not in public_text
|
||||
assert "private-host" not in public_text
|
||||
assert "postgresql://" not in public_text
|
||||
|
||||
|
||||
def test_discovery_failure_fails_closed_for_unknown_raw_error() -> None:
|
||||
payload = _ready_payload(
|
||||
discovery_status="failed",
|
||||
discovery_error="RuntimeError: /private/path leaked provider detail",
|
||||
)
|
||||
|
||||
assert payload["discovery"]["failure_reason_code"] == (
|
||||
"asset_scan_failed_public_details_unavailable"
|
||||
)
|
||||
assert payload["discovery"]["failed_collector_count"] == 0
|
||||
assert payload["discovery"]["failed_collector_ids"] == []
|
||||
assert "/private/path" not in json.dumps(payload)
|
||||
|
||||
|
||||
def test_runtime_image_policy_gaps_are_visible_and_never_false_green() -> None:
|
||||
now = datetime(2026, 7, 11, 4, 0, tzinfo=UTC)
|
||||
payload = build_iwooos_security_asset_control_plane(
|
||||
@@ -398,9 +447,7 @@ def test_unhealthy_live_tls_evidence_remains_a_p0_gap() -> None:
|
||||
domains = {
|
||||
domain["domain_id"]: domain for domain in payload["security_program_domains"]
|
||||
}
|
||||
assert domains["network_dns_tls"]["gap_code"] == (
|
||||
"certificate_live_tls_unhealthy"
|
||||
)
|
||||
assert domains["network_dns_tls"]["gap_code"] == ("certificate_live_tls_unhealthy")
|
||||
|
||||
|
||||
def test_fully_evidenced_healthy_tls_inventory_proves_health() -> None:
|
||||
@@ -438,8 +485,7 @@ def test_fully_evidenced_healthy_tls_inventory_proves_health() -> None:
|
||||
assert payload["certificate_inventory"]["live_tls_unhealthy_count"] == 0
|
||||
assert payload["certificate_inventory"]["live_tls_health_proven"] is True
|
||||
assert all(
|
||||
item["work_item_id"] != "AIA-P0-006-02B"
|
||||
for item in payload["work_items"]
|
||||
item["work_item_id"] != "AIA-P0-006-02B" for item in payload["work_items"]
|
||||
)
|
||||
|
||||
|
||||
@@ -517,6 +563,7 @@ def test_unavailable_payload_is_fixed_degraded_contract_without_raw_error() -> N
|
||||
assert payload["discovery"]["fresh"] is False
|
||||
assert payload["boundaries"]["raw_asset_identity_returned"] is False
|
||||
assert payload["boundaries"]["raw_event_payload_returned"] is False
|
||||
assert payload["boundaries"]["raw_discovery_error_returned"] is False
|
||||
|
||||
|
||||
def test_service_redacts_database_exception_details(monkeypatch) -> None:
|
||||
|
||||
Reference in New Issue
Block a user