Repository navigation
docs: align Compose1440 published status and usage #44
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.40 | |
| 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 ubuntu-security-packages.lock scripts/install-ubuntu-security-updates.sh .github/dapper-headers.json .github/dapper-unfixed.json 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.40-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.40' >/dev/null | |
| cp "$artifact" evidence/ | |
| ( | |
| cd evidence | |
| sha256sum "$(basename "$artifact")" > 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 | |
| grep -F 'go1.27.3' evidence/product-go-version.txt >/dev/null | |
| - 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 \ | |
| "if command -v docker >/dev/null 2>&1; then echo Unexpected Docker CLI >&2; exit 1; fi; 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 --list-all-pkgs --format json \ | |
| --output /work/evidence/dapper-image-raw.json "$DAPPER_IMAGE" | |
| docker run --rm --entrypoint python3 "$DAPPER_IMAGE" \ | |
| /opt/python-security/scripts/check-pip-vendor-slice.py \ | |
| > evidence/pip-vendor-proof.json | |
| python3 scripts/pip-security/test-pip-vex.py | |
| python3 scripts/pip-security/filter-pip-vendor-findings.py \ | |
| --raw evidence/dapper-image-raw.json --proof evidence/pip-vendor-proof.json \ | |
| --output evidence/dapper-image-applicable.json --vex evidence/pip-vendor.openvex.json \ | |
| --subject "$(docker image inspect --format '{{.Id}}' "$DAPPER_IMAGE")" | |
| bash .github/scripts/check-builder-header-boundary.sh "$DAPPER_IMAGE" > evidence/dapper-header-boundary.json | |
| python3 .github/scripts/test_builder_vulnerability_policy.py | |
| python3 .github/scripts/enforce-builder-vulnerabilities.py < evidence/dapper-image-applicable.json | | |
| tee evidence/dapper-full-severity-policy.txt | |
| jq -r ' | |
| .Results[]?.Vulnerabilities[]? | |
| | select(.Severity == "CRITICAL" or .Severity == "HIGH") | |
| | [.VulnerabilityID, .PkgIdentifier.PURL] | |
| | @tsv | |
| ' evidence/dapper-image-applicable.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 | |
| dapper_unreviewed="$(jq '[ | |
| .Results[]?.Vulnerabilities[]? | |
| | select(.Severity == "CRITICAL" or .Severity == "HIGH") | |
| | select(.PkgName != "linux-libc-dev" or ((.FixedVersion // "") != "")) | |
| ] | length' evidence/dapper-image-applicable.json)" | |
| test "$dapper_unreviewed" -eq 0 | |
| jq -n \ | |
| --slurpfile report evidence/dapper-image-applicable.json \ | |
| --arg author 'PastureStack Security' \ | |
| --arg timestamp "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ | |
| --arg impact 'linux-libc-dev supplies user-space kernel API headers only in the disposable build and race-test image. The package is not copied into the CGO-disabled product binary, so the vulnerable kernel implementation is not shipped.' \ | |
| '{ | |
| "@context":"https://openvex.dev/ns/v0.2.0", | |
| "@id":("https://github.com/PastureStack/compose-cli/security/dapper/" + $timestamp), | |
| author:$author, | |
| timestamp:$timestamp, | |
| version:1, | |
| statements:[ | |
| $report[0].Results[]?.Vulnerabilities[]? | |
| | select((.Severity == "CRITICAL" or .Severity == "HIGH") and .PkgName == "linux-libc-dev" and ((.FixedVersion // "") == "")) | |
| | { | |
| vulnerability:{name:.VulnerabilityID}, | |
| products:[{"@id":.PkgIdentifier.PURL}], | |
| status:"not_affected", | |
| justification:"vulnerable_code_not_present", | |
| impact_statement:$impact | |
| } | |
| ] | |
| }' > evidence/dapper.openvex.json | |
| 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 "$(jq '.statements | length' evidence/dapper.openvex.json)" \ | |
| -eq "$(wc -l < /tmp/dapper-critical-high.tsv)" | |
| 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-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length') | |
| dapper_raw_high=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length') | |
| dapper_reviewed=$(count evidence/dapper.openvex.json '.statements | length') | |
| dapper_unreviewed=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL" or .Severity == "HIGH") | select(.PkgName != "linux-libc-dev" or ((.FixedVersion // "") != ""))] | 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_reviewed_unfixed_linux_libc_dev=%s\n' "$dapper_reviewed" | |
| printf 'dapper_unreviewed_critical_high=%s\n' "$dapper_unreviewed" | |
| 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_unreviewed" -eq 0 | |
| test "$dapper_reviewed" -eq "$((dapper_raw_critical + dapper_raw_high))" | |
| 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 |