diff --git a/.github/workflows/govulncheck.yaml b/.github/workflows/govulncheck.yaml index 7e23bdb..8bbaeac 100644 --- a/.github/workflows/govulncheck.yaml +++ b/.github/workflows/govulncheck.yaml @@ -1,10 +1,17 @@ # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. -# Run govulncheck at midnight every night on the main branch, -# to alert us to recent vulnerabilities which affect the Go code in this -# project. -name: govulncheck +# Nightly security scan. Runs govulncheck against the Go code and, where the +# repository builds OCI images, oci-security-scan, which uses trivy to check +# the images for fixable vulnerabilities of severity MEDIUM, HIGH or CRITICAL. +# Scans the default branch, the two newest release branches and the latest +# release tag, to give early warning when a released image, or an image we are +# about to release, contains known vulnerabilities. +# +# This file keeps its historic "govulncheck" name so that GitHub continues to +# treat it as the same registered workflow (which allows workflow_dispatch on +# any branch) and to avoid leaving stale workflow files in downstream repos. +name: security-scan on: workflow_dispatch: {} schedule: @@ -14,24 +21,69 @@ permissions: contents: read jobs: - govulncheck: + list-refs: runs-on: ubuntu-latest if: github.repository == 'cert-manager/csi-driver-spiffe' + outputs: + refs: ${{ steps.list-refs.outputs.refs }} + steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - # Adding `fetch-depth: 0` makes sure tags are also fetched. We need - # the tags so `git describe` returns a valid version. - # see https://github.com/actions/checkout/issues/701 for extra info about this option + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { fetch-depth: 0 } + # Scan the branch this workflow runs on (usually the default branch), + # the two newest release branches, and the latest tag which is not a + # pre-release. + - id: list-refs + run: | + refs="$( + { + echo "${{ github.ref_name }}" + git branch --remotes --list 'origin/release-*' --format='%(refname:lstrip=3)' --sort=-version:refname \ + | grep -E '^release-[0-9]+\.[0-9]+$' | head -n 2 + git tag --list 'v*' --sort=-version:refname | grep -v '[-]' | head -n 1 + } | jq --raw-input . | jq --slurp --compact-output 'map(select(. != "")) | unique' + )" + echo "refs=${refs}" >> "$GITHUB_OUTPUT" + + security-scan: + runs-on: ubuntu-latest + + needs: list-refs + + strategy: + fail-fast: false + matrix: + ref: ${{ fromJSON(needs.list-refs.outputs.refs) }} + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ matrix.ref }} + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + fetch-depth: 0 + - id: go-version run: | make print-go-version >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: ${{ steps.go-version.outputs.result }} - run: make verify-govulncheck + + # Only repositories which build OCI images have the oci-security-scan + # target; older release branches and tags may not have it yet either. + # --keep-going so that one vulnerable image does not prevent the + # remaining images from being scanned and reported. + - run: | + if make --dry-run oci-security-scan >/dev/null 2>&1; then + make --keep-going oci-security-scan + else + echo "::notice::The oci-security-scan target does not exist on ${{ matrix.ref }}; skipping." + fi diff --git a/klone.yaml b/klone.yaml index 63ed17c..ba6fc49 100644 --- a/klone.yaml +++ b/klone.yaml @@ -28,9 +28,9 @@ targets: repo_hash: e0878ff3ab29a9d2af7b7eae8a98356ad388c931 repo_path: modules/generate-verify - folder_name: go - repo_url: https://github.com/cert-manager/makefile-modules.git - repo_ref: main - repo_hash: e0878ff3ab29a9d2af7b7eae8a98356ad388c931 + repo_url: https://github.com/wallrj/makefile-modules.git + repo_ref: oci-security-scan + repo_hash: 9f38f78fc9d3e9cdd00f57dc3202e82f9be2d123 repo_path: modules/go - folder_name: helm repo_url: https://github.com/cert-manager/makefile-modules.git @@ -58,9 +58,9 @@ targets: repo_hash: e0878ff3ab29a9d2af7b7eae8a98356ad388c931 repo_path: modules/licenses - folder_name: oci-build - repo_url: https://github.com/cert-manager/makefile-modules.git - repo_ref: main - repo_hash: e0878ff3ab29a9d2af7b7eae8a98356ad388c931 + repo_url: https://github.com/wallrj/makefile-modules.git + repo_ref: oci-security-scan + repo_hash: 9f38f78fc9d3e9cdd00f57dc3202e82f9be2d123 repo_path: modules/oci-build - folder_name: oci-publish repo_url: https://github.com/cert-manager/makefile-modules.git diff --git a/make/00_mod.mk b/make/00_mod.mk index 9e969f3..88c00f5 100644 --- a/make/00_mod.mk +++ b/make/00_mod.mk @@ -59,6 +59,12 @@ nodedriverregistrar_image_name_source := registry.k8s.io/sig-storage/csi-node-dr nodedriverregistrar_image_name := quay.io/jetstack/csi-node-driver-registrar nodedriverregistrar_image_tag := v2.17.0 +# Scan the third party sidecar images which are deployed alongside the +# csi-driver-spiffe images. +oci_scan_extra_images := \ + $(livenessprobe_image_name_source):$(livenessprobe_image_tag) \ + $(nodedriverregistrar_image_name_source):$(nodedriverregistrar_image_tag) + define helm_values_mutation_function $(YQ) \ '( .app.driver.livenessProbeImage._defaultReference = ":$(livenessprobe_image_tag)" ) | \ diff --git a/make/_shared/go/base/.github/workflows/govulncheck.yaml b/make/_shared/go/base/.github/workflows/govulncheck.yaml index 9c6c2cf..bcdb388 100644 --- a/make/_shared/go/base/.github/workflows/govulncheck.yaml +++ b/make/_shared/go/base/.github/workflows/govulncheck.yaml @@ -1,10 +1,17 @@ # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. -# Run govulncheck at midnight every night on the main branch, -# to alert us to recent vulnerabilities which affect the Go code in this -# project. -name: govulncheck +# Nightly security scan. Runs govulncheck against the Go code and, where the +# repository builds OCI images, oci-security-scan, which uses trivy to check +# the images for fixable vulnerabilities of severity MEDIUM, HIGH or CRITICAL. +# Scans the default branch, the two newest release branches and the latest +# release tag, to give early warning when a released image, or an image we are +# about to release, contains known vulnerabilities. +# +# This file keeps its historic "govulncheck" name so that GitHub continues to +# treat it as the same registered workflow (which allows workflow_dispatch on +# any branch) and to avoid leaving stale workflow files in downstream repos. +name: security-scan on: workflow_dispatch: {} schedule: @@ -14,24 +21,69 @@ permissions: contents: read jobs: - govulncheck: + list-refs: runs-on: ubuntu-latest if: github.repository == '{{REPLACE:GH-REPOSITORY}}' + outputs: + refs: ${{ steps.list-refs.outputs.refs }} + steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - # Adding `fetch-depth: 0` makes sure tags are also fetched. We need - # the tags so `git describe` returns a valid version. - # see https://github.com/actions/checkout/issues/701 for extra info about this option + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { fetch-depth: 0 } + # Scan the branch this workflow runs on (usually the default branch), + # the two newest release branches, and the latest tag which is not a + # pre-release. + - id: list-refs + run: | + refs="$( + { + echo "${{ github.ref_name }}" + git branch --remotes --list 'origin/release-*' --format='%(refname:lstrip=3)' --sort=-version:refname \ + | grep -E '^release-[0-9]+\.[0-9]+$' | head -n 2 + git tag --list 'v*' --sort=-version:refname | grep -v '[-]' | head -n 1 + } | jq --raw-input . | jq --slurp --compact-output 'map(select(. != "")) | unique' + )" + echo "refs=${refs}" >> "$GITHUB_OUTPUT" + + security-scan: + runs-on: ubuntu-latest + + needs: list-refs + + strategy: + fail-fast: false + matrix: + ref: ${{ fromJSON(needs.list-refs.outputs.refs) }} + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ matrix.ref }} + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + fetch-depth: 0 + - id: go-version run: | make print-go-version >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: ${{ steps.go-version.outputs.result }} - run: make verify-govulncheck + + # Only repositories which build OCI images have the oci-security-scan + # target; older release branches and tags may not have it yet either. + # --keep-going so that one vulnerable image does not prevent the + # remaining images from being scanned and reported. + - run: | + if make --dry-run oci-security-scan >/dev/null 2>&1; then + make --keep-going oci-security-scan + else + echo "::notice::The oci-security-scan target does not exist on ${{ matrix.ref }}; skipping." + fi diff --git a/make/_shared/oci-build/00_mod.mk b/make/_shared/oci-build/00_mod.mk index 71a444e..ab5d4b4 100644 --- a/make/_shared/oci-build/00_mod.mk +++ b/make/_shared/oci-build/00_mod.mk @@ -32,6 +32,22 @@ CGO_ENABLED ?= 0 GOEXPERIMENT ?= # empty by default oci_platforms ?= linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le +# Extra images (e.g. third party sidecar images which are deployed alongside +# the images built by this repository) to be scanned by oci-security-scan, +# for example: +# oci_scan_extra_images := registry.k8s.io/sig-storage/livenessprobe:v2.18.0 +oci_scan_extra_images ?= + +# The trivy policy applied by the oci-scan-* targets: report only +# vulnerabilities which have a known fix and a severity of MEDIUM, HIGH or +# CRITICAL, and fail if any are found. The secret scanner is disabled because +# it is slow. +trivy_scan_flags ?= \ + --scanners vuln \ + --severity MEDIUM,HIGH,CRITICAL \ + --ignore-unfixed \ + --exit-code 1 + # Default variables per build_names entry # # $1 - build_name @@ -120,10 +136,12 @@ $(foreach build_name,$(build_names),$(eval $(call check_per_build_variables,$(bu # - oci-build-$(build_name)__local = build the oci directory (local arch: linux/$(HOST_ARCH)) # - oci-load-$(build_name) = load the image into docker using the oci_$(build_name)_image_name_development variable # - docker-tarball-$(build_name) = build a "docker load" compatible tarball of the image +# - oci-scan-$(build_name) = scan the image for known vulnerabilities using trivy oci_build_targets := $(build_names:%=oci-build-%) oci_build_targets += $(build_names:%=oci-build-%__local) oci_load_targets := $(build_names:%=oci-load-%) docker_tarball_targets := $(build_names:%=docker-tarball-%) +oci_scan_targets := $(build_names:%=oci-scan-%) # Derive config based on user config # diff --git a/make/_shared/oci-build/01_mod.mk b/make/_shared/oci-build/01_mod.mk index 026e46b..494bf4c 100644 --- a/make/_shared/oci-build/01_mod.mk +++ b/make/_shared/oci-build/01_mod.mk @@ -81,3 +81,59 @@ endif .PHONY: $(docker_tarball_targets) $(docker_tarball_targets): docker-tarball-%: oci-build-%__local | $(NEEDS_GO) $(NEEDS_IMAGE-TOOL) $(IMAGE-TOOL) convert-to-docker-tar $(CURDIR)/$(oci_layout_path_$*).local $(docker_tarball_path_$*) $(oci_$*_image_name_development):$(oci_$*_image_tag) + +# Run "trivy image $2 $(trivy_scan_flags)" and print the report to stdout. +# When running in GitHub Actions (detected by GITHUB_STEP_SUMMARY being set; +# some repos using this module run CI on GitLab instead) the report is also +# appended to the job summary under the heading $1, because trivy's table +# format is hard to read in the raw job log. +# +# $1 - summary heading +# $2 - trivy image arguments (an image reference or --input ) +define trivy_scan +report=$$(mktemp); \ +$(TRIVY) image $2 $(trivy_scan_flags) --output $$report; \ +code=$$?; \ +cat $$report; \ +if [ -n "$${GITHUB_STEP_SUMMARY:-}" ]; then \ + { echo "### $1"; echo '```'; cat $$report; echo '```'; echo; } >> "$$GITHUB_STEP_SUMMARY"; \ +fi; \ +rm -f $$report; \ +exit $$code +endef + +.PHONY: $(oci_scan_targets) +## Scan the OCI image (local architecture) for OS and library +## vulnerabilities which have a known fix and a severity of +## MEDIUM, HIGH or CRITICAL, using trivy +## (https://github.com/aquasecurity/trivy). +## @category [shared] Build +$(oci_scan_targets): oci-scan-%: docker-tarball-% | $(NEEDS_TRIVY) + @echo "Scanning $(oci_$*_image_name_development):$(oci_$*_image_tag)" + @$(call trivy_scan,$(oci_$*_image_name_development):$(oci_$*_image_tag),--input $(docker_tarball_path_$*)) + +.PHONY: oci-scan-extra-images +## Scan the images listed in oci_scan_extra_images (e.g. third party sidecar +## images which are deployed alongside the images built by this repository) +## for known vulnerabilities, using trivy. +## @category [shared] Build +oci-scan-extra-images: | $(NEEDS_TRIVY) + @failed=0; \ + for image in $(oci_scan_extra_images); do \ + echo "Scanning $$image"; \ + report=$$(mktemp); \ + $(TRIVY) image $$image $(trivy_scan_flags) --output $$report || failed=1; \ + cat $$report; \ + if [ -n "$${GITHUB_STEP_SUMMARY:-}" ]; then \ + { echo "### $$image"; echo '```'; cat $$report; echo '```'; echo; } >> "$$GITHUB_STEP_SUMMARY"; \ + fi; \ + rm -f $$report; \ + done; \ + exit $$failed + +.PHONY: oci-security-scan +## Scan all the OCI images built by this repository, and any extra images +## listed in oci_scan_extra_images, for known vulnerabilities; failing if any +## fixable vulnerabilities of severity MEDIUM, HIGH or CRITICAL are found. +## @category [shared] Build +oci-security-scan: $(oci_scan_targets) oci-scan-extra-images