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>
This commit is contained in:
2026-10-15 22:37:28 +05:00
parent c6229c46f7
commit ca87d6961b
22 changed files with 3136 additions and 0 deletions

131
test/acceptance/scan.sh Executable file
View File

@@ -0,0 +1,131 @@
#!/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"