#!/bin/bash
#
# Perform vulnerability scan of a container image built by OBS using Trivy.

# Strict mode
set -euo pipefail

TOPDIR="/usr/src/packages"
OUTPUTDIR="$TOPDIR/DOCKER"

# Helper functions
log() {
    echo "$@" >&2
}
check_required_environment_variables() {
    local required_environment_variable
    for required_environment_variable in "$@"; do
        if [[ -z ${!required_environment_variable:-} ]]; then
            log "Missing environment variable '$required_environment_variable'"
            return 1
        fi
    done
}
containerinfos() {
    find "$OUTPUTDIR" -type f -name '*.containerinfo'
}

check_required_environment_variables BUILD_DIR BUILD_ROOT

log "Retrieving container image metadata"
CONTAINERINFO="$(find "$OUTPUTDIR" -name '*.containerinfo' -print -quit)"
CONTAINER_NAME="$(basename "$CONTAINERINFO" | sed 's/\.containerinfo$//')"
CONTAINER_TAG="$(python3 -c \
    'import json, sys; print(json.load(sys.stdin)["tags"][0])' \
    <"$CONTAINERINFO")"
log "Container image tag to check: $CONTAINER_TAG"

log "Starting Docker"
"$BUILD_DIR/startdockerd" \
    --root "$BUILD_ROOT" \
    --webserver "$TOPDIR/SOURCES/repos" \
    --webserver-upload "$TOPDIR/SOURCES/repos/UPLOAD"

# Load the final built image into Docker, this is based on build-recipe-docker
# https://github.com/openSUSE/obs-build/blob/master/build-recipe-docker#L152-L170
DOCKER_CMD="chroot $BUILD_ROOT docker"
if [[ -z "$($DOCKER_CMD images -q "$CONTAINER_TAG")" ]]; then
    log "Importing container image to Docker daemon"
    for image_path in $(find "$OUTPUTDIR" -regextype egrep \
        -regex ".*\.(tgz|tar|tar\.xz|tar\.gz)$" -print); do
        echo "Loading container image ${image_path##*/}"
        if test -L "$image_path"; then
            # copy into build root
            cp -L "$image_path" "$image_path.lnk"
            mv "$image_path.lnk" "$image_path"
        fi
        # Inspect the content of the image to decide if this is a layered image
        # or a filesystem one. We need to know if we will "docker load" it or
        # "docker import" it.
        if tar -tf $image_path | grep -q "^manifest.json"; then
            $DOCKER_CMD load --input $image_path
        else
            echo "Layered images not supported, FS-based image required"
            exit 1
        fi
    done
fi
# In some environments, the image is tagged with the `localhost/` prefix
# But when it is tagged with this prefix, we cannot reference images without it
if [[ -z "$($DOCKER_CMD images -q "$CONTAINER_TAG")" &&
    -n "$($DOCKER_CMD images -q "localhost/$CONTAINER_TAG")" ]]; then
    log "Tagging localhost/$CONTAINER_TAG as $CONTAINER_TAG"
    $DOCKER_CMD tag "localhost/$CONTAINER_TAG" "$CONTAINER_TAG"
fi

log "Scanning image with Trivy"
TRIVY_RESULT="$(mktemp)"
trivy image \
    --cache-dir /usr/share/trivy/cache \
    --format cosign-vuln \
    --offline-scan \
    --skip-db-update \
    --skip-java-db-update \
    --output "$TRIVY_RESULT" \
    "$CONTAINER_TAG"

log "Stopping Docker"
"$BUILD_DIR/startdockerd" \
    --root "$BUILD_ROOT" \
    --kill

# Convert file to the Cosign Vulnerability Scan Record Attestation Spec format
# https://github.com/sigstore/cosign/blob/main/specs/COSIGN_VULN_ATTESTATION_SPEC.md
# NOTE: The subject will be filled by OBS during publishing
# This command merges the files, avoid --argjson due to error using large files
OUTPUT_FILE="$OUTPUTDIR/$CONTAINER_NAME.trivy_vuln.intoto.json"
jq -s '.[1].predicate = .[0] | .[1]' "$TRIVY_RESULT" - >"$OUTPUT_FILE" <<EOF
{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1",
  "subject": [
    {
      "name": "",
      "digest": {
        "sha256": ""
      }
    }
  ],
  "predicate": {}
}
EOF

log "Cleaning up"
rm "$TRIVY_RESULT"
