diff --git a/.github/workflows/cve-scan-and-patching.yml b/.github/workflows/cve-scan-and-patching.yml index eeb1540..cad03db 100644 --- a/.github/workflows/cve-scan-and-patching.yml +++ b/.github/workflows/cve-scan-and-patching.yml @@ -5,6 +5,9 @@ permissions: on: push: + paths: + - .github/workflows/cve-scan-and-patching.yml + - CVEs/** #branches: # - "main" #schedule: @@ -142,6 +145,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: patch-report-${{ steps.patching.outputs.IMAGE_TO_PATCH_NORMALIZED }}.md + if-no-files-found: ignore path: | CVEs/reports/*.patched.md scan_post_patch: diff --git a/CVEs/Makefile b/CVEs/Makefile index e008730..70381e4 100644 --- a/CVEs/Makefile +++ b/CVEs/Makefile @@ -32,7 +32,7 @@ concat-multiple-kfd-images-list: rm $(PATCH_FILE_IMAGE_LIST_TO_PATCHING).tmp patch: - DOCKER_CONFIG="${DOCKER_CONFIG}" DRY_RUN=$(DRY_RUN) ../scripts/patch_images_with_copacetic.sh -i "$(IMAGE_TO_PATCH)" -l "$(PATCH_FILE_IMAGE_LIST_TO_PATCHING)" -o "$(PATCH_REPORT_OUTPUT_FILE)" + DOCKER_CONFIG="${DOCKER_CONFIG}" DRY_RUN=$(DRY_RUN) ./patch_images_with_copacetic.sh -i "$(IMAGE_TO_PATCH)" -l "$(PATCH_FILE_IMAGE_LIST_TO_PATCHING)" -o "$(PATCH_REPORT_OUTPUT_FILE)" scan-post-patch: @for version in $(KFD_VERSIONS); do \ @@ -67,11 +67,11 @@ generate-image-list-from-manifests: sort -u $(KFD_VERSION)/images.tmp.txt -o $(KFD_VERSION)/images.txt && rm $(KFD_VERSION)/images.tmp.txt trivy-download-db: - trivy image --download-db-only --db-repository registry.sighup.io/fury-secured/aquasecurity/trivy-db:2 - trivy image --download-java-db-only --java-db-repository registry.sighup.io/fury-secured/aquasecurity/trivy-java-db:1 + trivy image --download-db-only --db-repository registry.sighup.io/fury-secured/aquasecurity/trivy-db:2 --no-progress + trivy image --download-java-db-only --java-db-repository registry.sighup.io/fury-secured/aquasecurity/trivy-java-db:1 --no-progress scan-vulns: - ../scripts/scan_vuln.sh -v $(KFD_VERSION) -l $(LIST_FILE) -o $(OUTPUT_FILE); + ./scan_vuln.sh -v $(KFD_VERSION) -l $(LIST_FILE) -o $(OUTPUT_FILE); diff --git a/CVEs/logging.sh b/CVEs/logging.sh new file mode 100644 index 0000000..30059c7 --- /dev/null +++ b/CVEs/logging.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +NC='\033[0m' # No Color +BLACK='\033[0;30m' +RED='\033[0;31m' +GREEN='\033[0;32m' +ORANGE='\033[0;33m' +CYAN='\033[0;36m' +YELLOW='\033[1;33m' +WHITE='\033[1;37m' + +function info() { + echo -e ">>\t${CYAN}[INFO]${NC} $(date +"%Y-%m-%dT%H:%M:%S.%3NZ"): $*" +} + +function warn() { + echo -e ">>\t[${ORANGE}WARN${NC}] $(date +"%Y-%m-%dT%H:%M:%S.%3NZ"): $*" +} + +function error() { + echo -e ">>\t[${RED}ERROR${NC}] $(date +"%Y-%m-%dT%H:%M:%S.%3NZ"): $*" +} + +function success() { + echo -e ">>\t${GREEN}SUCCESS${NC} $(date +"%Y-%m-%dT%H:%M:%S.%3NZ"): $*" +} + +function fail() { + echo -e ">>\t${RED}FAIL${NC} $(date +"%Y-%m-%dT%H:%M:%S.%3NZ"): $*" && exit 1 +} \ No newline at end of file diff --git a/scripts/patch_images_with_copacetic.sh b/CVEs/patch_images_with_copacetic.sh similarity index 65% rename from scripts/patch_images_with_copacetic.sh rename to CVEs/patch_images_with_copacetic.sh index 2f40249..3d158a6 100755 --- a/scripts/patch_images_with_copacetic.sh +++ b/CVEs/patch_images_with_copacetic.sh @@ -1,5 +1,7 @@ #!/bin/bash +source logging.sh + IMAGE_TO_PATCH= FILE_WITH_IMAGES_LIST_TO_PATCH= PATCH_REPORT_OUTPUT_FILE= @@ -38,7 +40,7 @@ PATCH_ERROR_OUTPUT_FILE="$LOG_OUTPUT_DIR/patch-error.log" if [ -z "$(docker ps -f name=buildkitd -q)" ] then - echo ">>>>>>>>>>>>>>>>>>> Start buildkitd instance for COPA <<<<<<<<<<<<<<<<<<<<<" + info "Start buildkitd instance for COPA" if [ -n "$DOCKER_CONFIG" ] then docker_config_extra_args="-v ${DOCKER_CONFIG}:/root/.docker" @@ -49,17 +51,8 @@ fi mkdir -p "$TRIVY_SCAN_OUTPUT_DIR" "$COPA_PATCH_OUTPUT_DIR" "$DOCKERFILE_OUTPUT_DIR" "$LOG_OUTPUT_DIR" echo -n "" > "${PATCH_ERROR_OUTPUT_FILE}" -{ -[[ -n $IMAGE_TO_PATCH ]] && printf "# %s\n\n" $IMAGE_TO_PATCH -printf "Last updated %s\n\n" "$(date +'%Y-%m-%d')"; -printf "## CVEs patched\n\n" ; -echo "| Source Image | Source Image Hash |CVE | Severity | Description | Patched Image| Patched Image Hash |" -echo "| --- | --- | --- | --- |--- | --- | --- |" -} > "${PATCH_REPORT_OUTPUT_FILE}" - - -REGISTRY_BASE_URL='registry.sighup.io/fury' -REGISTRY_SECURED_BASE_URL='registry.sighup.io/fury-secured' +REGISTRY_BASE_URL='registry.sighup.io/fury/' +REGISTRY_SECURED_BASE_URL='registry.sighup.io/fury-secured/' function patch_image() { local image="$1" @@ -67,9 +60,9 @@ function patch_image() { image_to_patch="$image" DOCKER_PULL_OUTPUT_FILE="$LOG_OUTPUT_DIR/${image_to_patch//[:\/]/_}-image-pull.log" - if ! docker pull "$image_to_patch" >> "${DOCKER_PULL_OUTPUT_FILE}" 2>&1 + if ! docker pull "$image_to_patch" > "${DOCKER_PULL_OUTPUT_FILE}" 2>&1 then - echo ">>>>>>>>>>>>>>>>>>> Failed pull $image_to_patch <<<<<<<<<<<<<<<<<<<<<" + error "Failed pull $image_to_patch" cat "${DOCKER_PULL_OUTPUT_FILE}" return 1 fi @@ -77,22 +70,30 @@ function patch_image() { TRIVY_SCAN_OUTPUT_FILE=$TRIVY_SCAN_OUTPUT_DIR/${image_to_patch//[:\/]/_}.json COPA_REPORT_OUTPUT_FILE=$COPA_PATCH_OUTPUT_DIR/${image_to_patch//[:\/]/_}.vex.json COPA_PATCHING_LOG_FILE=$COPA_PATCH_OUTPUT_DIR/${image_to_patch//[:\/]/_}.log - echo ">>>>>>>>>>>>>>>>>>> Scan $image_to_patch for CVEs <<<<<<<<<<<<<<<<<<<<<" + info "Scan $image_to_patch for CVEs" trivy image --skip-db-update --skip-java-db-update --scanners vuln -q --vuln-type os --ignore-unfixed -f json -o "${TRIVY_SCAN_OUTPUT_FILE}" "$image_to_patch" # --platform=linux/amd64 - echo ">>>>>>>>>>>>>>>>>>> Clean trivy scan cache for $image_to_patch <<<<<<<<<<<<<<<<<<<<<" + info "Clean trivy scan cache for $image_to_patch" trivy clean --scan-cache - echo ">>>>>>>>>>>>>>>>>>> Patching CVEs for $image_to_patch <<<<<<<<<<<<<<<<<<<<<" + info "Patching CVEs for $image_to_patch" copa patch -r "${TRIVY_SCAN_OUTPUT_FILE}" -i "$image_to_patch" --format="openvex" --output "$COPA_REPORT_OUTPUT_FILE" -a tcp://127.0.0.1:8888 2>&1 | tee "$COPA_PATCHING_LOG_FILE" copa_exit_code=${PIPESTATUS[0]} if [ "$copa_exit_code" -eq 0 ] then - FIXED_CVES=$(jq '.statements[] | select(.status=="fixed") | .vulnerability."@id"' -r < "$COPA_REPORT_OUTPUT_FILE" | sort -r) - echo ">>>>>>>>>>>>>>>>>>> ${FIXED_CVES} patched in $image_to_patch-patched <<<<<<<<<<<<<<<<<<<<<" + { + [[ -n $IMAGE_TO_PATCH ]] && printf "# %s\n\n" $IMAGE_TO_PATCH + printf "Last updated %s\n\n" "$(date +'%Y-%m-%d')"; + printf "## CVEs patched\n\n" ; + echo "| Source Image | Source Image Hash |CVE | Severity | Description | Patched Image| Patched Image Hash |" + echo "| --- | --- | --- | --- |--- | --- | --- |" + } > "${PATCH_REPORT_OUTPUT_FILE}" + + FIXED_CVES=$(jq '.statements[] | select(.status=="fixed") | .vulnerability."@id"' -r < "$COPA_REPORT_OUTPUT_FILE" | sort -r ) + info "CVEs patched in $image_to_patch-patched: ${FIXED_CVES//[$'\r\n']/ }" DOCKER_LABELS= image_patched_hash=$(docker inspect "$image_to_patch-patched" --format '{{.Id}}') - echo ">>>>>>>>>>>>>>>>>>> $image_to_patch-patched hash: ${image_patched_hash} <<<<<<<<<<<<<<<<<<<<<" - echo ">>>>>>>>>>>>>>>>>>> Update patching report for $image_to_patch <<<<<<<<<<<<<<<<<<<<<" + info "$image_to_patch-patched hash: ${image_patched_hash}" + info "Update patching report for $image_to_patch" image_to_patch_hash=$(jq -r '.Metadata.ImageID' < "$TRIVY_SCAN_OUTPUT_FILE") for FIXED_CVE in ${FIXED_CVES[@]} do @@ -105,7 +106,7 @@ function patch_image() { --arg image_patched_hash "$image_patched_hash" \ '[try .Results[].Vulnerabilities[] | select(.VulnerabilityID==$cve)][0] | "|" + $image_to_patch + " | " + $image_to_patch_hash + " | " + .VulnerabilityID + " | " + .Severity + " | " + .Title + " | " + $image_patched + "|" + $image_patched_hash + "|"' < "$TRIVY_SCAN_OUTPUT_FILE">> "$PATCH_REPORT_OUTPUT_FILE" done - echo ">>>>>>>>>>>>>>>>>>> Tag $image_to_patch-patched as secured image: $secured_image <<<<<<<<<<<<<<<<<<<<<" + info "Tag $image_to_patch-patched as secured image: $secured_image" echo "FROM $image_to_patch-patched" | DOCKER_BUILDKIT=0 docker build \ ${DOCKER_LABELS} \ --label io.sighup.secured.image.created="$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")" \ @@ -116,39 +117,45 @@ function patch_image() { sed -i'.unsecured' s#"$image_to_patch-patched"#"$secured_image"# "$PATCH_REPORT_OUTPUT_FILE" sed -i'.unsecured' s#"$image_patched_hash"#"$secured_labeled_image_hash"# "$PATCH_REPORT_OUTPUT_FILE" rm "$PATCH_REPORT_OUTPUT_FILE.unsecured" - echo ">>>>>>>>>>>>>>>>>>> Push secure image: $secured_image <<<<<<<<<<<<<<<<<<<<<" + info "Push secure image: $secured_image" [[ $DRY_RUN -eq 0 ]] && docker push "$secured_image" - echo ">>>>>>>>>>>>>>>>>>> CLEANUP $image_to_patch-patched <<<<<<<<<<<<<<<<<<<<<" + info "Cleanup $image_to_patch-patched" buildctl --addr tcp://127.0.0.1:8888 prune docker rmi -f "$image_to_patch-patched" - echo ">>>>>>>>>>>>>>>>>>> CLEANUP $secured_image <<<<<<<<<<<<<<<<<<<<<" + info "cleanup $secured_image" docker rmi -f "$secured_image" + success "$secured_image pushed with id: ${secured_labeled_image_hash}" else if [ "$image_to_patch" != "$secured_image" ] then - echo ">>>>>>>>>>>>>>>>>>> No CVEs patched in $image_to_patch <<<<<<<<<<<<<<<<<<<<<" - echo ">>>>>>>>>>>>>>>>>>> Tag $image_to_patch as secured image: $secured_image <<<<<<<<<<<<<<<<<<<<<" + warn "No CVEs patched in $image_to_patch" + warn "Tag $image_to_patch as secured image: $secured_image" docker tag "$image_to_patch" "$secured_image" - echo ">>>>>>>>>>>>>>>>>>> Push secured image: $secured_image <<<<<<<<<<<<<<<<<<<<<" - [[ $DRY_RUN -eq 0 ]] && docker push "$secured_image" - echo ">>>>>>>>>>>>>>>>>>> CLEANUP $secured_image <<<<<<<<<<<<<<<<<<<<<" + [[ $DRY_RUN -eq 0 ]] && info "Push secured image: $secured_image" && docker push "$secured_image" + info "cleanup $secured_image" docker rmi -f "$secured_image" - echo ">>>>>>>>>>>>>>>>>>> Update patching error log <<<<<<<<<<<<<<<<<<<<<" + info "Update patching error log" echo "$secured_image: $(awk -F'Error:' '$0 ~ /Error:/ {print $2}' "$COPA_PATCHING_LOG_FILE")" >> "$PATCH_ERROR_OUTPUT_FILE" else - echo ">>>>>>>>>>>>>>>>>>> $image_to_patch is the same of $secured_image <<<<<<<<<<<<<<<<<<<<<" + error "$image_to_patch is the same of $secured_image" fi fi - echo ">>>>>>>>>>>>>>>>>>> CLEANUP $image_to_patch <<<<<<<<<<<<<<<<<<<<<" + info "Cleanup $image_to_patch" docker rmi -f "$image_to_patch" echo "" echo "================================================================" echo "" -} -[[ -n $IMAGE_TO_PATCH ]] && patch_image "$IMAGE_TO_PATCH" && exit 0 + return 0 +} -while IFS= read -r image; do - patch_image "$image" -done < "$FILE_WITH_IMAGES_LIST_TO_PATCH" +if [ -n "$IMAGE_TO_PATCH" ] +then + patch_image "$IMAGE_TO_PATCH" +else + [[ ! -f $FILE_WITH_IMAGES_LIST_TO_PATCH ]] && fail "Missing image list files" + while IFS= read -r image; do + patch_image "$image" + done < "$FILE_WITH_IMAGES_LIST_TO_PATCH" +fi \ No newline at end of file diff --git a/scripts/scan_vuln.sh b/CVEs/scan_vuln.sh similarity index 94% rename from scripts/scan_vuln.sh rename to CVEs/scan_vuln.sh index 19b8af1..88488fd 100755 --- a/scripts/scan_vuln.sh +++ b/CVEs/scan_vuln.sh @@ -27,8 +27,9 @@ while getopts ":v:l:o:" o; do done shift $((OPTIND-1)) +source logging.sh -[[ -z $KFD_VERSION ]] && echo "ERROR: Missing KFD VERSION" && exit 1 +[[ -z $KFD_VERSION ]] && fail "Missing KFD VERSION" [[ -z $IMAGE_LIST_FILE ]] && IMAGE_LIST_FILE="${KFD_VERSION}/images.txt" [[ -z $SCAN_RESULT_OUTPUT_FILE ]] && SCAN_RESULT_OUTPUT_FILE="${KFD_VERSION}/CVEs.md" @@ -53,7 +54,7 @@ mkdir -p "$TRIVY_SCAN_OUTPUT_DIR" for image in $IMAGE_LIST; do TRIVY_SCAN_OUTPUT_FILE="$TRIVY_SCAN_OUTPUT_DIR/scan-${image//[:\/]/_}.json" - echo ">>>>>>>>>>>>>>>>>>> Scan $image for CVEs <<<<<<<<<<<<<<<<<<<<<" + info "Scan $image for CVEs" if ! trivy image --skip-db-update --skip-java-db-update --scanners vuln --no-progress --output "$TRIVY_SCAN_OUTPUT_FILE" --format json --severity CRITICAL "$image" then echo "$image | ERROR PROCESSING! " >> "${SCAN_ERROR_OUTPUT_FILE}"