From 57d2d1f7e71b5ac1ac5808590cfaba35da7d9bd4 Mon Sep 17 00:00:00 2001 From: rkpattnaik780 Date: Wed, 6 Dec 2023 14:48:31 +0530 Subject: [PATCH] ci: add workflow to check for vulnerabilities in images --- .github/workflows/sec-scan.yml | 117 +++++++++++++++++++++++++++++++++ Makefile | 7 +- ci/quay_security_analysis.py | 93 ++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sec-scan.yml create mode 100644 ci/quay_security_analysis.py diff --git a/.github/workflows/sec-scan.yml b/.github/workflows/sec-scan.yml new file mode 100644 index 000000000..a1f9779f1 --- /dev/null +++ b/.github/workflows/sec-scan.yml @@ -0,0 +1,117 @@ +--- +# The aim of this GitHub workflow is to update the SECURITY.md with latest security scan results. +name: Update notebook image security reports +on: + workflow_dispatch: + inputs: + branch: + required: true + description: "Provide the name of the branch you want to update ex main, vYYYYx etc: " + # Put the scheduler on comment until automate the full release procedure + # schedule: + # - cron: "0 0 * * 5" #Scheduled every Friday +env: + SEC_SCAN_BRANCH: sec-scan-${{ github.run_id }} + BRANCH_NAME: ${{ github.event.inputs.branch || 'main' }} + RELEASE_VERSION_N: 2023b + RELEASE_VERSION_N_1: 2023a +jobs: + initialize: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Install Skopeo CLI + shell: bash + run: | + sudo apt-get -y update + sudo apt-get -y install skopeo + + # Checkout the branch + - name: Checkout branch + uses: actions/checkout@v3 + with: + ref: ${{ env.BRANCH_NAME }} + + # Create a new branch + - name: Create a new branch + run: | + echo ${{ env.SEC_SCAN_BRANCH }} + git checkout -b ${{ env.SEC_SCAN_BRANCH }} + git push --set-upstream origin ${{ env.SEC_SCAN_BRANCH }} + + update-n-version: + needs: [initialize] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Configure Git + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "GitHub Actions" + + # Get the latest weekly build commit hash: https://github.com/opendatahub-io/notebooks/commits/2023b + - name: Checkout upstream notebooks repo + uses: actions/checkout@v3 + with: + repository: opendatahub-io/notebooks.git + ref: ${{ env.RELEASE_VERSION_N }} + + - name: Retrieve latest weekly commit hash from the release branch + id: hash-n + shell: bash + run: | + echo "HASH_N=$(git rev-parse --short HEAD)" >> ${GITHUB_OUTPUT} + + # Checkout the release branch to apply the updates + - name: Checkout release branch + uses: actions/checkout@v3 + with: + ref: ${{ env.SEC_SCAN_BRANCH }} + + - name: setup python + uses: actions/setup-python@v4 + with: + python-version: '3.10' # install the python version needed + + - name: install python packages + run: | + python -m pip install --upgrade pip + pip install requests + + - name: execute py script # run trial.py + env: + HASH_N : ${{ steps.hash-n.outputs.HASH_N }} + RELEASE_VERSION_N: 2023b + run: python trial.py + + - name: Push the files + run: | + git fetch origin ${{ env.SEC_SCAN_BRANCH }} && git pull origin ${{ env.SEC_SCAN_BRANCH }} && git add . && git commit -m "Update security scans" && git push origin ${{ env.SEC_SCAN_BRANCH }} + + # Creates the Pull Request + open-pull-request: + needs: [update-n-version] + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: pull-request + uses: repo-sync/pull-request@v2 + with: + source_branch: ${{ env.SEC_SCAN_BRANCH }} + destination_branch: ${{ env.BRANCH_NAME}} + github_token: ${{ secrets.GH_TOKEN }} + pr_label: "automated pr" + pr_title: "[Digest Updater Action] Update notebook's imageStreams image tag to digest format" + pr_body: | + :rocket: This is an automated Pull Request. + + This PR updates the `manifests/base/params.env` file with the latest updated SHA digests of the notebooks (N & N-1). + Created by `/.github/workflows/notebooks-digest-updater-upstream.yaml` + + :exclamation: **IMPORTANT NOTE**: Remember to delete the ` ${{ env.SEC_SCAN_BRANCH }}` branch after merging the changes \ No newline at end of file diff --git a/Makefile b/Makefile index a23c0ed73..c3c33d580 100644 --- a/Makefile +++ b/Makefile @@ -468,4 +468,9 @@ refresh-pipfilelock-files: cd runtimes/tensorflow/ubi8-python-3.8 && pipenv lock cd runtimes/tensorflow/ubi9-python-3.9 && pipenv lock cd base/c9s-python-3.9 && pipenv lock - \ No newline at end of file + +# This is only for the workflow action +# For running manually, set the environment variables "RELEASE_VERSION_N" and "HASH_N" +.PHONY: scan-image-vulnerabilities +scan-image-vulnerabilities: + python ci/quay_security_analysis.py \ No newline at end of file diff --git a/ci/quay_security_analysis.py b/ci/quay_security_analysis.py new file mode 100644 index 000000000..b10acedc6 --- /dev/null +++ b/ci/quay_security_analysis.py @@ -0,0 +1,93 @@ +import os +import subprocess +import re + +import requests +from collections import Counter + + +IMAGES = [ + "odh-minimal-notebook-image-n", + "odh-minimal-gpu-notebook-image-n", + "odh-pytorch-gpu-notebook-image-n", + "odh-generic-data-science-notebook-image-n", + "odh-tensorflow-gpu-notebook-image-n", + "odh-trustyai-notebook-image-n" +] + +RELEASE_VERSION_N = os.environ['RELEASE_VERSION_N'] +HASH_N = os.environ['HASH_N'] + +my_dictionary = {} + +for i, image in enumerate(IMAGES): + + # Read the contents of params.env and extract the image information + with open('manifests/base/params.env', 'r') as params_file: + img_line = next(line for line in params_file if re.search(f"{image}=", line)) + img = img_line.split('=')[1].strip() + + registry = img.split('@')[0] + + # Get source tag from skopeo inspection + src_tag_cmd = f'skopeo inspect docker://{img} | jq \'.Env[] | select(startswith("OPENSHIFT_BUILD_NAME=")) | split("=")[1]\'' + src_tag = subprocess.check_output(src_tag_cmd, shell=True, text=True).strip().strip('"').replace('-amd64', '') + + regex = f"{src_tag}-{RELEASE_VERSION_N}-\\d+-{HASH_N}" + latest_tag_cmd = f'skopeo inspect docker://{img} | jq -r --arg regex "{regex}" \'.RepoTags | map(select(. | test($regex))) | .[0]\'' + + latest_tag = subprocess.check_output(latest_tag_cmd, shell=True, text=True).strip() + + digest_cmd = f'skopeo inspect docker://{registry}:{latest_tag} | jq .Digest | tr -d \'"\'' + digest = subprocess.check_output(digest_cmd, shell=True, text=True).strip() + + output = f"{registry}@{digest}" + + sha_ = output.split(":")[1] + + url = f"https://quay.io/api/v1/repository/opendatahub/workbench-images/manifest/sha256:{sha_}/security" + headers = { + "X-Requested-With": "XMLHttpRequest", + "Authorization": "Bearer 3PZX0UYX6FSENKQ14I1VTHUJ4KGBS8L5LHJ0W1RN7TPHFVQ4P0NR7VQNCZIFRC9B" + } + + response = requests.get(url, headers=headers) + data = response.json() + + vulnerabilities = [] + + for feature in data['data']['Layer']['Features']: + if(len(feature['Vulnerabilities']) > 0): + for vulnerability in feature['Vulnerabilities']: + vulnerabilities.append(vulnerability) + + severity_levels = [entry.get("Severity", "Unknown") for entry in vulnerabilities] + + # Count occurrences of each severity level + severity_counts = Counter(severity_levels) + + my_dictionary[latest_tag] = {} + + for severity, count in severity_counts.items(): + my_dictionary[latest_tag][severity] = count + +markdown_content = """# Security Scan Results + +| Image Name | Medium | Low | Unknown | High | Critical | +|------------|-------|-----|---------|------|------| +{table_content} +""" + +formatted_data = "" +for key, value in my_dictionary.items(): + formatted_data += f"| {key} |" + for severity in ['Medium', 'Low', 'Unknown', 'High', 'Critical']: + count = value.get(severity, 0) # Get count for the severity, default to 0 if not present + formatted_data += f" {count} |" + formatted_data += "\n" + +final_markdown = markdown_content.format(table_content=formatted_data) + +# Writing to the markdown file +with open("ci/security_scan_results.md", "w") as markdown_file: + markdown_file.write(final_markdown) \ No newline at end of file