Repository navigation
Pin Ubuntu build snapshot #19
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: | |
| pull_request: | |
| branches: | |
| - main | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: host-provisioner-security-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| scan: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 120 | |
| env: | |
| DAPPER_IMAGE: pasturestack/host-provisioner-dapper:${{ github.sha }} | |
| TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 | |
| VERSION_OVERRIDE: v0.39.5 | |
| SOURCE_DATE_EPOCH: '0' | |
| steps: | |
| - name: Check out candidate | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Record immutable source identity | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -z "$(git status --porcelain)" | |
| git merge-base --is-ancestor 22d7baca609c18589f08225a97bf8c5e00109204 HEAD | |
| mkdir -p evidence | |
| git rev-parse HEAD > evidence/source-revision.txt | |
| sha256sum Dockerfile.dapper Makefile .github/workflows/*.yml > evidence/source-locks.sha256 | |
| - name: Build and test immutable product | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| make DAPPER_IMAGE="$DAPPER_IMAGE" ci | |
| test -s dist/artifacts/host-provisioner-0.39.5-linux-amd64.tar.xz | |
| package_dir='host-provisioner-0.39.5-linux-amd64' | |
| tar -tJf dist/artifacts/host-provisioner-0.39.5-linux-amd64.tar.xz | grep -Fx "${package_dir}/host-provisioner" | |
| tar -tJf dist/artifacts/host-provisioner-0.39.5-linux-amd64.tar.xz | grep -Fx "${package_dir}/LICENSE" | |
| tar -tJf dist/artifacts/host-provisioner-0.39.5-linux-amd64.tar.xz | grep -Fx "${package_dir}/ORIGIN.md" | |
| expected_legal=$(find vendor -type f \( \ | |
| -name 'LICENSE' -o -name 'LICENSE.*' -o \ | |
| -name 'NOTICE' -o -name 'NOTICE.*' -o \ | |
| -name 'COPYING' -o -name 'COPYING.*' -o \ | |
| -name 'AUTHORS' -o -name 'AUTHORS.*' \ | |
| \) | wc -l) | |
| actual_legal=$(tar -tJf dist/artifacts/host-provisioner-0.39.5-linux-amd64.tar.xz | grep -Ec "^${package_dir}/licenses/.+/(LICENSE|LICENSE\..*|NOTICE|NOTICE\..*|COPYING|COPYING\..*|AUTHORS|AUTHORS\..*)$") | |
| test "$expected_legal" -gt 0 | |
| test "$actual_legal" -eq "$expected_legal" | |
| sha256sum dist/artifacts/host-provisioner-0.39.5-linux-amd64.tar.xz bin/host-provisioner > evidence/product-artifacts.sha256 | |
| docker run --rm --entrypoint go -v "$PWD:/work:ro" "$DAPPER_IMAGE" \ | |
| version -m /work/bin/host-provisioner > evidence/product-go-version.txt | |
| grep -F $'build\tCGO_ENABLED=0' evidence/product-go-version.txt >/dev/null | |
| - name: Scan source, product, and build image | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker pull "$TRIVY_IMAGE" | |
| cache="$RUNNER_TEMP/trivy-cache" | |
| source_tree="$(mktemp -d)" | |
| product_tree="$(mktemp -d)" | |
| mkdir -p "$cache" "$product_tree" | |
| trap 'rm -rf "$source_tree" "$product_tree"' EXIT | |
| git archive --format=tar HEAD | tar -xf - -C "$source_tree" | |
| cp bin/host-provisioner "$product_tree/host-provisioner" | |
| docker run --rm -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" image --download-db-only | |
| docker run --rm -v "$source_tree:/scan:ro" -v "$PWD/evidence:/evidence" -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" fs \ | |
| --skip-db-update --offline-scan --scanners vuln,secret --format json --output /evidence/source-security.json /scan | |
| docker run --rm -v "$product_tree:/scan:ro" -v "$PWD/evidence:/evidence" -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" rootfs \ | |
| --skip-db-update --offline-scan --scanners vuln,secret --format json --output /evidence/product-security.json /scan | |
| docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD:/work" -w /work -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" image \ | |
| --skip-db-update --offline-scan --scanners vuln,secret --format json --output /work/evidence/dapper-security.json "$DAPPER_IMAGE" | |
| docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD:/work" -w /work -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" image \ | |
| --skip-db-update --offline-scan --format cyclonedx --output /work/evidence/dapper.cdx.json "$DAPPER_IMAGE" | |
| - name: Enforce product safety and review build-only headers | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| : > evidence/security-summary.txt | |
| for scope in source product; do | |
| report="evidence/${scope}-security.json" | |
| secrets=$(jq '[.Results[]?.Secrets[]?] | length' "$report") | |
| critical=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' "$report") | |
| high=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' "$report") | |
| printf '%s_secrets=%s\n%s_critical=%s\n%s_high=%s\n' "$scope" "$secrets" "$scope" "$critical" "$scope" "$high" | tee -a evidence/security-summary.txt | |
| test "$secrets" -eq 0 | |
| test "$critical" -eq 0 | |
| test "$high" -eq 0 | |
| done | |
| dapper_secrets=$(jq '[.Results[]?.Secrets[]?] | length' evidence/dapper-security.json) | |
| dapper_raw_critical=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' evidence/dapper-security.json) | |
| dapper_raw_high=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' evidence/dapper-security.json) | |
| jq '[ | |
| .Results[]?.Vulnerabilities[]? | |
| | select(.Severity == "CRITICAL" or .Severity == "HIGH") | |
| | { | |
| vulnerabilityId: .VulnerabilityID, | |
| severity: .Severity, | |
| package: .PkgName, | |
| installedVersion: .InstalledVersion, | |
| fixedVersion: (.FixedVersion // ""), | |
| status: .Status, | |
| purl: (.PkgIdentifier.PURL // "") | |
| } | |
| ]' evidence/dapper-security.json > evidence/dapper-critical-high-review.json | |
| dapper_unreviewed_critical_high=$(jq '[ | |
| .[] | |
| | select( | |
| .package != "linux-libc-dev" | |
| or .status != "affected" | |
| or .fixedVersion != "" | |
| or (.purl | startswith("pkg:deb/ubuntu/linux-libc-dev@") | not) | |
| ) | |
| ] | length' evidence/dapper-critical-high-review.json) | |
| printf 'dapper_secrets=%s\ndapper_raw_critical=%s\ndapper_raw_high=%s\ndapper_reviewed_build_only_headers=%s\ndapper_unreviewed_critical_high=%s\n' \ | |
| "$dapper_secrets" "$dapper_raw_critical" "$dapper_raw_high" "$((dapper_raw_critical + dapper_raw_high))" "$dapper_unreviewed_critical_high" | tee -a evidence/security-summary.txt | |
| test "$dapper_secrets" -eq 0 | |
| test "$dapper_unreviewed_critical_high" -eq 0 | |
| python3 - <<'PY' | |
| import json | |
| with open('evidence/dapper.cdx.json', encoding='utf-8') as stream: | |
| bom = json.load(stream) | |
| assert bom.get('bomFormat') == 'CycloneDX' | |
| assert str(bom.get('specVersion')) == '1.7' | |
| assert len(bom.get('components', [])) > 0 | |
| print('dapper_components={}'.format(len(bom['components']))) | |
| PY | |
| - name: Upload reviewed evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: host-provisioner-security-${{ github.sha }} | |
| path: evidence/ | |
| if-no-files-found: error | |
| retention-days: 30 | |
| compression-level: 9 | |
| - name: Clean runner resources | |
| if: always() | |
| shell: bash | |
| run: | | |
| set +e | |
| docker image rm -f "$DAPPER_IMAGE" "$TRIVY_IMAGE" 2>/dev/null | |
| docker builder prune --all --force >/dev/null 2>&1 | |
| sudo rm -rf bin dist |