Skip to content

Modernize PastureStack Compose compatibility #4

Modernize PastureStack Compose compatibility

Modernize PastureStack Compose compatibility #4

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