Files
computer-playbook/tests/unit/python/utils/security/test_pipeline.py
T
kevinveenbirkenbachandClaude Opus 5.5 6b36c8579a fix(sys-ctl-hlth-runtime): scan with Trivy, Grype and OSV instead of PHP floors
The runtime check judged PHP by its upstream version and failed every
container on an end-of-life line. Distribution builds backport fixes without
raising that number, so main run 41154895895 failed Checkmk#0 on
`PHP 8.1.2-1ubuntu2.28 runs the end-of-life line 8.1`, a build Ubuntu lists
as fixed for CVE-2026-109618 and CVE-2026-109619.

- The role now runs Trivy, Grype and OSV-Scanner, with a Syft SBOM per
  target, against the host and the images of the running containers, in
  bounded parallel lanes. Targets, engines and the failing severities are
  lists in meta/services.yml; the default is every target and engine at
  CRITICAL and HIGH with a fix available.
- Grype always scans the binary-classified packages of the SBOM, so a PHP
  built from source in an official php image (8.4.26) still fails on the
  CVEs fixed in 8.4.27, which Trivy and OSV do not see.
- OSV reads an SBOM copy whose distro purls carry the source package name.
- A CVE is suppressed only in the role owning the image, under
  services.<service>.security.suppressions with id, until and a
  `# nocheck: cve <reason>` marker; a lint rejects bare, malformed and
  expired entries.
- The deploy-time run fails on its first finding: sys-service gains
  system_service_run_once and sys-service-terminator skips the retries
  only for those units. The timer runs daily at 00:00 UTC.
- The PHP version floors are removed; the php_hardening check stays.
- make test-security replaces make security-runtimes: Syft builds an SBOM
  of every pinned image straight from the GHCR mirror, cached by digest,
  and the three engines scan it with the role's threshold and suppressions.

Part of #1324

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:44:58 +01:00

34 lines
1.1 KiB
Python

from __future__ import annotations
import os
import re
from unittest import TestCase, main
from utils.security import pipeline
class TestPipeline(TestCase):
def test_the_role_script_loads_once(self):
self.assertIs(pipeline.pipeline(), pipeline.pipeline())
def test_every_engine_has_a_semver_pin(self):
for engine in pipeline.pipeline().ENGINES:
with self.subTest(engine=engine):
self.assertRegex(
pipeline.engine_image(engine),
re.compile(r"^ghcr\.io/.+:v?\d+\.\d+\.\d+$"),
)
def test_the_engines_run_as_the_calling_user(self):
self.assertEqual(pipeline.user_runtime()[-1], f"{os.getuid()}:{os.getgid()}")
def test_the_role_default_is_a_valid_scan_config(self):
runtime = pipeline.role_services()["runtime"]
keys = ("targets", "engines", "severity", "fixed_only", "workers", "db_update")
config = pipeline.pipeline().scan_config({key: runtime[key] for key in keys})
self.assertEqual(config.severity, frozenset({"CRITICAL", "HIGH"}))
if __name__ == "__main__":
main()