Files
gis/test/acceptance/scan.sh
Bakhtiyar Issakhmetov ca87d6961b test(acceptance): reproducible Stage 8 acceptance package
Add test/acceptance/ so the customer's reviewers can re-run what the
Stage 8 documents claim on an isolated stand built from this branch:

- compose.acceptance.yml + stand.sh: compose project gis-acceptance
  (app 18080, postgres 15432, minio 19000/19001, rabbitmq mgmt 15672),
  POSTGIS_PLATFORM / MINIO_IMAGE knobs for arm64 and amd64 hosts.
- functional.sh: the 89 test-stand checks of Приложение 7 section 5
  (F01-F38, N01-N38, L02-L05, L07-L15), own prefixed test data,
  PostGIS/MinIO confirmation via the stand containers, full cleanup.
- lp_gen.py / lp_run.sh / lp_verify.py: synthetic ЛП inputs, a run from
  stage 0, and an independent numpy re-implementation of every node that
  compares all 15 outputs pixel by pixel; read-only (GET) mode works
  against any stand, in row/column windows for 22219x14977 rasters.
- load.sh + seed.sh: scenarios S1-S6 of Приложение 3 with hey 0.1.5
  running inside the stand network; CSV/JSON summaries, criteria K1-K4.
- scan.sh: govulncheck (source and binary), gosec, trivy fs/image,
  golangci-lint; fails on any High/Critical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:37:28 +05:00

