fix(vuln): survive OOM kills, isolate bad upstream strings, label findings per line
Review findings on the integrated scanner: - An OOM kill no longer wedges the loop: inFlight is persisted before each scan, and on restart that image is marked failed and sits out full runs for 20h. - One bad upstream string can no longer freeze the report: safe_url requires an exact lowercase scheme, acceptance reasons are bounded, /triage urls use the same rule, and report.build repairs or drops any entry the schema would refuse. lastCompleteRunAt advances only after a report is actually published. - The private registry reports credential-required, not failed: VULN_PRIVATE_REGISTRIES names it, and Trivy is not invoked. - loadError clears after a full run against a good inventory. - Every image under platform/vuln-scanner stays out of Renovate auto-merge. - The page labels status and target state per line, and mixed acceptance reads as mixed. The Keycloak client is still an operator action: its key must go in an existing encrypted file that an agent must not modify. platform/vuln-scanner/README.md has the exact steps. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4c82c64b2f
commit
d8b25a67fe
15 files changed
+557
-43
No files matched your search
@@ -16,6 +16,11 @@
|
||||
# (6) the pod: non-root, read-only rootfs, drop ALL, seccomp RuntimeDefault, no host
|
||||
# namespaces or hostPath, CPU and memory limits on every container, an ephemeral-storage
|
||||
# limit on the scanner, and a size-limited /cache emptyDir.
|
||||
# (8) a renovate.json5 rule covers every file under platform/vuln-scanner: automerge:false,
|
||||
# minimumReleaseAge of at least 7 days, and no later rule turns auto-merge back on (the
|
||||
# python image holds the cluster-read token);
|
||||
# (9) the scanner names the private registry it cannot reach, so that registry reports
|
||||
# credential-required rather than a permanent failed.
|
||||
# AND (7) each evaluator is run on a mutated copy and must flag it (non-vacuity).
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
@@ -150,6 +155,33 @@ def eval_renovate(rules):
|
||||
return bad
|
||||
|
||||
|
||||
SCANNER_FILES = "platform/vuln-scanner/**"
|
||||
|
||||
|
||||
def eval_renovate_files(rules):
|
||||
positions = [i for i, r in enumerate(rules) if SCANNER_FILES in (r.get("matchFileNames") or [])]
|
||||
if not positions:
|
||||
return [f"no packageRule has matchFileNames {SCANNER_FILES}"]
|
||||
bad = []
|
||||
rule = rules[positions[-1]]
|
||||
if rule.get("automerge") is not False:
|
||||
bad.append("the platform/vuln-scanner file rule is not automerge:false")
|
||||
age = VERSION_UPDATE_RULE_RE.match(rule.get("minimumReleaseAge") or "")
|
||||
if not age or int(age.group(1)) < 7:
|
||||
bad.append("the platform/vuln-scanner file rule has no minimumReleaseAge of at least 7 days")
|
||||
if any(r.get("automerge") is True for r in rules[positions[-1] + 1:]):
|
||||
bad.append("a rule after the platform/vuln-scanner file rule re-enables auto-merge")
|
||||
return bad
|
||||
|
||||
|
||||
def eval_private_registries(pod):
|
||||
scanner = next(c for c in pod["containers"] if c["name"] == "scanner")
|
||||
env = {e["name"]: e.get("value") for e in scanner.get("env") or []}
|
||||
if env.get("VULN_PRIVATE_REGISTRIES") != "gitea.${BASE_DOMAIN}":
|
||||
return ["the scanner does not name the private registry gitea.${BASE_DOMAIN}"]
|
||||
return []
|
||||
|
||||
|
||||
def eval_rbac(rbac):
|
||||
bad = []
|
||||
croles = [d for d in rbac if d.get("kind") == "ClusterRole"]
|
||||
@@ -224,6 +256,10 @@ rules = renovate.get("packageRules") or []
|
||||
check(len(rules) > 5, "(4) parsed renovate.json5 packageRules (parser degenerated)")
|
||||
for v in eval_renovate(rules):
|
||||
check(False, f"(4) {v}")
|
||||
for v in eval_renovate_files(rules):
|
||||
check(False, f"(8) {v}")
|
||||
for v in eval_private_registries(pod):
|
||||
check(False, f"(9) {v}")
|
||||
|
||||
rbac = docs(COMP / "rbac.yaml")
|
||||
for v in eval_rbac(rbac):
|
||||
@@ -274,6 +310,23 @@ for name, fn in (("Trivy automerge flipped true", ren_auto), ("minimumReleaseAge
|
||||
("Trivy removed from Renovate rules", ren_drop), ("later auto-merge rule appended", ren_later)):
|
||||
check(bool(eval_renovate(mutated(fn, rules))), f"(7) Renovate evaluator missed mutant: {name}")
|
||||
|
||||
# Renovate file-rule mutants
|
||||
def files_rule(r):
|
||||
return next(x for x in r if SCANNER_FILES in (x.get("matchFileNames") or []))
|
||||
def files_auto(r): files_rule(r)["automerge"] = True
|
||||
def files_age(r): files_rule(r)["minimumReleaseAge"] = "1 day"
|
||||
def files_drop(r): r.remove(files_rule(r))
|
||||
def files_later(r): r.append({"matchDatasources": ["docker"], "automerge": True})
|
||||
for name, fn in (("file rule automerge true", files_auto), ("file rule age shortened", files_age),
|
||||
("file rule removed", files_drop), ("later rule re-enables auto-merge", files_later)):
|
||||
check(bool(eval_renovate_files(mutated(fn, rules))), f"(7) file-rule evaluator missed mutant: {name}")
|
||||
|
||||
def reg_drop(p):
|
||||
next(c for c in p["containers"] if c["name"] == "scanner")["env"][:] = [
|
||||
e for e in next(c for c in p["containers"] if c["name"] == "scanner")["env"]
|
||||
if e["name"] != "VULN_PRIVATE_REGISTRIES"]
|
||||
check(bool(eval_private_registries(mutated(reg_drop, pod))), "(7) private-registry evaluator missed the removal")
|
||||
|
||||
# Pod mutants
|
||||
def pod_root(p): p["securityContext"]["runAsNonRoot"] = False
|
||||
def pod_host(p): p["hostNetwork"] = True
|
||||
|
||||
Reference in new issue
Block a user