Modernize PastureStack Compose compatibility #4
Workflow file for this run
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: | |
| - '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.73.0@sha256:7cced7cae583819fc7806d4cbc0dbbc7cad18b99f7d3e235192e6da8c091045c | |
| VERSION_OVERRIDE: v0.14.32 | |
| PLATFORM_COMPAT_JAR_URL: https://github.com/PastureStack/server/releases/download/v1.6.346/orchestration-engine-0.183.276.jar | |
| PLATFORM_COMPAT_JAR_SHA256: 64bc18a1654b73116dce89f29ede7b4c629a8c5af236b482ef95f50a78ed6376 | |
| steps: | |
| - name: Check out candidate | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Record candidate identity | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -z "$(git status --porcelain)" | |
| mkdir -p evidence | |
| git rev-parse HEAD > evidence/source-revision.txt | |
| sha256sum Dockerfile.dapper ubuntu-apt.lock vendor.conf > 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" \ | |
| --volume /var/run/docker.sock:/var/run/docker.sock \ | |
| --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.32-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.32' >/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 \ | |
| "printf 'package\\tversion\\n'; dpkg-query -W -f='\${binary:Package}\\t\${Version}\\n' | LC_ALL=C sort" \ | |
| > evidence/dapper-dpkg.tsv | |
| cp vendor.conf evidence/vendor.conf | |
| - 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.json /scan | |
| docker run --rm \ | |
| -v "$PWD/evidence:/evidence" \ | |
| -v "$trivy_cache:/root/.cache/trivy" \ | |
| "$TRIVY_IMAGE" rootfs \ | |
| --scanners vuln,secret --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 | |
| 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 | |
| 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 | |
| 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 | |
| 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_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') | |
| 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_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 '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 "$product_secrets" -eq 0 | |
| test "$product_critical" -eq 0 | |
| test "$product_high" -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 | |
| 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 |