fix(sys-ctl-hlth-runtime): harden LAM CLI, catch apk options, drop vars comment

Addresses three CodeRabbit findings on PR #1265.

1. LAM hardening invisible to the probe (thread 4413764929): the runtime
   check probes PHP with `php -r`, the CLI SAPI. web-app-lam mounted the
   hardening ini only into /etc/php/8.4/apache2/conf.d, which the Debian
   CLI does not scan, so the probe read unhardened CLI settings and the
   check failed for LAM. The php_hardening entry now also mounts the ini
   into /etc/php/8.4/cli/conf.d. No other role mounts it into a Debian
   per-SAPI directory; the rest use the shared scan dir of the official
   image or of Alpine. The TODO entry now names the remaining gap: the
   probe reads only the CLI copy.

2. Explanatory comment above HEALTH_RUNTIME_FLOORS (thread 4413764949):
   removed. The README already states the end-of-life rule and now also
   defines a floor as the lowest patched release of each line.

3. `apk --no-cache add` missed (thread 4415018662): PACKAGE_INSTALL_PATTERN
   required `add` right after `apk`, so `RUN apk --no-cache add php7`
   passed as an image without PHP. The apk branch now accepts options
   before `add`, like the apt branch. New tests cover the option form and
   guard that `apk` in a path or a comment is no package install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-30 08:57:21 +01:00
1 parent 87404d62be
commit 284b0873dd
6 files changed
+19 -4

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ This role checks the language runtime inside every running Docker container. It
## Overview
The role installs a check script with a `systemd` service and timer. The check fails, and the failure notifier from [`sys-ctl-alm-compose`](../sys-ctl-alm-compose/README.md) alerts, when a container runs PHP below the security floor of its release line or on an end-of-life line, or when a service that declares `php_hardening` in its `meta/volumes.yml` still reports `expose_php` or `session.upload_progress.enabled` as on. The floors live in `HEALTH_RUNTIME_FLOORS` in [main.yml](./vars/main.yml).
The role installs a check script with a `systemd` service and timer. The check fails, and the failure notifier from [`sys-ctl-alm-compose`](../sys-ctl-alm-compose/README.md) alerts, when a container runs PHP below the security floor of its release line or on an end-of-life line, or when a service that declares `php_hardening` in its `meta/volumes.yml` still reports `expose_php` or `session.upload_progress.enabled` as on. The floors, the lowest patched release of each `major.minor` line, live in `HEALTH_RUNTIME_FLOORS` in [main.yml](./vars/main.yml).
## Cosmos
+1 -1
View File
@@ -1,6 +1,6 @@
# To-dos
- Extend `HEALTH_RUNTIME_FLOORS`, the container probe and `make security-runtimes` to `node`, `python`, `ruby` and `java`.
- Verify the web SAPI where an image splits it from the CLI. `web-app-lam` mounts the hardening ini into `/etc/php/8.4/apache2/conf.d`, which the Debian CLI does not scan, so `php` in that container reads the CLI configuration instead of the one Apache loads.
- Verify the web SAPI where an image splits it from the CLI. `web-app-lam` mounts the hardening ini into both `/etc/php/8.4/apache2/conf.d` and `/etc/php/8.4/cli/conf.d`, and the probe reads only the CLI copy, so a wrong Apache target would still pass.
- Run `make security-runtimes` in a scheduled CI workflow.
- Scan the pinned images for CVEs (Trivy or Grype) in the scheduled CI and report new criticals as issues.
-1
View File
@@ -1,6 +1,5 @@
---
system_service_id: sys-ctl-hlth-runtime
# Lowest patched release per runtime line; a line older than every listed one is end of life
HEALTH_RUNTIME_FLOORS:
php:
"8.2": "8.2.35"
+2
View File
@@ -6,3 +6,5 @@ php_hardening:
mounts:
- service: lam
target: /etc/php/8.4/apache2/conf.d/zz-infinito-hardening.ini
- service: lam
target: /etc/php/8.4/cli/conf.d/zz-infinito-hardening.ini
@@ -66,6 +66,20 @@ class TestPhpRuntime(TestCase):
)
)
def test_apk_options_before_add_still_name_the_line(self):
self.assertEqual(
runtime.php_runtime(["RUN apk --no-cache add php7 php7-fpm"]),
_php(lines=["7"]),
)
def test_apk_in_a_path_or_comment_is_no_package_install(self):
for instruction in (
"RUN rm -rf /var/cache/apk/* && cp php7 /srv/add",
"RUN echo php7 # apk cache: add nothing",
):
with self.subTest(instruction=instruction):
self.assertIsNone(runtime.php_runtime([instruction]))
def test_a_pinned_alpine_package_names_its_line(self):
found = runtime.php_runtime(["RUN apk add --no-cache php84=8.4.27-r0 nginx;"])
self.assertEqual(found, _php(lines=["8.4"]))
+1 -1
View File
@@ -21,7 +21,7 @@ USER_AGENT = "infinito-nexus-runtime-report"
PHP_VERSION_PATTERN = re.compile(r"\bPHP_VERSION=([0-9][^\s\"';]*)")
PHP_BASE_PATTERN = re.compile(r"\bPHP_BASE=(\d+\.\d+)(?![\d.])")
PACKAGE_INSTALL_PATTERN = re.compile(
r"\b(?:apk\s+add|(?:apt-get|apt|dnf|microdnf|yum)\s+(?:-\S+\s+)*install)\b"
r"\b(?:apk\s+(?:-\S+\s+)*add|(?:apt-get|apt|dnf|microdnf|yum)\s+(?:-\S+\s+)*install)\b"
)
PHP_PACKAGE_PATTERN = re.compile(
r"(?<!\S)(?:[\w.+]+-)*php(\d)(?:\.?(\d))?(?:-[\w.+-]+)?(?:=\S*?)?(?=[;&|\\]*(?:\s|$))"