GeoIntel release gates / Trusted workflow source (pull_request) Canceled after 0s
GeoIntel release gates / Compile, test, contracts and builds (pull_request) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (pull_request) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (pull_request) Canceled after 0s
GeoIntel release gates / AI image, SBOM and container scan (pull_request) Canceled after 0s
100 lines
3.2 KiB
Bash
100 lines
3.2 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
TARGET_IMAGE="${1:-geointel-ci:local}"
|
|
OUTPUT="${2:-artifacts/geointel-container-vulnerabilities.json}"
|
|
TRIVY_IMAGE="aquasec/trivy:0.70.0@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e"
|
|
CACHE_VOLUME="${GEOINTEL_TRIVY_CACHE_VOLUME:-geointel-trivy-cache}"
|
|
PYTHON_CMD="${PYTHON_BIN:-python3}"
|
|
IGNORE_FILE="$(mktemp)"
|
|
CONTAINER_IGNORE_FILE="/tmp/geointel-trivy-ignore.txt"
|
|
POLICY_CONTAINER=""
|
|
SCANNER_ENGINE_ARGS=()
|
|
|
|
cleanup() {
|
|
rm -f "$IGNORE_FILE"
|
|
if [ -n "$POLICY_CONTAINER" ]; then
|
|
docker rm -f "$POLICY_CONTAINER" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
case "$OUTPUT" in
|
|
/*|*..*)
|
|
echo "Scan output must be a repository-relative path without '..'." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
configure_scanner_engine() {
|
|
local docker_host="${DOCKER_HOST:-}"
|
|
if [[ "$docker_host" == tcp://* ]]; then
|
|
local port="${docker_host##*:}"
|
|
if ! [[ "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then
|
|
echo "Unsupported TCP Docker host for nested scanner: $docker_host" >&2
|
|
exit 1
|
|
fi
|
|
SCANNER_ENGINE_ARGS=(
|
|
--add-host host.docker.internal:host-gateway
|
|
-e "DOCKER_HOST=tcp://host.docker.internal:${port}"
|
|
)
|
|
elif [[ -z "$docker_host" || "$docker_host" == unix:///var/run/docker.sock ]]; then
|
|
SCANNER_ENGINE_ARGS=(-v /var/run/docker.sock:/var/run/docker.sock)
|
|
else
|
|
echo "Unsupported Docker host for nested scanner: $docker_host" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
docker image inspect "$TARGET_IMAGE" >/dev/null
|
|
mkdir -p "$ROOT/$(dirname "$OUTPUT")"
|
|
"$PYTHON_CMD" "$ROOT/scripts/verify_security_exceptions.py"
|
|
"$PYTHON_CMD" "$ROOT/scripts/verify_security_exceptions.py" \
|
|
--print-container-ids > "$IGNORE_FILE"
|
|
|
|
configure_scanner_engine
|
|
docker volume create "$CACHE_VOLUME" >/dev/null
|
|
|
|
# Keep the complete report, including vulnerabilities without an available fix.
|
|
# The pinned scanner receives access only to the selected Docker engine. Native
|
|
# runs use the local socket; Gitea TCP-DinD runs use the daemon's host-gateway.
|
|
docker run --rm \
|
|
"${SCANNER_ENGINE_ARGS[@]}" \
|
|
-v "$CACHE_VOLUME:/root/.cache/trivy" \
|
|
"$TRIVY_IMAGE" \
|
|
image \
|
|
--image-src docker \
|
|
--scanners vuln \
|
|
--timeout 20m \
|
|
--skip-version-check \
|
|
--format json \
|
|
"$TARGET_IMAGE" > "$ROOT/$OUTPUT"
|
|
|
|
# Release policy: fixed HIGH/CRITICAL findings block the build. Unfixed findings
|
|
# remain visible in the full report and must be reviewed before release. Copy
|
|
# the verified exception IDs into a stopped scanner container before starting
|
|
# it; `docker cp` avoids mounting the job checkout through the nested runner.
|
|
POLICY_CONTAINER="$(docker create \
|
|
"${SCANNER_ENGINE_ARGS[@]}" \
|
|
-v "$CACHE_VOLUME:/root/.cache/trivy" \
|
|
"$TRIVY_IMAGE" \
|
|
image \
|
|
--image-src docker \
|
|
--scanners vuln \
|
|
--timeout 20m \
|
|
--skip-version-check \
|
|
--ignore-unfixed \
|
|
--ignorefile "$CONTAINER_IGNORE_FILE" \
|
|
--skip-files /usr/local/bin/gosu \
|
|
--severity HIGH,CRITICAL \
|
|
--exit-code 1 \
|
|
"$TARGET_IMAGE")"
|
|
docker cp "$IGNORE_FILE" "$POLICY_CONTAINER:$CONTAINER_IGNORE_FILE"
|
|
docker start -a "$POLICY_CONTAINER"
|
|
docker rm "$POLICY_CONTAINER" >/dev/null
|
|
POLICY_CONTAINER=""
|
|
|
|
test -s "$ROOT/$OUTPUT"
|
|
echo "Container vulnerability report written to $OUTPUT"
|