diff --git a/build-pipeline-yocto.yml.jinja2 b/build-pipeline-yocto.yml.jinja2 index b23e689a295d636c1ebcf9b47faa7b8a55cf6f7e..90fc3d4dd10a85d2c88d79b37df56ecde07c82b5 100644 --- a/build-pipeline-yocto.yml.jinja2 +++ b/build-pipeline-yocto.yml.jinja2 @@ -17,6 +17,7 @@ include: stages: - build - deploy + - security - changelog - notify - sync-mirror @@ -342,6 +343,16 @@ deploy-{{ machine }}-{{ distro }}: artifacts: true variables: &deploy-{{ machine }}-{{ distro }} <<: *build-{{ machine }}-{{ distro }} +#---------------------------------------------------- +# {{ SECO_REMOTE }}-security-{{ machine }}-{{ distro }} +#---------------------------------------------------- +security-{{ machine }}-{{ distro }}: + extends: .security + needs: + - job: deploy-{{ machine }}-{{ distro }} + artifacts: true + variables: + <<: *deploy-{{ machine }}-{{ distro }} {% if testing is defined %} #---------------------------------------------------- # {{ SECO_REMOTE }}-test-{{ machine }}-{{ distro }} @@ -399,6 +410,16 @@ deploy-{{ machine }}-{{ distro }}: artifacts: true variables: &deploy-{{ machine }}-{{ distro }} <<: *build-{{ machine }}-{{ distro }} +#---------------------------------------------------- +# {{ SECO_REMOTE }}-security-{{ machine }}-{{ distro }} +#---------------------------------------------------- +security-{{ machine }}-{{ distro }}: + extends: .security + needs: + - job: deploy-{{ machine }}-{{ distro }} + artifacts: true + variables: + <<: *deploy-{{ machine }}-{{ distro }} {% if machine == 'a62-1G-4x256M' or machine == 'c20' or machine == 'd23' or machine == 'intel' %} #---------------------------------------------------- # {{ SECO_REMOTE }}-test-{{ machine }}-{{ distro }} diff --git a/build-pipeline.yml b/build-pipeline.yml index e2b9d72bb776651719a7c70f6b479339bc724857..f9b6ef6a3a771491bfbf9729be667e091c8e3154 100644 --- a/build-pipeline.yml +++ b/build-pipeline.yml @@ -203,6 +203,47 @@ workflow: - Link_report_${BOARD}_${IMAGE_NAME}.txt - MD5_sums_${BOARD}_${IMAGE_NAME}.txt +# ----------------------------------- +# Stage: security +# ----------------------------------- +.security: + extends: + - .infrastructure + image: git.seco.com:5050/clea-os/infrastructure/ci-images/infrastructure/exein-analyzer:latest + stage: security + cache: {} + retry: 2 + timeout: 2h + tags: + - security + rules: + - if: $SECURITY_STAGE == "manual" + when: manual + allow_failure: true + - if: $SECURITY_STAGE == "auto" + allow_failure: true + script: + - | + if [ -n "$CUSTOM" ]; then + AZURE_STORAGE_SAS_TOKEN="${AZURE_STORAGE_PRIVATE_SAS_TOKEN}" + AZURE_CONTAINER_NAME="${AZURE_PRIVATE_CONTAINER_NAME}" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + if [ ! -n "$CUSTOM" ]; then + AZURE_STORAGE_SAS_TOKEN="${AZURE_STORAGE_PUBLIC_SAS_TOKEN}" + AZURE_CONTAINER_NAME="${AZURE_PUBLIC_CONTAINER_NAME}" + fi + fi + - .gitlab-ci/scripts/exein-analyzer.sh $EXEIN_API_PLATFORM_URL $EXEIN_API_KEY $AZURE_CONTAINER_NAME $AZURE_STORAGE_SAS_TOKEN + allow_failure: true + artifacts: + when: always + paths: + - "*exein*.tar.gz" + - Job_report_${BOARD}_${IMAGE_NAME}.txt + - Job_message_${BOARD}_${IMAGE_NAME}.txt + - Link_report_${BOARD}_${IMAGE_NAME}.txt + # ----------------------------------- # Stage: changelog # ----------------------------------- diff --git a/docs/add-new-project-to-pipeline.md b/docs/add-new-project-to-pipeline.md index 53f0f0e65c644ed8bdbfa8ae5e44306b79e4af4a..adb9c6225fe447fedee5a1506b73ae51ac97c751 100644 --- a/docs/add-new-project-to-pipeline.md +++ b/docs/add-new-project-to-pipeline.md @@ -181,3 +181,5 @@ level) in the custom manifest repository:<> Set whether to include the Test stage (supported values: `manual`, `auto`) * `CREATE_CHANGELOG` Set whether "changelog" job should be started. +* `SECURITY_STAGE` + Set whether to include the Test stage (supported values: `manual`, `auto`) diff --git a/manifest-pipeline-yocto.yml b/manifest-pipeline-yocto.yml index 9bd48597328b687e920852aa23fa72eba5c3084e..54b77451718673a36c0a03893c02d6b605210ab1 100644 --- a/manifest-pipeline-yocto.yml +++ b/manifest-pipeline-yocto.yml @@ -66,6 +66,7 @@ yocto-pipeline: ARTIFACTS_PATH: build_*/${IMAGES_PATH}/**/* TEST_STAGE: "auto" CREATE_CHANGELOG: "true" + SECURITY_STAGE: "auto" sdk-pipeline: extends: diff --git a/scripts/exein-analyzer.sh b/scripts/exein-analyzer.sh new file mode 100755 index 0000000000000000000000000000000000000000..0bcef4cd33e624e9fd27272302836239d6672d6c --- /dev/null +++ b/scripts/exein-analyzer.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +EXEIN_API_PLATFORM_URL=$1 +EXEIN_API_KEY=$2 +AZURE_CONTAINER_NAME=$3 +AZURE_STORAGE_SAS_TOKEN=$4 + +ListObject(){ + COSMO_HTTP_PLATFORM_URL="$EXEIN_API_PLATFORM_URL" \ + cosmo \ + --api-key=$EXEIN_API_KEY \ + object list +} + +CreateObject(){ + COSMO_HTTP_PLATFORM_URL="$EXEIN_API_PLATFORM_URL" \ + cosmo \ + --api-key=$EXEIN_API_KEY \ + object new $1 +} + +TestReport(){ + COSMO_HTTP_PLATFORM_URL="$EXEIN_API_PLATFORM_URL" \ + cosmo \ + --api-key=$EXEIN_API_KEY \ + scan new \ + --report $1 \ + --output $2 \ + $3 \ + $4 \ + linux \ + info kernel cve-check password-hash hardening security-scan static-code crypto software-bom capability +} + +# Get Link report +link_report=$(ls | grep '^Link_report') +echo "This is the Link report the job is analyzing: $link_report" + +# Get Azure from Link report +azure_path=$(grep '^http' "$link_report" | head -n 1 | cut -d '/' -f 5- | sed -n 's/\(.*\)\/seco_.*/\1/p') +echo "This is the Azure Path for report deploy: ${azure_path}" + +# Create Analyzer Object name by first checking if it exists, otherwise reuse the existing one +object_name="${CI_JOB_NAME#*-}" +echo "This is the Exein Scan Object Name: ${object_name}" +output=$(ListObject) + +# Check if the output contains the exact match +if echo "$output" | grep -qw "$object_name"; then + echo "An Exein Object with the same name exists, reusing it: $object_name" + object_id=$(ListObject | grep ${object_name} | head -n 1 | cut -d '|' -f 2 | tr -d ' ') +else + echo "No match found for the selected Exein Object Name, creating it: $object_name" + object_id=$(CreateObject ${object_name} | grep ${object_name} | head -n 1 | cut -d '|' -f 2 | tr -d ' ') +fi + +echo "This is the Exein Object ID: ${object_id}" + +scan_report_cnt=0 + +# Use grep to filter lines starting with 'http' of URL report and process each line +while read -r url; do + echo "Processing software artifact at this link: $url" + file_name=$(basename "${url%%\?*}") + + # For the moment performing scan only on WIC image + if [[ "$file_name" == *wic.bz2* ]]; then + echo "Found WIC image on which to perform Exein Analyzer scan: $file_name" + + # Download the file using curl + curl -o "${file_name}" "${url}" + bzip2 -d "$file_name" + file_name=$(echo "${file_name}" | sed 's/\.bz2//') + + # Set report and output names + report_name=$(echo "$file_name" | sed 's/seco_/&exein-analyzer-report_/' | sed 's/\..*/.pdf/') + output_name=$(echo "$file_name" | sed 's/seco_/&exein-analyzer-output_/' | sed 's/\..*/.json/') + + # Calculate the path to the file for the scan + file_path="${PWD}/${file_name}" + + echo "This is the Exein Analyzer Report Name : $report_name" + echo "This is the Exein Analyzer Output Name : $output_name" + echo "This is the Exein Analyzer Object ID : $object_id" + echo "This is the Exein Analyzer File Path : $file_path" + + # Call TestReport and check for errors + TestReport "$report_name" "$output_name" "$object_id" "$file_path" + if [ $? -eq 1 ]; then + echo "Error detected, exiting..." + exit 1 + else + scan_report_cnt=$((scan_report_cnt + 1)) + fi + else + echo "Not performing Exein Analyzer scan on SW artifacts files different from WIC images, skipping them: $file_name" + continue + fi +done < <(grep '^http' "$link_report") + +echo "Final scan report count: $scan_report_cnt" + +# Exit if the shall_exit variable is set to 1 +if [ "$shall_exit" -eq 1 ]; then + exit 0 +fi + +# Search for at least one file creted by the scan +report_file=$(find . -name "*output*image*json" -print -quit) + +# Check if any files were found and eventually create an archive +if [ -z "$report_file" ]; then + echo "Error: No files matching '*output*json' found, no scans performed" + exit 1 +else + archive_id=$(echo "$report_file" | sed -E 's/.*_([0-9A-Za-z-]+_[0-9A-Za-z]+_[0-9]{8})\.json/\1/') + report_archive_name="seco_exein-analyzer_${object_name}_${archive_id}.tar.gz" + echo "This is the name of the archive report that will be created: ${report_archive_name}" + tar -czvf $report_archive_name *output*.json *report*.pdf +fi + +# Upload the report to Azure at the same path of other artifacts in the link report +echo "Uploading Exein report to Azure" +az storage blob upload --account-name $AZURE_STORAGE_ACCOUNT \ + --sas-token $AZURE_STORAGE_SAS_TOKEN \ + --container-name $AZURE_CONTAINER_NAME \ + --file $report_archive_name \ + --name $azure_path/$report_archive_name \ + --overwrite + +# Update link report with the Exein security analysis report +current_year=$(date -u '+%Y') +current_month=$(date -u '+%m') +current_month=${current_month#0} +future_month=$(( (current_month + 6) % 12 )) +future_year=$(( current_year + (current_month + 6) / 12 )) + +# Adjust the year if the future month is 0 +if [ $future_month -eq 0 ]; then + future_month=12 + future_year=$(( future_year - 1 )) +fi + +# Format the expiration date +expire_date="${future_year}-${future_month}-01T00:00Z" + +# Generate read-only SAS token and URL +sas_report=$(az storage blob generate-sas --account-key $AZURE_STORAGE_KEY --container-name $AZURE_CONTAINER_NAME --name $azure_path/$report_archive_name --permissions r --expiry "$expire_date" --output tsv) +url_report=$(az storage blob url --container-name $AZURE_CONTAINER_NAME --name $azure_path/$report_archive_name --output tsv | sed -E 's/\?s.*//') + +# Add link to the report +case "$AZURE_CONTAINER_NAME" in + *"private"*) + eval echo -e "\$url_report?\$sas_report" >> ${link_report} + ;; + *) + eval echo -e "\$url_report" >> ${link_report} + ;; +esac + +# Count the total number of Exein scan reports and eventually throw +# an error if the total number is lower than the expected one +# We perform this check at the end because we want to upload +# something anyway since the failure is very probable with this +# version of the Exein Analyzer +file_count=$(find . -type f -name "*output*image*json" | wc -l) +if [ "$file_count" -lt "$scan_report_cnt" ]; then + echo "Error: Found $file_count files, which is less than the expected $scan_report_cnt." + exit 1 +else + echo "Success: Found expected output $scan_report_cnt files." +fi \ No newline at end of file