94 lines
4.7 KiB
Bash
94 lines
4.7 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
repo="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
|
|
image="${1:-${SWITCH_IMAGE:-ludarium/eden-controller:0.4.9-rc.1}}"
|
|
container="${2:-${SWITCH_CONTAINER_NAME:-Ludarium-Switch}}"
|
|
evidence="${3:?set an evidence directory below /mnt/user/appdata/ludarium-candidate or /tmp}"
|
|
vex="$repo/deploy/security/eden-controller-0.4.9.openvex.json"
|
|
cache="${LUDARIUM_RELEASE_TOOL_CACHE:-/mnt/user/appdata/ludarium-candidate/release-tools}"
|
|
|
|
case "$evidence" in
|
|
/mnt/user/appdata/ludarium-candidate/*|/tmp/*) ;;
|
|
*) echo "Eden evidence must remain below /mnt/user/appdata/ludarium-candidate or /tmp" >&2; exit 2 ;;
|
|
esac
|
|
|
|
command -v docker >/dev/null || { echo "docker is required" >&2; exit 3; }
|
|
command -v jq >/dev/null || { echo "jq is required" >&2; exit 3; }
|
|
[ -f "$vex" ] || { echo "version-bound Eden OpenVEX document is missing" >&2; exit 3; }
|
|
mkdir -p "$evidence" "$cache/bin" "$cache/downloads" "$cache/grype-db"
|
|
|
|
grype="$cache/bin/grype-0.116.1"
|
|
if [ ! -x "$grype" ]; then
|
|
archive="$cache/downloads/grype_0.116.1_linux_amd64.tar.gz"
|
|
checksum="0122df7b655981abe547ad3d2190d65551dac6a2bfc80b4dc2a989b5d0587458"
|
|
if [ ! -f "$archive" ] || ! printf '%s %s\n' "$checksum" "$archive" | sha256sum -c - >/dev/null 2>&1; then
|
|
command -v curl >/dev/null || { echo "curl is required to acquire the pinned scanner" >&2; exit 3; }
|
|
rm -f "$archive.part"
|
|
curl --fail --location --silent --show-error --retry 5 --retry-all-errors \
|
|
--connect-timeout 15 --max-time 300 \
|
|
https://github.com/anchore/grype/releases/download/v0.116.1/grype_0.116.1_linux_amd64.tar.gz \
|
|
-o "$archive.part"
|
|
printf '%s %s\n' "$checksum" "$archive.part" | sha256sum -c - >/dev/null
|
|
mv "$archive.part" "$archive"
|
|
fi
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT INT TERM
|
|
tar -xzf "$archive" -C "$work" grype
|
|
install -m 0755 "$work/grype" "$grype"
|
|
fi
|
|
|
|
container_image="$(docker inspect "$container" --format '{{.Image}}')"
|
|
expected_image="$(docker image inspect "$image" --format '{{.Id}}')"
|
|
[ "$container_image" = "$expected_image" ] || {
|
|
echo "running Eden container does not use the exact requested image" >&2
|
|
exit 4
|
|
}
|
|
[ "$(docker inspect "$container" --format '{{.State.Health.Status}}')" = "healthy" ] || {
|
|
echo "Eden container is not healthy" >&2
|
|
exit 4
|
|
}
|
|
[ "$(docker inspect "$container" --format '{{.RestartCount}}')" = "0" ] || {
|
|
echo "Eden candidate has restarted" >&2
|
|
exit 4
|
|
}
|
|
docker inspect "$container" --format '{{range .Mounts}}{{println .Destination .RW}}{{end}}' |
|
|
grep -F '/games false' >/dev/null || { echo "Eden /games mount is not read-only" >&2; exit 4; }
|
|
|
|
docker exec "$container" /opt/ludarium/audit-eden-runtime.sh | tee "$evidence/runtime-audit.txt"
|
|
cp "$vex" "$evidence/openvex.json"
|
|
|
|
run_grype() {
|
|
if command -v timeout >/dev/null; then
|
|
timeout 600 env GRYPE_DB_CACHE_DIR="$cache/grype-db" "$@"
|
|
else
|
|
env GRYPE_DB_CACHE_DIR="$cache/grype-db" "$@"
|
|
fi
|
|
}
|
|
run_grype "$grype" db update
|
|
run_grype "$grype" "docker:$image" -o json --file "$evidence/grype-raw.json"
|
|
run_grype "$grype" "docker:$image" --vex "$vex" --fail-on critical \
|
|
-o json --file "$evidence/grype-vex.json"
|
|
|
|
raw_critical="$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' "$evidence/grype-raw.json")"
|
|
active_critical="$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' "$evidence/grype-vex.json")"
|
|
ignored_critical="$(jq '[.ignoredMatches[] | select(.vulnerability.severity == "Critical")] | length' "$evidence/grype-vex.json")"
|
|
[ "$raw_critical" -gt 0 ] || { echo "expected raw Critical evidence is absent; review the VEX baseline" >&2; exit 5; }
|
|
[ "$active_critical" = "0" ] || { echo "unresolved Critical Eden findings remain" >&2; exit 5; }
|
|
[ "$ignored_critical" = "$raw_critical" ] || { echo "VEX does not account for every raw Critical match" >&2; exit 5; }
|
|
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT INT TERM
|
|
jq -r '.matches[] | select(.vulnerability.severity == "Critical") | .vulnerability.id' \
|
|
"$evidence/grype-raw.json" | sort -u > "$work/raw-ids"
|
|
jq -r '.ignoredMatches[] | select(.vulnerability.severity == "Critical") | .vulnerability.id' \
|
|
"$evidence/grype-vex.json" | sort -u > "$work/vex-ids"
|
|
cmp -s "$work/raw-ids" "$work/vex-ids" || { echo "VEX Critical ID set differs from raw evidence" >&2; exit 5; }
|
|
|
|
raw_high="$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' "$evidence/grype-raw.json")"
|
|
unique_critical="$(wc -l < "$work/raw-ids" | tr -d ' ')"
|
|
sha256sum "$evidence/runtime-audit.txt" "$evidence/openvex.json" \
|
|
"$evidence/grype-raw.json" "$evidence/grype-vex.json" > "$evidence/SHA256SUMS"
|
|
printf 'Eden security gate passed image=%s digest=%s raw_critical=%s unique_critical=%s active_critical=0 raw_high=%s\n' \
|
|
"$image" "$expected_image" "$raw_critical" "$unique_critical" "$raw_high"
|