diff --git a/README.md b/README.md index 436be33..d701592 100644 --- a/README.md +++ b/README.md @@ -372,7 +372,7 @@ contexts: **What runs:** Maven build/test, Checkstyle, SpotBugs, PMD, JaCoCo coverage, OWASP + Trivy security, Docker build/push (Dockerfile auto-generated), gitleaks. OWASP dependency check requires the `nvd` context; if omitted, the security scan runs without NVD database enrichment. -**Dockerfile:** Auto-generated production-ready image using Eclipse Temurin JRE Alpine. Includes non-root user (uid 1001), JVM container tuning flags, Docker HEALTHCHECK, and configurable port via `health.port`. The pre-built JAR from CI is copied directly (no redundant Maven build stage). +**Dockerfile:** Auto-generated production-ready image using Eclipse Temurin JRE Alpine. Includes non-root user (uid 1001), JVM container tuning flags, Docker HEALTHCHECK, and configurable port via `health.port`. The pre-built JAR from CI is copied directly (no redundant Maven build stage). PR and release image builds pull the current base and bypass Docker layer caching for generated Java/Gradle runtime images, ensuring the Alpine package upgrade runs on every build. Node builds retain their layer cache. Trivy gates remain required. ### `terraform` diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index a796468..29abe49 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -1,5 +1,17 @@ # Munitor session state +## JVM runtime package freshness (2026-10-19) + +Website-Backend release6258 and PR scans6266/6277 reused a cached apk upgrade +layer containing vulnerable libexpat. The shared docker_build command now pulls +the base and bypasses layer caching for generated Java/Gradle runtimes, while +Node caching and all Trivy gates remain intact. Narrow behavioral tests cover +both pipeline types, alternate/missing manifests, tags, provenance, and both +PR/release job routes. All eight focused shell cases passed via the coordinating +agent; ShellCheck and git diff --check passed. Full make/BATS/orb validation and +real-image scanning remain pending. Stable release and consumer adoption remain +pending; no unchanged vulnerable image should be retried. + ## PR container scanning (2026-10-13) Uncommitted work on `feature/pr-image-scan` adds `docker_scan` after @@ -84,3 +96,5 @@ after Java installation. APT now receives noninteractive debconf and automatic needrestart settings; a stubbed integration test verifies both APT invocations. These fixes require normal feature-to-develop review and stable tag publication from main before website consumers can select the new version. Nothing deployed. + +Runtime freshness validation: coordinating agent completed make all (lint, pack, orb validation, scripts,277template assertions) and all7BATS tests in Ubuntu. Real consumer image scan remains required after release. diff --git a/docs/TODO.md b/docs/TODO.md index 4d1d0e3..641d742 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -2,6 +2,8 @@ ## Pending +- [ ] Release JVM runtime cache fix and adopt the stable orb in Website-Backend; verify fresh image Trivy before release. + - [ ] Run PR image-scan script/template/BATS tests on Linux and verify a consumer PR with Docker/Trivy before publishing. - [x] Add PR-only image scans for the four application templates, supplemental scan-only mode, JSON artifacts, and regression tests (uncommitted). diff --git a/src/scripts/docker_build.sh b/src/scripts/docker_build.sh index bee2465..91dee60 100755 --- a/src/scripts/docker_build.sh +++ b/src/scripts/docker_build.sh @@ -23,7 +23,16 @@ munitor_header "docker_build (${FULL_IMAGE}:${VERSION})" munitor_check_tool docker --version # Build with version tag; add env tag when set (e.g., develop, staging) -BUILD_CMD=(docker build -f "${DOCKERFILE}" -t "${FULL_IMAGE}:${VERSION}") +BUILD_CMD=(docker build --pull -f "${DOCKERFILE}" -t "${FULL_IMAGE}:${VERSION}") +# Generated JVM images only copy a prebuilt JAR. Re-run their package upgrades: +# a cached apk upgrade layer can retain vulnerable packages indefinitely. +CONFIG_FILE="${MUNITOR_CONFIG:-.munitor.yml}" +if [[ -f "${CONFIG_FILE}" ]]; then + munitor_check_tool yq --version + case "$(yq -r '.pipeline' "${CONFIG_FILE}")" in + java-webapp|gradle-webapp) BUILD_CMD+=(--no-cache) ;; + esac +fi if [[ -n "${ENV_TAG}" ]]; then BUILD_CMD+=(-t "${FULL_IMAGE}:${ENV_TAG}") fi diff --git a/tests/docker_build.bats b/tests/docker_build.bats new file mode 100644 index 0000000..84b3a79 --- /dev/null +++ b/tests/docker_build.bats @@ -0,0 +1,6 @@ +#!/usr/bin/env bats + +@test "PR and release JVM builds refresh packages without disabling Node caching or Trivy" { + run bash "${BATS_TEST_DIRNAME}/test_docker_build.sh" + [ "$status" -eq 0 ] +} diff --git a/tests/test_docker_build.sh b/tests/test_docker_build.sh new file mode 100644 index 0000000..63c96b5 --- /dev/null +++ b/tests/test_docker_build.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Assert the actual shared PR/release build command without a Docker daemon. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +WORK=$(mktemp -d) +trap 'rm -rf "${WORK}"' EXIT +mkdir -p "${WORK}/bin" "${WORK}/repo" +cat > "${WORK}/bin/docker" <<'MOCK' +#!/usr/bin/env bash +[[ "$1" == --version ]] && exit 0 +printf '%s\n' "$@" > "${CALL_LOG}" +MOCK +cat > "${WORK}/bin/yq" <<'MOCK' +#!/usr/bin/env bash +[[ "$1" == --version ]] && exit 0 +[[ "$1" == -r && "$2" == .pipeline ]] || exit 2 +sed -n 's/^pipeline: //p' "$3" +MOCK +chmod +x "${WORK}/bin/docker" "${WORK}/bin/yq" +export PATH="${WORK}/bin:${PATH}" +export CALL_LOG="${WORK}/calls" BASH_ENV="${WORK}/env" +export IMAGE_NAME=app REGISTRY=ghcr.io/example PROJECT_VERSION=1.2.3 +export CIRCLE_SHA1=abc123 DOCKER_ENV_TAG=develop +cd "${WORK}/repo" +for pipeline in java-webapp gradle-webapp node-api node-webapp; do + printf 'pipeline: %s\n' "${pipeline}" > .munitor.yml + bash "${PROJECT_DIR}/src/scripts/docker_build.sh" > "${WORK}/output" + grep -qx -- '--pull' "${CALL_LOG}" + if [[ "${pipeline}" == java-webapp || "${pipeline}" == gradle-webapp ]]; then + grep -qx -- '--no-cache' "${CALL_LOG}" + else + ! grep -qx -- '--no-cache' "${CALL_LOG}" + fi + grep -qx 'org.opencontainers.image.revision=abc123' "${CALL_LOG}" + grep -qx 'ghcr.io/example/app:1.2.3' "${CALL_LOG}" + grep -qx 'ghcr.io/example/app:develop' "${CALL_LOG}" + echo "PASS: ${pipeline} cache policy, provenance and tags" +done +mv .munitor.yml 'custom config.yml' +printf 'pipeline: java-webapp\n' > 'custom config.yml' +MUNITOR_CONFIG='custom config.yml' bash "${PROJECT_DIR}/src/scripts/docker_build.sh" > "${WORK}/output" +grep -qx -- '--no-cache' "${CALL_LOG}" +echo 'PASS: alternate manifest path' +bash "${PROJECT_DIR}/src/scripts/docker_build.sh" > "${WORK}/output" +grep -qx -- '--pull' "${CALL_LOG}" +! grep -qx -- '--no-cache' "${CALL_LOG}" +echo 'PASS: standalone build without manifest' +for job in docker_scan docker_build_push; do + grep -q '^ - docker_build:' "${PROJECT_DIR}/src/jobs/${job}.yml" + grep -q '^ - run_trivy_image:' "${PROJECT_DIR}/src/jobs/${job}.yml" + echo "PASS: ${job} uses shared builder and retains Trivy" +done