ci: align source gate with Go toolchain directive #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Security release gate | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - main | |
| - 'verification/compose-cli-*' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: compose-cli-security-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| build-test-scan: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 75 | |
| env: | |
| DAPPER_IMAGE: pasturestack/compose-cli-dapper:${{ github.sha }} | |
| TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 | |
| VERSION_OVERRIDE: 0.14.33 | |
| PLATFORM_COMPAT_JAR_URL: https://github.com/PastureStack/orchestration-engine/releases/download/v0.183.281/orchestration-engine-0.183.281.jar | |
| PLATFORM_COMPAT_JAR_SHA256: da2a8a51562ed16e296f7e29e99482bb44042ff0834cca679bbe01d951ba1682 | |
| steps: | |
| - name: Check out candidate | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Record candidate identity | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -z "$(git status --porcelain)" | |
| test "$(git rev-parse HEAD)" = "$GITHUB_SHA" | |
| mkdir -p evidence | |
| git rev-parse HEAD > evidence/source-revision.txt | |
| sha256sum Dockerfile.dapper ubuntu-apt.lock go.mod go.sum vendor/modules.txt .python-version tests/integration/requirements.txt > evidence/source-locks.sha256 | |
| - name: Build, test, validate, and package twice | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker build \ | |
| --build-arg DAPPER_HOST_ARCH=amd64 \ | |
| --tag "$DAPPER_IMAGE" \ | |
| --file Dockerfile.dapper \ | |
| . | |
| source_path="$GITHUB_WORKSPACE" | |
| run_ci() { | |
| docker run --rm \ | |
| --volume "${source_path}:/go/src/github.com/PastureStack/compose-cli" \ | |
| --env "DAPPER_UID=$(id -u)" \ | |
| --env "DAPPER_GID=$(id -g)" \ | |
| --env "GOCACHE=/tmp/go-build-cache-${GITHUB_RUN_ID}" \ | |
| --env "XDG_CONFIG_HOME=/tmp/go-config-${GITHUB_RUN_ID}" \ | |
| --env "GIT_CONFIG_GLOBAL=/tmp/gitconfig-${GITHUB_RUN_ID}" \ | |
| --env "VERSION_OVERRIDE=$VERSION_OVERRIDE" \ | |
| --env "SKIP_INTEGRATION=true" \ | |
| --env "PLATFORM_COMPAT_JAR_URL=$PLATFORM_COMPAT_JAR_URL" \ | |
| --env "PLATFORM_COMPAT_JAR_SHA256=$PLATFORM_COMPAT_JAR_SHA256" \ | |
| "$DAPPER_IMAGE" ci | |
| } | |
| run_ci | |
| artifact="dist/artifacts/compose-executor-0.14.33-linux-amd64.gz" | |
| test -s "$artifact" | |
| cp "$artifact" /tmp/compose-executor-first.gz | |
| rm -rf bin build dist | |
| run_ci | |
| cmp /tmp/compose-executor-first.gz "$artifact" | |
| ./scripts/check-pasturestack-source | |
| mkdir -p evidence/product | |
| gzip -cd "$artifact" > evidence/product/compose-executor | |
| chmod +x evidence/product/compose-executor | |
| evidence/product/compose-executor --version | grep -F '0.14.33' >/dev/null | |
| sha256sum "$artifact" > evidence/compose-executor.gz.sha256 | |
| docker run --rm --entrypoint go \ | |
| --volume "$PWD:/work:ro" \ | |
| "$DAPPER_IMAGE" \ | |
| version -m /work/evidence/product/compose-executor \ | |
| > evidence/product-go-version.txt | |
| - name: Record resolved build inputs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker inspect "$DAPPER_IMAGE" > evidence/dapper-image-inspect.json | |
| docker run --rm --entrypoint sh "$DAPPER_IMAGE" -lc \ | |
| "! command -v docker >/dev/null 2>&1; printf 'package\\tversion\\n'; dpkg-query -W -f='\${binary:Package}\\t\${Version}\\n' | LC_ALL=C sort" \ | |
| > evidence/dapper-dpkg.tsv | |
| cp go.mod go.sum vendor/modules.txt evidence/ | |
| - name: Scan source, product, and build image | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker pull "$TRIVY_IMAGE" | |
| trivy_cache="$RUNNER_TEMP/trivy-cache" | |
| source_tree="$(mktemp -d)" | |
| trap 'rm -rf "$source_tree"' EXIT | |
| mkdir -p "$trivy_cache" | |
| git archive --format=tar HEAD | tar -xf - -C "$source_tree" | |
| docker run --rm \ | |
| -v "$source_tree:/scan:ro" \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" fs \ | |
| --scanners vuln,secret --format json \ | |
| --output /evidence/source-security-raw.json /scan | |
| docker run --rm \ | |
| -v "$source_tree:/scan:ro" \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" fs \ | |
| --scanners vuln,secret \ | |
| --vex /scan/security/openvex.json --show-suppressed --format json \ | |
| --output /evidence/source-security.json /scan | |
| docker run --rm \ | |
| -v "$PWD:/work:ro" \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" rootfs \ | |
| --scanners vuln,secret --format json \ | |
| --output /evidence/product-security-raw.json /evidence/product | |
| docker run --rm \ | |
| -v "$PWD:/work:ro" \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" rootfs \ | |
| --scanners vuln,secret --vex /work/security/openvex.json --format json \ | |
| --output /evidence/product-security.json /evidence/product | |
| docker run --rm \ | |
| -v /var/run/docker.sock:/var/run/docker.sock \ | |
| -v "$PWD:/work" -w /work \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" image \ | |
| --scanners vuln,secret --format json \ | |
| --output /work/evidence/dapper-image-raw.json "$DAPPER_IMAGE" | |
| jq -r ' | |
| .Results[]?.Vulnerabilities[]? | |
| | select(.Severity == "CRITICAL" or .Severity == "HIGH") | |
| | [.VulnerabilityID, .PkgIdentifier.PURL] | |
| | @tsv | |
| ' evidence/dapper-image-raw.json | LC_ALL=C sort -u \ | |
| > /tmp/dapper-critical-high.tsv | |
| grep -F $'build\tCGO_ENABLED=0' evidence/product-go-version.txt >/dev/null | |
| if grep -Eq '^linux-(image|modules)([-:]|[[:space:]])' evidence/dapper-dpkg.tsv; then | |
| printf 'Unexpected Linux kernel runtime package in Dapper image\n' >&2 | |
| exit 1 | |
| fi | |
| jq -r ' | |
| .statements[]? | |
| | select( | |
| .status == "not_affected" | |
| and .justification == "vulnerable_code_not_present" | |
| ) | |
| | .vulnerability.name as $id | |
| | .products[]? | |
| | [$id, .["@id"]] | |
| | @tsv | |
| ' security/dapper.openvex.json | LC_ALL=C sort -u \ | |
| > /tmp/dapper-vex.tsv | |
| jq -r ' | |
| .statements[] | |
| | .vulnerability.name as $id | |
| | .products[] | |
| | [$id, .["@id"]] | |
| | @tsv | |
| ' security/openvex.json | LC_ALL=C sort -u > /tmp/source-vex.tsv | |
| for raw_report in evidence/source-security-raw.json evidence/product-security-raw.json; do | |
| jq -r ' | |
| .Results[]?.Vulnerabilities[]? | |
| | [.VulnerabilityID, .PkgIdentifier.PURL] | |
| | @tsv | |
| ' "$raw_report" | LC_ALL=C sort -u > /tmp/source-raw-findings.tsv | |
| diff -u /tmp/source-vex.tsv /tmp/source-raw-findings.tsv | |
| done | |
| test -s /tmp/dapper-critical-high.tsv | |
| diff -u /tmp/dapper-critical-high.tsv /tmp/dapper-vex.tsv | |
| test "$(jq '.statements | length' security/dapper.openvex.json)" \ | |
| -eq "$(wc -l < /tmp/dapper-vex.tsv)" | |
| jq -e ' | |
| all( | |
| .statements[]; | |
| .status == "not_affected" | |
| and .justification == "vulnerable_code_not_present" | |
| and ((.impact_statement | type) == "string") | |
| and ((.impact_statement | length) > 0) | |
| ) | |
| ' security/dapper.openvex.json >/dev/null | |
| docker run --rm \ | |
| -v /var/run/docker.sock:/var/run/docker.sock \ | |
| -v "$PWD:/work" -w /work \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" image \ | |
| --scanners vuln,secret \ | |
| --vex /work/security/dapper.openvex.json --show-suppressed \ | |
| --format json \ | |
| --output /work/evidence/dapper-image-applicable.json "$DAPPER_IMAGE" | |
| docker run --rm \ | |
| -v "$source_tree:/scan:ro" \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" fs \ | |
| --format cyclonedx --output /evidence/source.cdx.json /scan | |
| docker run --rm \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" rootfs \ | |
| --format cyclonedx --output /evidence/product.cdx.json /evidence/product | |
| - name: Summarize and enforce candidate security | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| count() { | |
| jq "$2" "$1" | |
| } | |
| source_secrets=$(count evidence/source-security.json '[.Results[]?.Secrets[]?] | length') | |
| source_applicable=$(count evidence/source-security.json '[.Results[]?.Vulnerabilities[]? | select(.Status != "not_affected")] | length') | |
| source_critical=$(count evidence/source-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length') | |
| source_high=$(count evidence/source-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length') | |
| product_secrets=$(count evidence/product-security.json '[.Results[]?.Secrets[]?] | length') | |
| product_critical=$(count evidence/product-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length') | |
| product_high=$(count evidence/product-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length') | |
| product_applicable=$(count evidence/product-security.json '[.Results[]?.Vulnerabilities[]? | select(.Status != "not_affected")] | length') | |
| dapper_raw_critical=$(count evidence/dapper-image-raw.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length') | |
| dapper_raw_high=$(count evidence/dapper-image-raw.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length') | |
| dapper_applicable_critical=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL" and .Status != "not_affected")] | length') | |
| dapper_applicable_high=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" and .Status != "not_affected")] | length') | |
| dapper_secrets=$(count evidence/dapper-image-applicable.json '[.Results[]?.Secrets[]?] | length') | |
| { | |
| printf 'source_secrets=%s\n' "$source_secrets" | |
| printf 'source_applicable=%s\n' "$source_applicable" | |
| printf 'source_critical=%s\n' "$source_critical" | |
| printf 'source_high=%s\n' "$source_high" | |
| printf 'product_secrets=%s\n' "$product_secrets" | |
| printf 'product_critical=%s\n' "$product_critical" | |
| printf 'product_high=%s\n' "$product_high" | |
| printf 'product_applicable=%s\n' "$product_applicable" | |
| printf 'dapper_raw_critical=%s\n' "$dapper_raw_critical" | |
| printf 'dapper_raw_high=%s\n' "$dapper_raw_high" | |
| printf 'dapper_applicable_critical=%s\n' "$dapper_applicable_critical" | |
| printf 'dapper_applicable_high=%s\n' "$dapper_applicable_high" | |
| printf 'dapper_secrets=%s\n' "$dapper_secrets" | |
| } | tee evidence/security-summary.txt | |
| test "$source_secrets" -eq 0 | |
| test "$source_applicable" -eq 0 | |
| test "$source_critical" -eq 0 | |
| test "$source_high" -eq 0 | |
| test "$product_secrets" -eq 0 | |
| test "$product_critical" -eq 0 | |
| test "$product_high" -eq 0 | |
| test "$product_applicable" -eq 0 | |
| test "$dapper_applicable_critical" -eq 0 | |
| test "$dapper_applicable_high" -eq 0 | |
| test "$dapper_secrets" -eq 0 | |
| - name: Upload review evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: compose-cli-security-${{ github.sha }} | |
| path: evidence/ | |
| if-no-files-found: error | |
| retention-days: 30 | |
| - name: Clean runner resources | |
| if: always() | |
| shell: bash | |
| run: | | |
| set +e | |
| docker rm -f $(docker ps -aq --filter ancestor="$DAPPER_IMAGE") 2>/dev/null | |
| docker image rm -f "$DAPPER_IMAGE" "$TRIVY_IMAGE" 2>/dev/null | |
| docker builder prune --all --force >/dev/null 2>&1 | |
| rm -rf bin build dist evidence/product |