From 180d6855b7f41f1e38e0cd24031422e884b30b62 Mon Sep 17 00:00:00 2001 From: Adrian Clay Lake Date: Fri, 29 Nov 2024 16:41:45 +0100 Subject: [PATCH] Rocks 1453 - Reusable test rock workflow (#294) * feat: make testing workflow reuseable --------- Co-authored-by: clay-lake Co-authored-by: zhijie-yang --- .github/workflows/Continuous-Testing.yaml | 2 +- .github/workflows/Image.yaml | 53 +--- .github/workflows/Test-Rock.yaml | 302 ++++++++++++++++++++++ .github/workflows/Tests.yaml | 262 ------------------- .github/workflows/Vulnerability-Scan.yaml | 159 +++++------- .github/workflows/_Test-OCI-Factory.yaml | 31 +++ oci/mock-rock/_releases.json | 20 +- 7 files changed, 414 insertions(+), 415 deletions(-) create mode 100644 .github/workflows/Test-Rock.yaml delete mode 100644 .github/workflows/Tests.yaml diff --git a/.github/workflows/Continuous-Testing.yaml b/.github/workflows/Continuous-Testing.yaml index b4279543..776bf68d 100644 --- a/.github/workflows/Continuous-Testing.yaml +++ b/.github/workflows/Continuous-Testing.yaml @@ -42,5 +42,5 @@ jobs: oci-image-name: "${{ matrix.source-image }}" oci-image-path: "oci/${{ matrix.name }}" date-last-scan: ${{ needs.prepare-test-matrix.outputs.last-scan }} - is-from-release: true + create-issue: true secrets: inherit diff --git a/.github/workflows/Image.yaml b/.github/workflows/Image.yaml index 5cae71fe..17b94578 100644 --- a/.github/workflows/Image.yaml +++ b/.github/workflows/Image.yaml @@ -152,7 +152,7 @@ jobs: exit 1 fi - run-build: + build-rock: needs: [prepare-build, validate-matrix] strategy: fail-fast: true @@ -167,45 +167,21 @@ jobs: lpci-fallback: true secrets: inherit - tmp-cache-job: - # TODO: This is a temporary job that will be removed when the refactored test job is merged. - # Going forward we download the built rocks from artifacts instead of cache. This job takes - # the uploaded rocks then re-caches them for compatibility. - name: Temporary step to cache rocks - runs-on: ubuntu-22.04 - needs: [prepare-build, run-build] - strategy: - fail-fast: true - matrix: ${{ fromJSON(needs.prepare-build.outputs.build-matrix) }} - steps: - - name: Download rock - uses: actions/download-artifact@v4 - with: - name: ${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }} - - - uses: actions/cache/save@v4 - with: - key: ${{ github.run_id }}-${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }} - path: ${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }} - - test: - needs: [prepare-build, run-build, tmp-cache-job] + test-rock: + needs: [prepare-build, build-rock] # TODO: Remove tmp-cache-job when removing the job tmp-cache-job - name: Test strategy: fail-fast: true matrix: ${{ fromJSON(needs.prepare-build.outputs.build-matrix) }} - uses: ./.github/workflows/Tests.yaml + uses: ./.github/workflows/Test-Rock.yaml with: - oci-image-name: "${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }}" - oci-image-path: "oci/${{ matrix.name }}" - test-from: "cache" - cache-key: ${{ github.run_id }}-${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }} + oci-archive-name: "${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }}" + trivyignore-path: "oci/${{ matrix.name }}/.trivyignore" secrets: inherit prepare-upload: runs-on: ubuntu-22.04 - needs: [prepare-build, run-build, test] + needs: [prepare-build, build-rock, test-rock] name: Prepare upload if: ${{ inputs.upload || (github.ref_name == 'main' && github.event_name == 'push') }} env: @@ -313,11 +289,9 @@ jobs: ./src/uploads/requirements.sh pip install -r src/uploads/requirements.txt -r src/image/requirements.txt - - uses: actions/cache/restore@v4 + - uses: actions/download-artifact@v4 with: - path: ${{ env.OCI_ARCHIVE_NAME }} - key: ${{ github.run_id }}-${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }} - fail-on-cache-miss: true + name: ${{ env.OCI_ARCHIVE_NAME }} - name: Name output artefact id: rename-oci-archive @@ -380,12 +354,9 @@ jobs: echo "sboms=${all_sboms_zip}" >> "$GITHUB_OUTPUT" - - name: Fetch vulnerability artifacts for hashing - uses: actions/cache/restore@v4 + - uses: actions/download-artifact@v4 with: - path: ${{ env.OCI_ARCHIVE_NAME }}${{ env.VULNERABILITY_REPORT_SUFFIX }} - key: ${{ github.run_id }}-${{ env.OCI_ARCHIVE_NAME }}${{ env.VULNERABILITY_REPORT_SUFFIX }} - fail-on-cache-miss: true + name: ${{ env.OCI_ARCHIVE_NAME }}${{ env.VULNERABILITY_REPORT_SUFFIX }} # https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md - name: Calculate artefacts hashes @@ -615,7 +586,7 @@ jobs: runs-on: ubuntu-22.04 name: Notify needs: - [prepare-build, run-build, upload, prepare-releases, generate-provenance] + [prepare-build, build-rock, upload, prepare-releases, generate-provenance] if: ${{ !cancelled() && contains(needs.*.result, 'failure') && github.event_name != 'pull_request' }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/Test-Rock.yaml b/.github/workflows/Test-Rock.yaml new file mode 100644 index 00000000..4bab48df --- /dev/null +++ b/.github/workflows/Test-Rock.yaml @@ -0,0 +1,302 @@ +name: Test rock +run-name: "Test rock - ${{ inputs.oci-image-name }} - ${{ github.ref }}" + +on: + workflow_call: + inputs: + # Workflow Parameters + oci-archive-name: + description: "OCI image artifact name." + required: true + type: string + + # Individual Test Parameters: + + ## OCI Compliance Test + test-oci-compliance: + description: "Enable compliance test." + default: true + type: boolean + + ## Image Efficiency Test + test-efficiency: + description: "Enable image efficiency test." + default: true + type: boolean + + ## Vulnerability Test + test-vulnerabilities: + description: "Enable vulnerability test." + default: true + type: boolean + trivyignore-path: + description: "Optional path to .trivyignore file." + type: string + + ## Malware Test + test_malware: + description: "Enable malware test." + default: true + type: boolean + +env: + VULNERABILITY_REPORT_SUFFIX: ".vulnerability-report.json" # TODO: inherit string from caller + TEST_IMAGE_NAME: "test-img" + TEST_IMAGE_TAG: "test" + SKOPEO_IMAGE: "quay.io/skopeo/stable:v1.15.1" + UMOCI_VERSION: "v0.4.7" + UMOCI_BINARY: "umoci.amd64" + DIVE_IMAGE: "wagoodman/dive:v0.12" + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db + TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db,aquasec/trivy-java-db,ghcr.io/aquasecurity/trivy-java-db + +jobs: + configure-tests: + runs-on: ubuntu-22.04 + outputs: + oci-factory-ref: ${{ steps.workflow-version.outputs.sha }} + cache-key: ${{ steps.set-cache.outputs.key }} + name: "configure-tests ${{ inputs.oci-archive-name != '' && format('| {0}', inputs.oci-archive-name) || ' '}}" + + steps: + - name: Get Workflow Version + # Note: we may need to pass a github token when working with private repositories. + # https://github.com/canonical/get-workflow-version-action + id: workflow-version + uses: canonical/get-workflow-version-action@v1 + with: + repository-name: canonical/oci-factory + file-name: Test-Rock.yaml + + - name: Cloning OCI Factory + uses: actions/checkout@v4 + with: + repository: canonical/oci-factory + ref: ${{ steps.workflow-version.outputs.sha }} + fetch-depth: 1 + + # download and unpacks the image under test, then store it under a common cache key. + # This unpacked image is used in test-oci-compliance and test_malware jobs + - name: Download Rock + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.oci-archive-name }} + + - name: Unpack Rock + run: | + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD:/workdir -w /workdir \ + ${{ env.SKOPEO_IMAGE }} \ + copy oci-archive:${{ inputs.oci-archive-name }} \ + oci:${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} + + - name: Set Cache Key + id: set-cache + run: | + echo "key=${{ github.run_id }}-${{ inputs.oci-archive-name }}" >> $GITHUB_OUTPUT + + - name: Cache Rock + uses: actions/cache/save@v4 + with: + path: ${{ env.TEST_IMAGE_NAME }} + key: ${{ steps.set-cache.outputs.key }} + + test-oci-compliance: + runs-on: ubuntu-22.04 + name: "test-oci-compliance ${{ inputs.oci-archive-name != '' && format('| {0}', inputs.oci-archive-name) || ' '}}" + needs: [configure-tests] + steps: + - uses: actions/cache/restore@v4 + with: + path: ${{ env.TEST_IMAGE_NAME }} + key: ${{ needs.configure-tests.outputs.cache-key }} + + - name: Install Umoci + run: | + wget https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/${UMOCI_BINARY} + sudo mv ${UMOCI_BINARY} /usr/bin/umoci + sudo chmod +x /usr/bin/umoci + + - name: Run Umoci tests + run: | + sudo umoci unpack --keep-dirlinks \ + --image ${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} \ + bundle + + umoci list --layout ${{ env.TEST_IMAGE_NAME }} | grep -w -c ${{ env.TEST_IMAGE_TAG }} + + test-black-box: + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-20.04] + runs-on: ${{ matrix.os }} + name: "test-black-box ${{ inputs.oci-archive-name != '' && format('| {0}', inputs.oci-archive-name) || ' '}}" + needs: [configure-tests] + steps: + - uses: actions/cache/restore@v4 + with: + path: ${{ env.TEST_IMAGE_NAME }} + key: ${{ needs.configure-tests.outputs.cache-key }} + + - name: Copy image to Docker daemon + run: | + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD:/workdir -w /workdir \ + ${{ env.SKOPEO_IMAGE }} \ + copy oci:${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} \ + docker-daemon:${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} + + - name: Test rock + run: | + set -ex + docker run --rm ${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} \ + help | grep Pebble + + - name: Create container + run: | + docker create ${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} + + test-efficiency: + runs-on: ubuntu-22.04 + name: "test-efficiency ${{ inputs.oci-archive-name != '' && format('| {0}', inputs.oci-archive-name) || ' '}}" + needs: [configure-tests] + # TODO: remove once https://chat.charmhub.io/charmhub/pl/o5wxpb65ffbfzy7bcmi8kzftzy is fixed + continue-on-error: true + steps: + + - name: Cloning OCI Factory + uses: actions/checkout@v4 + with: + repository: canonical/oci-factory + ref: ${{ needs.configure-tests.outputs.oci-factory-ref }} + fetch-depth: 1 + + - uses: actions/cache/restore@v4 + with: + path: ${{ env.TEST_IMAGE_NAME }} + key: ${{ needs.configure-tests.outputs.cache-key }} + + - name: Copy image to Docker daemon + run: | + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD:/workdir -w /workdir \ + ${{ env.SKOPEO_IMAGE }} \ + copy oci:${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} \ + docker-daemon:${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} + + # The existing Dive GH actions are outdated: + # https://github.com/MartinHeinz/dive-action/issues/1 + # https://github.com/yuichielectric/dive-action/issues/581 + - name: Dive efficiency test + run: | + docker run -e CI=true --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD/src/tests/.dive-ci.yaml:/.dive-ci.yaml \ + ${{ env.DIVE_IMAGE }} \ + ${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} --ci-config /.dive-ci.yaml + + test-vulnerabilities: + runs-on: ubuntu-22.04 + name: "test-vulnerabilities ${{ inputs.oci-archive-name != '' && format('| {0}', inputs.oci-archive-name) || ' '}}" + needs: [configure-tests] + steps: + + - name: Cloning OCI Factory + uses: actions/checkout@v4 + with: + repository: canonical/oci-factory + ref: ${{ needs.configure-tests.outputs.oci-factory-ref }} + fetch-depth: 1 + + - name: Download Rock + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.oci-archive-name }} + + - name: Configure Trivy + id: configure-trivy + run: | + + docker_image="${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }}" + echo "docker-image=$docker_image" >> "$GITHUB_OUTPUT" + source=oci-archive:${{ env.TEST_IMAGE_NAME }} + + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD:/workdir -w /workdir \ + ${{ env.SKOPEO_IMAGE }} \ + copy oci-archive:${{ inputs.oci-archive-name }} \ + docker-daemon:$docker_image + + if [ -n "${{ inputs.trivyignore-path }}" ] && [ -f "${{ inputs.trivyignore-path }}" ] + then + trivyignore_path="${{ inputs.trivyignore-path }}" + else + # dummy .trivyignore file + trivyignore_path=.trivyignore + touch $trivyignore_path + fi + echo "trivyignore-path=$trivyignore_path" >> "$GITHUB_OUTPUT" + + echo "report-name=${{ inputs.oci-archive-name }}${{ env.VULNERABILITY_REPORT_SUFFIX }}" >> "$GITHUB_OUTPUT" + + - name: Scan for vulnerabilities + uses: aquasecurity/trivy-action@0.28.0 + with: + # NOTE: we're allowing images with vulnerabilities to be published + ignore-unfixed: true + trivyignores: ${{ steps.configure-trivy.outputs.trivyignore-path }} + format: "cosign-vuln" + severity: "HIGH,CRITICAL" + exit-code: "1" + # NOTE: pebble is flagged with a HIGH vuln because of golang.org/x/crypto and encoding/gob + # CVE-2021-43565, CVE-2022-27191, CVE-2024-34156 + skip-files: "/bin/pebble,/usr/bin/pebble" + # missing ${{ runner.arch }} + output: "${{ steps.configure-trivy.outputs.report-name }}" + image-ref: "${{ steps.configure-trivy.outputs.docker-image }}" + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: ${{ steps.configure-trivy.outputs.report-name }} + path: ${{ steps.configure-trivy.outputs.report-name}} + + test-malware: + runs-on: ubuntu-22.04 + name: "test-malware ${{ inputs.oci-archive-name != '' && format('| {0}', inputs.oci-archive-name) || ' '}}" + needs: [configure-tests] + steps: + - name: Cloning OCI Factory + uses: actions/checkout@v4 + with: + repository: canonical/oci-factory + ref: ${{ needs.configure-tests.outputs.oci-factory-ref }} + fetch-depth: 1 + + - uses: actions/cache/restore@v4 + with: + path: ${{ env.TEST_IMAGE_NAME }} + key: ${{ needs.configure-tests.outputs.cache-key }} + + - name: Install Umoci + run: | + wget https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/${UMOCI_BINARY} + sudo mv ${UMOCI_BINARY} /usr/bin/umoci + sudo chmod +x /usr/bin/umoci + + - name: Unpack image + run: | + set -ex + umoci unpack \ + --image ${{ env.TEST_IMAGE_NAME }}:${{ env.TEST_IMAGE_TAG }} \ + --rootless raw + + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - run: pip install -r src/tests/requirements.txt + + - name: Scan for malware + run: | + ./src/tests/malware_scan.py --filesystem ./raw/rootfs diff --git a/.github/workflows/Tests.yaml b/.github/workflows/Tests.yaml deleted file mode 100644 index 09be6e99..00000000 --- a/.github/workflows/Tests.yaml +++ /dev/null @@ -1,262 +0,0 @@ -name: Tests -run-name: 'Tests - ${{ inputs.oci-image-name }} - ${{ github.ref }}' - -on: - workflow_call: - inputs: - oci-image-name: - description: 'Name of the image to be fetched and tested' - required: true - type: string - oci-image-path: - description: 'Path to the image in this repo (eg. "oci/foo")' - required: true - type: string - test-from: - description: 'From where to fetch the OCI image to be tested' - required: true - default: 'cache' - type: string - cache-key: - description: 'Cache key (when fetching from cache)' - required: false - type: string - vulnerability-report-suffix: - description: 'Suffix for the vulnerability report artefact' - required: false - type: string - default: '.vulnerability-report.json' - workflow_dispatch: - inputs: - oci-image-name: - description: 'Name of the image to be fetched and tested' - required: true - oci-image-path: - description: 'Path to the image in this repo (eg. "oci/foo")' - required: true - test-from: - description: 'From where to fetch the OCI image to be tested' - required: true - default: 'cache' - type: choice - options: - - cache - - registry - cache-key: - description: 'Cache key (when fetching from cache)' - required: false - type: string - vulnerability-report-suffix: - description: 'Suffix for the vulnerability report artefact' - required: true - type: string - default: '.vulnerability-report.json' - external_ref_id: # (1) - description: 'Optional ID for unique run detection' - required: false - type: string - default: "default-id" - -env: - TEST_IMAGE_NAME: 'test-img' - TEST_IMAGE_TAG: 'test' - SKOPEO_IMAGE: 'quay.io/skopeo/stable:v1.15.1' - UMOCI_VERSION: 'v0.4.7' - UMOCI_BINARY: 'umoci.amd64' - DIVE_IMAGE: 'wagoodman/dive:v0.12' - -jobs: - access-check: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - name: Validate access to triggered image - uses: ./.github/actions/validate-actor - if: ${{ github.repository == 'canonical/oci-factory' && !github.event.pull_request.head.repo.fork }} - with: - admin-only: true - image-path: ${{ inputs.oci-image-path }} - github-token: ${{ secrets.ROCKSBOT_TOKEN }} - - fetch-oci-image: - runs-on: ubuntu-22.04 - name: Fetch OCI image for testing - needs: [access-check] - outputs: - test-cache-key: ${{ steps.cache.outputs.key }} - steps: - - name: ${{ inputs.external_ref_id }} # (2) - run: echo 'Started by ${{ inputs.external_ref_id }}' >> "$GITHUB_STEP_SUMMARY" - - - uses: actions/cache/restore@v4 - if: ${{ inputs.test-from == 'cache' }} - with: - path: ${{ inputs.oci-image-name }} - key: ${{ inputs.cache-key }} - fail-on-cache-miss: true - - - if: ${{ inputs.test-from == 'cache' }} - run: | - # from OCI ARCHIVE to OCI - docker run --rm -v $PWD:/workdir \ - -w /workdir \ - ${{ env.SKOPEO_IMAGE }} \ - copy oci-archive:${{ inputs.oci-image-name }} \ - oci:${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} - - - if: ${{ inputs.test-from == 'registry' }} - run: | - # from REGISTRY to OCI - docker run --rm -v $PWD:/workdir \ - -w /workdir \ - ${{ env.SKOPEO_IMAGE }} \ - copy docker://${{ inputs.oci-image-name }} \ - oci:${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} - - - uses: actions/cache/save@v4 - with: - path: ${{ env.TEST_IMAGE_NAME}} - key: ${{ github.run_id }}-${{ inputs.oci-image-name }}-${{ env.TEST_IMAGE_NAME }} - - - name: Save cache key - id: cache - run: echo "key=${{ github.run_id }}-${{ inputs.oci-image-name }}-${{ env.TEST_IMAGE_NAME }}" >> "$GITHUB_OUTPUT" - - - test-oci-compliance: - runs-on: ubuntu-22.04 - name: Test OCI compliance - needs: [fetch-oci-image] - steps: - - uses: actions/cache/restore@v4 - with: - path: ${{ env.TEST_IMAGE_NAME}} - key: ${{ needs.fetch-oci-image.outputs.test-cache-key }} - - - name: Install Umoci - run: | - wget https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/${UMOCI_BINARY} - sudo mv ${UMOCI_BINARY} /usr/bin/umoci - sudo chmod +x /usr/bin/umoci - - - name: Run Umoci tests - run: | - sudo umoci unpack --keep-dirlinks \ - --image ${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} \ - bundle - - umoci list --layout ${{ env.TEST_IMAGE_NAME}} | grep -w -c ${{ env.TEST_IMAGE_TAG }} - - - test-black-box: - strategy: - matrix: - os: [ubuntu-22.04, ubuntu-20.04] - runs-on: ${{ matrix.os }} - name: Black-box and portability tests - needs: [fetch-oci-image] - steps: - - uses: actions/cache/restore@v4 - with: - path: ${{ env.TEST_IMAGE_NAME}} - key: ${{ needs.fetch-oci-image.outputs.test-cache-key }} - - - name: Copy image to Docker daemon - run: | - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ - -v $PWD:/workdir -w /workdir \ - ${{ env.SKOPEO_IMAGE }} \ - copy oci:${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} \ - docker-daemon:${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} - - - name: Test rock - run: | - set -ex - docker run --rm ${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} \ - help | grep Pebble - - - name: Create container - run: | - docker create ${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} - - - test-efficiency: - runs-on: ubuntu-22.04 - name: Efficiency test - needs: [fetch-oci-image] - # TODO: remove once https://chat.charmhub.io/charmhub/pl/o5wxpb65ffbfzy7bcmi8kzftzy is fixed - continue-on-error: true - steps: - - uses: actions/checkout@v4 - - - uses: actions/cache/restore@v4 - with: - path: ${{ env.TEST_IMAGE_NAME}} - key: ${{ needs.fetch-oci-image.outputs.test-cache-key }} - - - name: Copy image to Docker daemon - run: | - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ - -v $PWD:/workdir -w /workdir \ - ${{ env.SKOPEO_IMAGE }} \ - copy oci:${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} \ - docker-daemon:${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} - - # The existing Dive GH actions are outdated: - # https://github.com/MartinHeinz/dive-action/issues/1 - # https://github.com/yuichielectric/dive-action/issues/581 - - name: Dive efficiency test - run: | - docker run -e CI=true --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v $PWD/src/tests/.dive-ci.yaml:/.dive-ci.yaml \ - ${{ env.DIVE_IMAGE }} \ - ${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} --ci-config /.dive-ci.yaml - - - test-vulnerabilities: - name: Vulnerability scan - needs: [fetch-oci-image] - uses: ./.github/workflows/Vulnerability-Scan.yaml - with: - oci-image-name: "${{ inputs.oci-image-name }}" - oci-image-path: "${{ inputs.oci-image-path }}" - cache-key: "${{ needs.fetch-oci-image.outputs.test-cache-key }}" - vulnerability-report-suffix: "${{ inputs.vulnerability-report-suffix}}" - secrets: inherit - - - test-malware: - runs-on: ubuntu-22.04 - name: Malware scan - needs: [fetch-oci-image] - steps: - - uses: actions/checkout@v4 - - - uses: actions/cache/restore@v4 - with: - path: ${{ env.TEST_IMAGE_NAME}} - key: ${{ github.run_id }}-${{ inputs.oci-image-name }}-${{ env.TEST_IMAGE_NAME }} - - - name: Install Umoci - run: | - wget https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/${UMOCI_BINARY} - sudo mv ${UMOCI_BINARY} /usr/bin/umoci - sudo chmod +x /usr/bin/umoci - - - name: Unpack image - run: | - set -ex - umoci unpack \ - --image ${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }} \ - --rootless raw - - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - run: pip install -r src/tests/requirements.txt - - - name: Scan for malware - run: | - ./src/tests/malware_scan.py --filesystem ./raw/rootfs diff --git a/.github/workflows/Vulnerability-Scan.yaml b/.github/workflows/Vulnerability-Scan.yaml index 6ba4359a..e94d1351 100644 --- a/.github/workflows/Vulnerability-Scan.yaml +++ b/.github/workflows/Vulnerability-Scan.yaml @@ -1,5 +1,5 @@ name: Vulnerability Scan -run-name: 'Tests - ${{ inputs.oci-image-name }} - ${{ github.ref }}' +run-name: 'Vulnerability Scan - ${{ inputs.oci-image-name }} - ${{ github.ref }}' on: workflow_call: @@ -12,16 +12,6 @@ on: description: 'Path to the image in this repo (eg. "oci/foo")' required: true type: string - cache-key: - description: 'Key ID for restoring image (in OCI format) from cache' - required: false - type: string - default: '' - vulnerability-report-suffix: - description: 'Suffix for the vulnerability report artefact' - required: false - type: string - default: '.vulnerability-report.json' date-last-scan: description: 'If there are new CVEs after this date, we notify' required: false @@ -34,18 +24,55 @@ on: default: false env: - TEST_IMAGE_NAME: 'test-img' - TEST_IMAGE_TAG: 'test' + VULNERABILITY_REPORT_SUFFIX: '.vulnerability-report.json' # TODO: inherit string from caller SKOPEO_IMAGE: 'quay.io/skopeo/stable:v1.15.1' - TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db - TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db,aquasec/trivy-java-db,ghcr.io/aquasecurity/trivy-java-db jobs: + configure-scan: + runs-on: ubuntu-22.04 + name: "configure-scan ${{ inputs.oci-image-name != '' && format('| {0}', inputs.oci-image-name) || ' '}}" + outputs: + oci-image: ${{ steps.configure.outputs.oci-filename }} + vulnerability-report: ${{ steps.configure.outputs.report-filename }} + steps: + - id: configure + run: | + oci_filename="$(echo "${{ inputs.oci-image-name }}" \ + | sed 's/ghcr.io\/canonical\/oci-factory\///g' | tr ':' '_')" + + echo "oci-filename=$oci_filename" >> "$GITHUB_OUTPUT" + echo "report-filename=${oci_filename}${{ env.VULNERABILITY_REPORT_SUFFIX }}" >> "$GITHUB_OUTPUT" + + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD:/workdir -w /workdir \ + ${{ env.SKOPEO_IMAGE }} \ + copy docker://${{ inputs.oci-image-name }} \ + oci-archive:$oci_filename + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: ${{ steps.configure.outputs.oci-filename }} + path: ${{ steps.configure.outputs.oci-filename }} + retention-days: 1 + test-vulnerabilities: + name: "test-vulnerabilities ${{ inputs.oci-image-name != '' && format('| {0}', inputs.oci-image-name) || ' '}}" + uses: ./.github/workflows/Test-Rock.yaml + needs: [configure-scan] + with: + oci-archive-name: ${{ needs.configure-scan.outputs.oci-image }} + test-vulnerabilities: true # just incase we set this to false by default in the future + test-oci-compliance: false + test-efficiency: false + test_malware: false + + parse-results: + name: "parse-results ${{ inputs.oci-image-name != '' && format('| {0}', inputs.oci-image-name) || ' '}}" runs-on: ubuntu-22.04 - name: Vulnerability scan + needs: [configure-scan, test-vulnerabilities] + if: ${{ !cancelled() }} outputs: - vulnerability-report: ${{ steps.vulnerability-report.outputs.name }} notify: ${{ steps.check-report.outputs.notify }} vulnerabilities: ${{ steps.check-report.outputs.vulnerabilities }} steps: @@ -59,70 +86,16 @@ jobs: image-path: ${{ inputs.oci-image-path }} github-token: ${{ secrets.ROCKSBOT_TOKEN }} - - id: vulnerability-report - run: | - full_name="${{ inputs.oci-image-name }}${{ inputs.vulnerability-report-suffix }}" - final_name="$(echo ${full_name} | sed 's/ghcr.io\/canonical\/oci-factory\///g' | tr ':' '_')" - echo "name=$final_name" >> "$GITHUB_OUTPUT" - - - uses: actions/cache/restore@v4 - if: ${{ inputs.cache-key != '' }} + - name: Download Vulnerability Report + uses: actions/download-artifact@v4 with: - path: ${{ env.TEST_IMAGE_NAME}} - key: ${{ inputs.cache-key }} - fail-on-cache-miss: true - - - name: Copy image to Docker daemon - id: to-docker-daemon - run: | - d_img_name="${{ env.TEST_IMAGE_NAME}}:${{ env.TEST_IMAGE_TAG }}" - echo "name=$d_img_name" >> "$GITHUB_OUTPUT" - if [[ "${{ inputs.cache-key}}" != "" ]] - then - source=oci:${{ env.TEST_IMAGE_NAME}} - else - source=docker://${{ inputs.oci-image-name }} - fi - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ - -v $PWD:/workdir -w /workdir \ - ${{ env.SKOPEO_IMAGE }} \ - copy $source \ - docker-daemon:$d_img_name - - - name: Check for .trivyignore - id: trivyignore - run: | - if [ -f ${{ inputs.oci-image-path }}/.trivyignore ] - then - file=${{ inputs.oci-image-path }}/.trivyignore - else - # dummy .trivyignore file - file=.trivyignore - touch $file - fi - echo "file=$file" >> "$GITHUB_OUTPUT" - - - name: Scan for vulnerabilities - uses: aquasecurity/trivy-action@0.28.0 - with: - # NOTE: we're allowing images with vulnerabilities to be published - ignore-unfixed: true - trivyignores: ${{ steps.trivyignore.outputs.file }} - format: 'cosign-vuln' - severity: 'HIGH,CRITICAL' - exit-code: '1' - # NOTE: pebble is flagged with a HIGH vuln because of golang.org/x/crypto - # CVE-2021-43565, CVE-2022-27191 - skip-files: "/bin/pebble,/usr/bin/pebble" - # missing ${{ runner.arch }} - output: '${{ steps.vulnerability-report.outputs.name }}' - image-ref: '${{ steps.to-docker-daemon.outputs.name }}' + name: '${{ needs.configure-scan.outputs.vulnerability-report }}' - name: Process report if: ${{ !cancelled() }} id: check-report run: | - report="${{ steps.vulnerability-report.outputs.name }}" + report="${{ needs.configure-scan.outputs.vulnerability-report }}" echo "notify=false" >> "$GITHUB_OUTPUT" set -x vulnerabilities="$(jq -r -c '[ @@ -148,27 +121,13 @@ jobs: fi done - - uses: actions/cache/save@v4 - if: ${{ !cancelled() }} - with: - path: ${{ steps.vulnerability-report.outputs.name }} - key: ${{ github.run_id }}-${{ steps.vulnerability-report.outputs.name }} - - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: ${{ steps.vulnerability-report.outputs.name }} - path: ${{ steps.vulnerability-report.outputs.name }} - - # Many workflows are now using a similar notification job. It would be better # if this was a common workflows reachable via a workflow_call notify: + name: "notify ${{ inputs.oci-image-name != '' && format('| {0}', inputs.oci-image-name) || ' '}}" runs-on: ubuntu-22.04 - name: Notify on failure - needs: - - test-vulnerabilities - if: ${{ !cancelled() && needs.test-vulnerabilities.outputs.notify == 'true' }} + needs: [parse-results] + if: ${{ !cancelled() && needs.parse-results.outputs.notify == 'true' }} steps: - uses: actions/checkout@v4 @@ -199,16 +158,15 @@ jobs: done issue: + name: "issue ${{ inputs.oci-image-name != '' && format('| {0}', inputs.oci-image-name) || ' '}}" runs-on: ubuntu-22.04 - name: Create issue - needs: - - test-vulnerabilities + needs: [parse-results, test-vulnerabilities] env: GITHUB_TOKEN: ${{ secrets.ROCKSBOT_TOKEN }} if: ${{ !cancelled() && github.event_name != 'pull_request' }} steps: - uses: actions/checkout@v4 - + - uses: actions/setup-python@v5 with: python-version: '3.x' @@ -234,7 +192,7 @@ jobs: id: create-markdown run: | set -x - num_vulns=$(echo '${{ needs.test-vulnerabilities.outputs.vulnerabilities }}' | jq -r 'length') + num_vulns=$(echo '${{ needs.parse-results.outputs.vulnerabilities }}' | jq -r 'length') vulnerability_exists=$([[ $num_vulns -gt 0 ]] && echo 'true' || echo 'false') echo "vulnerability-exists=$vulnerability_exists" >> "$GITHUB_OUTPUT" if [[ $vulnerability_exists == 'true' ]]; then @@ -242,7 +200,7 @@ jobs: echo "## $title" > issue.md echo "| ID | Target | Severity | Package |" >> issue.md echo "| -- | ----- | -------- | ------- |" >> issue.md - echo '${{ needs.test-vulnerabilities.outputs.vulnerabilities }}' | jq -r '.[] | "| \(.VulnerabilityID) | /\(.Target) | \(.Severity) | \(.PkgName) |"' >> issue.md + echo '${{ needs.parse-results.outputs.vulnerabilities }}' | jq -r '.[] | "| \(.VulnerabilityID) | /\(.Target) | \(.Severity) | \(.PkgName) |"' >> issue.md if [[ ${{ inputs.create-issue }} == 'true' ]]; then revision_to_released_tags=$(python3 -m src.shared.release_info get_revision_to_released_tags --all-releases ${{ inputs.oci-image-path }}/_releases.json) affected_tracks=$(echo "${revision_to_released_tags}" | jq -r '."${{ steps.simplify-image-name.outputs.img_revision }}" | map("- `\(.)`") | join("\n")') @@ -268,7 +226,6 @@ jobs: echo "issue-exists=$([[ -n "$issue_number" ]] && echo 'true' || echo 'false')" >> "$GITHUB_OUTPUT" echo "issue-number=$issue_number" >> "$GITHUB_OUTPUT" - # Truth table for issue creation # | issue-exists | notify | vulnerability-exists | op | # |--------------|--------|----------------------|--------| @@ -280,7 +237,7 @@ jobs: # | F | T | F | never | # | F | F | T | create | # | F | F | F | nop | - + - name: Notify via GitHub issue if: ${{ steps.create-markdown.outputs.vulnerability-exists == 'true' && inputs.create-issue }} run: | @@ -289,7 +246,7 @@ jobs: if [[ ${{ steps.issue-exists.outputs.issue-exists }} == 'false' ]]; then op="create" elif [[ ${{ steps.issue-exists.outputs.issue-exists }} == 'true' \ - && ${{ needs.test-vulnerabilities.outputs.notify }} == 'true' ]]; then + && ${{ needs.parse-results.outputs.notify }} == 'true' ]]; then op="edit ${{ steps.issue-exists.outputs.issue-number }}" fi if [[ $op != 'nop' ]]; then diff --git a/.github/workflows/_Test-OCI-Factory.yaml b/.github/workflows/_Test-OCI-Factory.yaml index e11c18eb..cc8164d2 100644 --- a/.github/workflows/_Test-OCI-Factory.yaml +++ b/.github/workflows/_Test-OCI-Factory.yaml @@ -95,7 +95,38 @@ jobs: GITHUB_TOKEN: ${{ secrets.ROCKSBOT_TOKEN }} run: | find ${{ github.workspace }} -name 'test-*.bats' | xargs bats + + # # Test use case of reuseable workflow + # build-rock: + # uses: ./.github/workflows/Build-Rock.yaml + # with: + # # mock rock + # oci-archive-name: "mock-rock" + # rock-repo: canonical/rocks-toolbox + # rock-repo-commit: main + # rockfile-directory: mock_rock/1.2 # 1.0 - multi arch, 1.2 single arch rock + # test-rock: + # uses: ./.github/workflows/Test-Rock.yaml + # needs: [build-rock] + # with: + # oci-archive-name: "mock-rock" + # test-vulnerabilities: true + # test-oci-compliance: true + # test-efficiency: true + # test_malware: true + + # # Test workflow used in continuous testing + # test-vulnerability-scan: + # name: Test vulnerability scan workflow + # needs: [access-check] + # uses: ./.github/workflows/Vulnerability-Scan.yaml + # with: + # oci-image-name: "ghcr.io/canonical/oci-factory/mock-rock:1.1-22.04_844" + # oci-image-path: "oci/mock-rock" + # secrets: inherit + + # Test full image workflow test-workflows: name: Trigger internal tests for mock-rock needs: [access-check] diff --git a/oci/mock-rock/_releases.json b/oci/mock-rock/_releases.json index 22d0b0b6..5dccf931 100644 --- a/oci/mock-rock/_releases.json +++ b/oci/mock-rock/_releases.json @@ -13,13 +13,13 @@ }, "1.0-22.04": { "candidate": { - "target": "799" + "target": "878" }, "beta": { - "target": "799" + "target": "878" }, "edge": { - "target": "799" + "target": "878" }, "end-of-life": "2024-05-01T00:00:00Z" }, @@ -35,31 +35,31 @@ "1.1-22.04": { "end-of-life": "2030-05-01T00:00:00Z", "candidate": { - "target": "885" + "target": "909" }, "beta": { - "target": "885" + "target": "909" }, "edge": { - "target": "885" + "target": "909" } }, "1-22.04": { "end-of-life": "2030-05-01T00:00:00Z", "candidate": { - "target": "885" + "target": "909" }, "beta": { - "target": "885" + "target": "909" }, "edge": { - "target": "885" + "target": "909" } }, "1.2-22.04": { "end-of-life": "2030-05-01T00:00:00Z", "beta": { - "target": "886" + "target": "910" }, "edge": { "target": "1.2-22.04_beta"