Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21517b25e7
|
||
|
|
a3d680f130 | ||
|
|
57dffdd9a3 | ||
|
|
9e2c45e79a | ||
|
|
fbeec65cb5 | ||
|
|
69898922be
|
||
|
|
59eb5294fe | ||
|
|
16079c5ca8 | ||
|
|
578c7ea0eb | ||
|
|
53c04937b0 | ||
|
|
b09ced056f
|
||
|
|
fb232fb6fe |
No files matched your search
@@ -332,6 +332,7 @@ These components are shared across all modules -- you don't need to recreate the
|
||||
| `secrets_scan` job | Gitleaks (all modules) |
|
||||
| `sast_scan` job | Semgrep (all modules, opt-in) |
|
||||
| `docker_build_push` job | Build + health check + Trivy scan + GHCR push |
|
||||
| `docker_scan` job | PR image build + Trivy scan (including supplemental images), JSON artifacts, no publishing |
|
||||
| `health_check` command | Container health check smoke test (all Docker modules) |
|
||||
| `update_cd_repo` job | GitOps CD repo update |
|
||||
| `github_release` job | Annotated git tags + GitHub Releases (auto-enabled with `contexts.github`) |
|
||||
|
||||
@@ -6,7 +6,7 @@ Latin: *munitor* (fortifier, protector) -- KofTwentyTwo's unified CircleCI orb f
|
||||
| --- | --- |
|
||||
| **Tier** | Internal until classified -- listed under *Awaiting classification* in the KofTwentyTwo standards [MAINTAINERS.md](https://github.com/KofTwentyTwo/standards/blob/main/MAINTAINERS.md); Product-tier requirements are being adopted |
|
||||
| **Standards** | Targets [KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards) 1.0.0 |
|
||||
| **Orb** | [`kof22/munitor`](https://circleci.com/developer/orbs/orb/kof22/munitor), latest stable `0.3.3` |
|
||||
| **Orb** | [`kof22/munitor`](https://circleci.com/developer/orbs/orb/kof22/munitor), latest stable `0.3.6` |
|
||||
| **Security** | [SECURITY.md](SECURITY.md) |
|
||||
| **Contributing** | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
||||
|
||||
@@ -70,16 +70,16 @@ Munitor validates branch names and will fail on non-conforming names.
|
||||
|
||||
Munitor is a public CircleCI orb: any CircleCI organization can use it without forking. This checklist covers what a new consumer needs, for example a Java/Maven web service. A QQQ application should also read the [QQQ adoption checklist](https://github.com/QRun-IO/qqq/blob/develop/docs/migration/adoption-checklist.adoc) (section 7 covers release tooling).
|
||||
|
||||
**Version.** Use `kof22/munitor@0.3.3` (or `@0.3` to float on the 0.3 line) in `.circleci/config.yml`, and the same value as `orb_version` in `.munitor.yml`. Keep the setup orb and the generated pipeline on the same line.
|
||||
**Version.** Use `kof22/munitor@0.3.6` (or `@0.3` to float on the 0.3 line) in `.circleci/config.yml`, and the same value as `orb_version` in `.munitor.yml`. Keep the setup orb and the generated pipeline on the same line.
|
||||
|
||||
### 1. Repository files
|
||||
|
||||
- `.circleci/config.yml`: the setup config from [Quick Start](#quick-start), pinned to `kof22/munitor@0.3.3`.
|
||||
- `.circleci/config.yml`: the setup config from [Quick Start](#quick-start), pinned to `kof22/munitor@0.3.6`.
|
||||
- `.munitor.yml`: pick a [pipeline type](#pipeline-types). A Java service is `java-webapp`. KofTwentyTwo's Website-Backend is a working example; a generalized version:
|
||||
|
||||
```yaml
|
||||
pipeline: java-webapp
|
||||
orb_version: "0.3.3"
|
||||
orb_version: "0.3.6"
|
||||
image_name: my-service
|
||||
java_version: "21"
|
||||
docker:
|
||||
@@ -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`
|
||||
|
||||
@@ -490,7 +490,7 @@ Each workflow runs on specific branches and includes jobs based on your config:
|
||||
|
||||
| Workflow | Branches | Jobs |
|
||||
|----------|----------|------|
|
||||
| `pr-checks` | `feature/*`, `hotfix/*` | Build, test, lint, coverage, security scans |
|
||||
| `pr-checks` | `feature/*`, `hotfix/*` | Build, test, lint, coverage, security scans, container image scan (Docker modules) |
|
||||
| `develop` | `develop` | All pr-checks + Docker build/push + CD update (dev env) |
|
||||
| `staging` | `staging` | All pr-checks + Docker build/push + CD update (staging env) |
|
||||
| `release-candidate` | `release/*` | All pr-checks + Docker, CD (staging), SonarCloud, SBOM, GitHub Release (pre-release) |
|
||||
@@ -498,11 +498,20 @@ Each workflow runs on specific branches and includes jobs based on your config:
|
||||
|
||||
For `validate-cd-repo` and `argocd-apps`, all three workflows (`pr-checks`, `develop`, `release`) run the same 5-job set: yaml-lint, kustomize-validate, kubesec-scan, kube-linter, secrets-scan.
|
||||
|
||||
For `java-webapp`, `gradle-webapp`, `node-api`, and `node-webapp`, PRs run
|
||||
`image-scan` after `build-and-test`: build the application and any supplemental
|
||||
images locally, then fail on HIGH/CRITICAL CVEs with the same Trivy policy and
|
||||
repository-root `.trivyignore` as release builds. JSON reports are saved as
|
||||
artifacts. This job does not log in, publish images, reserve tags, or update CD.
|
||||
Release candidates already scan through `docker-build-push` and do not run a
|
||||
second image scan job.
|
||||
|
||||
### Job Dependency Graph (node-api pr-checks)
|
||||
|
||||
```
|
||||
checkout
|
||||
--> build-and-test
|
||||
--> image-scan (local build + Trivy, including supplemental images)
|
||||
--> code-quality
|
||||
--> coverage
|
||||
--> security-scan
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# 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
|
||||
`build-and-test` in the four application PR templates. It restores the existing
|
||||
feature/hotfix version metadata, builds locally, and applies the release Trivy
|
||||
HIGH/CRITICAL policy from the repository root. Supplemental images use
|
||||
`push: false`; the command defaults to publishing for existing callers. Main
|
||||
and supplemental JSON reports are artifacts. Release candidates already scan
|
||||
through `docker_build_push`, so no duplicate job was added.
|
||||
|
||||
Verified: native CircleCI orb pack/validate, ShellCheck on all source scripts
|
||||
and the changed tests, template structure/filters/parameter wiring, packed
|
||||
template synchronization, and `git diff --check`. Requested make and BATS runs
|
||||
were attempted but Git Bash cannot start in this sandbox (CreateFileMapping,
|
||||
Win32 error 5); yamllint is not installed. New behavioral tests therefore need
|
||||
a Linux/CI run, including real Docker/Trivy integration. No commit or publish.
|
||||
The Second Brain vault is outside writable roots; continuity is recorded here.
|
||||
|
||||
## Release 0.3.3 (2026-10-11)
|
||||
|
||||
PR22 (00f349c) fixes release version collisions. Website-Backend #65/#66 (0.1.24)
|
||||
@@ -66,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.
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
## 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).
|
||||
|
||||
- [x] Review publishing CI fixes and merge PR19 to develop.
|
||||
- [x] Publish v0.3.2 from main.
|
||||
- [ ] Publish v0.3.3 (atomic release versions); bump Website-Backend/Frontend pins to 0.3.3.
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
description: >
|
||||
Build, scan, and push supplemental Docker images alongside the main application image.
|
||||
Each supplemental image shares the same version tag. Requires docker_push_ghcr to have
|
||||
run first (for registry auth and Trivy installation).
|
||||
Each supplemental image shares the same version tag. Requires run_trivy_image first
|
||||
for Trivy installation, and docker_push_ghcr for registry auth only when push is true.
|
||||
parameters:
|
||||
push:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Publish scanned images to the registry. Disable for PR scans.
|
||||
supplemental_images:
|
||||
type: string
|
||||
default: "[]"
|
||||
@@ -16,9 +20,13 @@ parameters:
|
||||
description: Container registry URL.
|
||||
steps:
|
||||
- run:
|
||||
name: Build and push supplemental images
|
||||
name: Build and scan supplemental images (optionally push)
|
||||
environment:
|
||||
PUSH_IMAGES: << parameters.push >>
|
||||
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
|
||||
IMAGE_NAME: << parameters.image_name >>
|
||||
REGISTRY: << parameters.registry >>
|
||||
command: << include(scripts/build_push_supplemental.sh) >>
|
||||
- store_artifacts:
|
||||
path: /tmp/trivy-supplemental-results
|
||||
destination: trivy-supplemental-scans
|
||||
@@ -0,0 +1,49 @@
|
||||
description: >
|
||||
Build and scan application and supplemental images without publishing.
|
||||
Requires the source and version metadata workspace from build-and-test.
|
||||
parameters:
|
||||
image_name:
|
||||
type: string
|
||||
description: Docker image name (without registry prefix).
|
||||
registry:
|
||||
type: string
|
||||
default: ghcr.io/KofTwentyTwo
|
||||
description: Container registry URL.
|
||||
resource_class:
|
||||
type: enum
|
||||
enum:
|
||||
- medium
|
||||
- large
|
||||
default: large
|
||||
description: Machine resource class.
|
||||
docker_layer_caching:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Enable Docker Layer Caching for faster builds.
|
||||
supplemental_images:
|
||||
type: string
|
||||
default: "[]"
|
||||
description: JSON array of supplemental images to build and scan.
|
||||
executor:
|
||||
name: machine
|
||||
resource_class: << parameters.resource_class >>
|
||||
docker_layer_caching: << parameters.docker_layer_caching >>
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Restore version metadata
|
||||
command: <<include(scripts/restore_version_metadata.sh)>>
|
||||
- generate_dockerfile
|
||||
- docker_build:
|
||||
image_name: << parameters.image_name >>
|
||||
registry: << parameters.registry >>
|
||||
- restore_trivy_cache
|
||||
- run_trivy_image:
|
||||
image_tag: << parameters.registry >>/<< parameters.image_name >>:${DOCKER_TAG}
|
||||
- save_trivy_cache
|
||||
- build_push_supplemental:
|
||||
supplemental_images: << parameters.supplemental_images >>
|
||||
image_name: << parameters.image_name >>
|
||||
registry: << parameters.registry >>
|
||||
push: false
|
||||
@@ -27,6 +27,19 @@ IMAGE="${IMAGE_NAME:?IMAGE_NAME not set}"
|
||||
REGISTRY="${REGISTRY:-ghcr.io/KofTwentyTwo}"
|
||||
VERSION="${PROJECT_VERSION:?PROJECT_VERSION not set -- run export_version_vars first}"
|
||||
ENV_TAG="${DOCKER_ENV_TAG:-}"
|
||||
# CircleCI renders a boolean orb parameter as "1"/"0" in a step environment, not
|
||||
# "true"/"false", so accept both spellings. Unset means push (release builds).
|
||||
case "$(echo "${PUSH_IMAGES-true}" | tr '[:upper:]' '[:lower:]')" in
|
||||
true | 1 | yes | on) PUSH_IMAGES=true ;;
|
||||
false | 0 | no | off | "") PUSH_IMAGES=false ;;
|
||||
*)
|
||||
echo "ERROR: PUSH_IMAGES must be true/false (or 1/0), got '${PUSH_IMAGES}'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# run_trivy_image installs here in a previous CircleCI step.
|
||||
export PATH="${HOME}/bin:${PATH}"
|
||||
mkdir -p /tmp/trivy-supplemental-results
|
||||
|
||||
COUNT=$(echo "${IMAGES_JSON}" | jq 'length')
|
||||
echo "Found ${COUNT} supplemental image(s) to build."
|
||||
@@ -76,26 +89,33 @@ for i in $(seq 0 $((COUNT - 1))); do
|
||||
fi
|
||||
docker build "${BUILD_ARGS[@]}" .
|
||||
|
||||
if [[ -n "${ENV_TAG}" ]]; then
|
||||
if [[ "${PUSH_IMAGES}" == "true" && -n "${ENV_TAG}" ]]; then
|
||||
ENV_FULL_TAG="${REGISTRY}/${DERIVED_NAME}:${ENV_TAG}"
|
||||
docker tag "${FULL_TAG}" "${ENV_FULL_TAG}"
|
||||
fi
|
||||
|
||||
# Trivy scan
|
||||
echo "Scanning ${FULL_TAG} with Trivy ..."
|
||||
trivy image --severity HIGH,CRITICAL --exit-code 1 "${FULL_TAG}"
|
||||
trivy image --format json --output "/tmp/trivy-supplemental-results/${NAME}.json" \
|
||||
--severity HIGH,CRITICAL --exit-code 1 "${FULL_TAG}" || {
|
||||
EXIT_CODE=$?
|
||||
trivy image --severity HIGH,CRITICAL "${FULL_TAG}"
|
||||
exit "${EXIT_CODE}"
|
||||
}
|
||||
|
||||
# Push
|
||||
echo "Pushing ${FULL_TAG} ..."
|
||||
docker push "${FULL_TAG}"
|
||||
if [[ "${PUSH_IMAGES}" == "true" ]]; then
|
||||
echo "Pushing ${FULL_TAG} ..."
|
||||
docker push "${FULL_TAG}"
|
||||
|
||||
if [[ -n "${ENV_TAG}" ]]; then
|
||||
echo "Pushing ${ENV_FULL_TAG} ..."
|
||||
docker push "${ENV_FULL_TAG}"
|
||||
if [[ -n "${ENV_TAG}" ]]; then
|
||||
echo "Pushing ${ENV_FULL_TAG} ..."
|
||||
docker push "${ENV_FULL_TAG}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "--- Done: ${DERIVED_NAME} ---"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "All supplemental images built, scanned, and pushed."
|
||||
echo "All supplemental images built and scanned (push=${PUSH_IMAGES})."
|
||||
@@ -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
|
||||
|
||||
@@ -366,6 +366,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
@@ -694,6 +708,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
@@ -1037,6 +1065,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
@@ -1406,6 +1448,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
|
||||
@@ -16,6 +16,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
|
||||
@@ -16,6 +16,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
|
||||
@@ -31,6 +31,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
|
||||
@@ -31,6 +31,20 @@ workflows:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/docker_scan:
|
||||
name: image-scan
|
||||
image_name: ${MUNITOR_IMAGE_NAME}
|
||||
registry: ${MUNITOR_DOCKER_REGISTRY}
|
||||
##IF_SUPPLEMENTAL##
|
||||
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
|
||||
##ENDIF_SUPPLEMENTAL##
|
||||
requires:
|
||||
- build-and-test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /feature\/.*/
|
||||
- /hotfix\/.*/
|
||||
- munitor/secrets_scan:
|
||||
name: secrets-scan
|
||||
filters:
|
||||
|
||||
@@ -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 ]
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
# Exercise image scanning with local tool stubs; no Docker daemon or network needed.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SRC_SCRIPTS="$(cd "${SCRIPT_DIR}/../src/scripts" && pwd)"
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
PASS=0
|
||||
FAIL=0
|
||||
pass() { echo "PASS"; PASS=$((PASS + 1)); }
|
||||
fail() { echo "FAIL"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
mkdir -p "${WORK}/bin" "${WORK}/home/bin" "${WORK}/repo"
|
||||
cat > "${WORK}/bin/docker" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'docker %s\n' "$*" >> "${CALL_LOG}"
|
||||
EOF
|
||||
cat > "${WORK}/home/bin/trivy" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
[[ -f .trivyignore ]] || exit 99
|
||||
printf 'trivy %s\n' "$*" >> "${CALL_LOG}"
|
||||
# Redirect the production /tmp output path into the test workspace.
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if [[ "$1" == --output ]]; then
|
||||
printf '{"Results":[]}\n' > "${REPORT_DIR}/$(basename "$2")"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
exit "${SCAN_EXIT:-0}"
|
||||
EOF
|
||||
chmod +x "${WORK}/bin/docker" "${WORK}/home/bin/trivy"
|
||||
export PATH="${WORK}/bin:${PATH}" HOME="${WORK}/home"
|
||||
export CALL_LOG="${WORK}/calls" REPORT_DIR="${WORK}"
|
||||
export IMAGE_NAME=app REGISTRY=ghcr.io/example CIRCLE_SHA1=abcdef123456
|
||||
export SUPPLEMENTAL_IMAGES_JSON='[{"name":"migrations","dockerfile":"Dockerfile.migrations"},{"name":"worker","base":"example/base:1"}]'
|
||||
cd "${WORK}/repo"
|
||||
touch .trivyignore
|
||||
|
||||
echo "=== PR image scan tests ==="
|
||||
echo -n " TEST: feature metadata survives workspace restore and supplies Docker scan tag... "
|
||||
export BASH_ENV="${WORK}/build-env" CIRCLE_BRANCH=feature/image-scan
|
||||
export GITVERSION_MAJOR=1 GITVERSION_MINOR=2 GITVERSION_PATCH=3
|
||||
export GITVERSION_SHA=abcdef123456
|
||||
bash "${SRC_SCRIPTS}/export_version_vars.sh" > "${WORK}/version.log"
|
||||
bash "${SRC_SCRIPTS}/persist_version_metadata.sh" >> "${WORK}/version.log"
|
||||
export BASH_ENV="${WORK}/scan-env"
|
||||
bash "${SRC_SCRIPTS}/restore_version_metadata.sh" >> "${WORK}/version.log"
|
||||
bash "${SRC_SCRIPTS}/docker_build.sh" >> "${WORK}/version.log"
|
||||
# shellcheck disable=SC1090
|
||||
source "${BASH_ENV}"
|
||||
export IMAGE_TAG='ghcr.io/example/app:${DOCKER_TAG}'
|
||||
if bash "${SRC_SCRIPTS}/run_trivy_image.sh" >> "${WORK}/version.log" &&
|
||||
grep -q 'trivy image .*--severity HIGH,CRITICAL --exit-code 1 ghcr.io/example/app:1.2.3-image-scan-abcdef123456-SNAPSHOT' "${CALL_LOG}"; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
|
||||
echo -n " TEST: push=false builds and scans every supplemental without publishing... "
|
||||
: > "${CALL_LOG}"
|
||||
echo 'export DOCKER_ENV_TAG=develop' >> "${BASH_ENV}"
|
||||
if PUSH_IMAGES=false bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
||||
[[ $(grep -c '^docker build ' "${CALL_LOG}") == 2 ]] &&
|
||||
[[ $(grep -c '^trivy image .*--severity HIGH,CRITICAL --exit-code 1 ' "${CALL_LOG}") == 2 ]] &&
|
||||
! grep -Eq '^docker (push|login|tag|buildx) ' "${CALL_LOG}" &&
|
||||
[[ -s "${WORK}/migrations.json" && -s "${WORK}/worker.json" ]]; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
|
||||
echo -n " TEST: default push still publishes version and environment tags... "
|
||||
: > "${CALL_LOG}"
|
||||
unset PUSH_IMAGES
|
||||
if bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
||||
[[ $(grep -c '^docker push ' "${CALL_LOG}") == 4 ]]; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
|
||||
# Regression (0.3.5): CircleCI renders the boolean "push" parameter as "1"/"0" in
|
||||
# the step environment, and release builds silently skipped the push.
|
||||
for push in 1 true TRUE; do
|
||||
echo -n " TEST: push=${push} (release builds) publishes every supplemental image... "
|
||||
: > "${CALL_LOG}"
|
||||
if PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
||||
[[ $(grep -c '^docker push ' "${CALL_LOG}") == 4 ]] &&
|
||||
grep -q '^docker push ghcr.io/example/app-migrations:' "${CALL_LOG}" &&
|
||||
grep -q '^docker push ghcr.io/example/app-worker:' "${CALL_LOG}" &&
|
||||
grep -q 'push=true' "${WORK}/supplemental.log"; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
done
|
||||
|
||||
for push in 0 false ""; do
|
||||
echo -n " TEST: push='${push}' (PR scans) publishes nothing... "
|
||||
: > "${CALL_LOG}"
|
||||
if PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" &&
|
||||
! grep -Eq '^docker (push|tag) ' "${CALL_LOG}"; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
done
|
||||
|
||||
echo -n " TEST: an unrecognised push value fails instead of guessing... "
|
||||
: > "${CALL_LOG}"
|
||||
if PUSH_IMAGES=maybe bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" 2>&1; then
|
||||
fail
|
||||
elif ! grep -Eq '^docker (build|tag|push) ' "${CALL_LOG}" && grep -q "must be true/false" "${WORK}/supplemental.log"; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
|
||||
for push in false true; do
|
||||
echo -n " TEST: push=${push} fails on HIGH/CRITICAL findings before any push... "
|
||||
: > "${CALL_LOG}"
|
||||
rm -f "${WORK}/migrations.json"
|
||||
if SCAN_EXIT=1 PUSH_IMAGES="${push}" bash "${SRC_SCRIPTS}/build_push_supplemental.sh" > "${WORK}/supplemental.log" 2>&1; then
|
||||
fail
|
||||
elif ! grep -q '^docker push ' "${CALL_LOG}" && [[ -s "${WORK}/migrations.json" ]]; then
|
||||
pass
|
||||
else
|
||||
fail
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Results: ${PASS} passed, ${FAIL} failed"
|
||||
[[ ${FAIL} -eq 0 ]]
|
||||
@@ -157,6 +157,36 @@ run_negative_context_test() {
|
||||
|
||||
echo "=== Template Rendering Tests ==="
|
||||
|
||||
# Container scans belong only to PR jobs; RC already builds and scans before release.
|
||||
for module in java-webapp gradle-webapp node-api node-webapp; do
|
||||
fixture="${FIXTURES_DIR}/${module}.munitor.yml"
|
||||
run_context_test "${module}: PR builds and scans the image" \
|
||||
"${fixture}" "pr-checks:" " develop:" 'munitor/docker_scan:'
|
||||
run_context_test "${module}: image scan is named image-scan" \
|
||||
"${fixture}" "munitor/docker_scan:" "munitor/secrets_scan:" 'name: image-scan'
|
||||
run_context_test "${module}: image scan waits for build-and-test" \
|
||||
"${fixture}" "munitor/docker_scan:" "munitor/secrets_scan:" '\- build-and-test'
|
||||
run_negative_context_test "${module}: no duplicate scan outside PR workflow" \
|
||||
"${fixture}" " develop:" "END_OF_CONFIG" 'munitor/docker_scan:'
|
||||
run_context_test "${module}: RC already builds and scans" \
|
||||
"${fixture}" "release-candidate:" "production:" 'munitor/docker_build_push:'
|
||||
|
||||
echo -n " TEST: ${module}: every image scan has exactly the PR branch filters... "
|
||||
output=$(render_fixture "${fixture}")
|
||||
filters=$(yq -o=json -I=0 '[.workflows[].jobs[] | select(has("munitor/docker_scan")) | ."munitor/docker_scan".filters.branches.only]' <<< "${output}")
|
||||
if [[ "${filters}" == '[["/feature\\/.*/","/hotfix\\/.*/"]]' ]]; then
|
||||
echo "PASS"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "FAIL (unexpected scan filters: ${filters})"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
run_context_test "supplemental images are wired into the PR image scan" \
|
||||
"${FIXTURES_DIR}/java-webapp-supplemental.munitor.yml" \
|
||||
"munitor/docker_scan:" "munitor/secrets_scan:" 'supplemental_images:.*migrations'
|
||||
|
||||
# Test java-webapp renders with correct values
|
||||
run_test "java-webapp: renders image_name" \
|
||||
"${FIXTURES_DIR}/java-webapp.munitor.yml" \
|
||||
|
||||
Reference in new issue
Block a user