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>
132 lines
7.4 KiB
Bash
Executable File
132 lines
7.4 KiB
Bash
Executable File
#!/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"
|