132 lines
7.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Security re-scan (Приложение 4 «Отчет об устранении уязвимостей»):
# 1. govulncheck, source mode — vulnerabilities reachable from the code
# 2. govulncheck -mode=binary — the binary shipped in the image
# 3. gosec — static analysis (HIGH severity counted)
# 4. trivy fs — dependencies, secrets, IaC misconfig
# 5. trivy image — the application image (built here)
# 6. golangci-lint (if installed) — code quality, informational only
#
# test/acceptance/scan.sh
#
# Environment: SCAN_IMAGE (default gis-scan:local, built from
# build/package/Dockerfile), SKIP_IMAGE=1 (no docker: skip 2 via image and 5;
# the binary for 2 is then built locally with cgo + GDAL).
# govulncheck has no severity scale: every vulnerability reachable from the
# code (source) or present in the binary symbol table is counted as High.
# Prints High/Critical per tool; exit code 1 if any High/Critical is found.
# Raw reports: test/acceptance/results/scan-<timestamp>/.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="$HERE/results/scan-$STAMP"; mkdir -p "$OUT"
SCAN_IMAGE="${SCAN_IMAGE:-gis-scan:local}"
SKIP_IMAGE="${SKIP_IMAGE:-0}"
GOBIN_DIR="$(go env GOPATH 2>/dev/null)/bin"
PATH="$PATH:$GOBIN_DIR"
cd "$ROOT"
ROWS=""; FAIL=0
row() { # tool target high critical note
ROWS="${ROWS}$(printf '%-26s | %-40s | %5s | %8s | %s' "$1" "$2" "$3" "$4" "$5")"$'\n'
case "$3$4" in *[!0-9]*) ;; *) [ "$3" -eq 0 ] && [ "$4" -eq 0 ] || FAIL=1;; esac
}
have() { command -v "$1" >/dev/null 2>&1; }
log() { echo "[$(date +%H:%M:%S)] $*"; }
log "commit $(git rev-parse --short HEAD 2>/dev/null || echo '?'), $(go version)"
# --- govulncheck (source) -------------------------------------------------
gv_count() { # json stream -> number of distinct OSV ids with a call trace (reachable)
jq -rs '[.[] | select(.finding != null) | .finding | select((.trace // []) | length > 0)
| select(.trace[0].function != null) | .osv] | unique | length' "$1"
}
gv_ids() { jq -rs '[.[] | select(.finding != null) | .finding | select(.trace[0].function != null) | .osv] | unique | join(", ")' "$1"; }
if have govulncheck; then
log "govulncheck ./... (source)"
govulncheck -format json ./... > "$OUT/govulncheck-source.json" 2> "$OUT/govulncheck-source.err" || true
n="$(gv_count "$OUT/govulncheck-source.json")"
mods="$(jq -rs '[.[] | select(.finding != null) | .finding.osv] | unique | length' "$OUT/govulncheck-source.json")"
row "govulncheck (source)" "./..." "$n" 0 "reachable: ${n}$( [ "$n" -gt 0 ] && echo " ($(gv_ids "$OUT/govulncheck-source.json"))"); in dependencies but not called: $((mods - n))"
else row "govulncheck (source)" "./..." "n/a" "n/a" "not installed (go install golang.org/x/vuln/cmd/govulncheck@latest)"; fi
# --- application image + binary -------------------------------------------
BIN=""
if [ "$SKIP_IMAGE" != 1 ] && have docker; then
log "docker build -t $SCAN_IMAGE -f build/package/Dockerfile ."
docker build -q -t "$SCAN_IMAGE" -f build/package/Dockerfile . > "$OUT/docker-build.txt"
cid="$(docker create "$SCAN_IMAGE")"
docker cp "$cid:/usr/local/bin/gis" "$OUT/gis" >/dev/null; docker rm "$cid" >/dev/null
BIN="$OUT/gis"; BIN_SRC="image $SCAN_IMAGE:/usr/local/bin/gis"
else
log "go build (cgo + GDAL) for binary-mode scan"
CGO_ENABLED=1 go build -trimpath -o "$OUT/gis" ./cmd/gis; BIN="$OUT/gis"; BIN_SRC="local build"
fi
# --- govulncheck (binary) -------------------------------------------------
if have govulncheck; then
log "govulncheck -mode=binary ($BIN_SRC)"
govulncheck -mode=binary -format json "$BIN" > "$OUT/govulncheck-binary.json" 2> "$OUT/govulncheck-binary.err" || true
n="$(gv_count "$OUT/govulncheck-binary.json")"
gov="$(go version "$BIN" 2>/dev/null | awk '{print $2}')"
row "govulncheck (binary)" "$BIN_SRC" "$n" 0 "built with $gov; vulnerable symbols present: $n$( [ "$n" -gt 0 ] && echo " ($(gv_ids "$OUT/govulncheck-binary.json"))")"
fi
# --- gosec -----------------------------------------------------------------
if have gosec; then
log "gosec ./..."
gosec -quiet -fmt json -out "$OUT/gosec.json" ./... 2> "$OUT/gosec.err" || true
hi="$(jq '[.Issues[]? | select(.severity=="HIGH")] | length' "$OUT/gosec.json")"
me="$(jq '[.Issues[]? | select(.severity=="MEDIUM")] | length' "$OUT/gosec.json")"
lo="$(jq '[.Issues[]? | select(.severity=="LOW")] | length' "$OUT/gosec.json")"
ns="$(jq '.Stats.nosec // 0' "$OUT/gosec.json")"
row "gosec" "./... ($(jq '.Stats.files' "$OUT/gosec.json") files)" "$hi" 0 "MEDIUM $me, LOW $lo; #nosec-annotated $ns"
else row "gosec" "./..." "n/a" "n/a" "not installed"; fi
# --- trivy ------------------------------------------------------------------
trivy_counts() { # json -> "high critical"
jq -r '[.Results[]? | (.Vulnerabilities[]?, .Secrets[]?, .Misconfigurations[]?) | .Severity] as $s
| "\([$s[] | select(.=="HIGH")] | length) \([$s[] | select(.=="CRITICAL")] | length)"' "$1"
}
if have trivy; then
log "trivy fs (vuln, secret, misconfig)"
trivy fs --quiet --scanners vuln,secret,misconfig --format json --output "$OUT/trivy-fs.json" \
--skip-dirs test/acceptance/results --skip-dirs graft . 2> "$OUT/trivy-fs.err" || true
read -r h c <<< "$(trivy_counts "$OUT/trivy-fs.json")"
all="$(jq '[.Results[]? | (.Vulnerabilities[]?, .Secrets[]?, .Misconfigurations[]?)] | length' "$OUT/trivy-fs.json")"
row "trivy fs" "repository (go.mod, Dockerfile, compose)" "$h" "$c" "all severities: $all"
if [ -n "$BIN" ] && [ "$SKIP_IMAGE" != 1 ] && have docker; then
log "trivy image $SCAN_IMAGE"
trivy image --quiet --format json --output "$OUT/trivy-image.json" "$SCAN_IMAGE" 2> "$OUT/trivy-image.err" || true
read -r h c <<< "$(trivy_counts "$OUT/trivy-image.json")"
all="$(jq '[.Results[]? | .Vulnerabilities[]?] | length' "$OUT/trivy-image.json")"
os="$(jq -r '.Metadata.OS | "\(.Family) \(.Name)"' "$OUT/trivy-image.json")"
row "trivy image" "$SCAN_IMAGE ($os)" "$h" "$c" "all severities: $all"
fi
else row "trivy" "-" "n/a" "n/a" "not installed"; fi
# --- golangci-lint (informational) ------------------------------------------
if have golangci-lint; then
log "golangci-lint run ./..."
note=""
golangci-lint run --output.json.path "$OUT/golangci-lint.json" ./... > "$OUT/golangci-lint.txt" 2>&1 || true
if grep -q "unsupported version of the configuration" "$OUT/golangci-lint.txt"; then
# .golangci.yml is in the v1 format; a v2 binary refuses it — use the default linters
note=" (.golangci.yml is v1 format, $(golangci-lint version 2>/dev/null | awk '{print $4}') ran with --no-config)"
golangci-lint run --no-config --output.json.path "$OUT/golangci-lint.json" ./... > "$OUT/golangci-lint.txt" 2>&1 || true
fi
n="$(jq '[.Issues[]?] | length' "$OUT/golangci-lint.json" 2>/dev/null || echo '?')"
row "golangci-lint" "./..." "-" "-" "issues: $n$note; no severity scale, informational"
fi
echo
printf '%-26s | %-40s | %5s | %8s | %s\n' "Tool" "Target" "High" "Critical" "Notes"
printf '%s\n' "---------------------------+------------------------------------------+-------+----------+------------------------------"
printf '%s' "$ROWS"
echo
if [ "$FAIL" = 0 ]; then echo "RESULT: 0 High / 0 Critical -> PASS"; else echo "RESULT: High/Critical findings present -> FAIL"; fi
echo "raw reports: $OUT"
exit "$FAIL"