#!/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"