Skip to content

build: minimize HAProxy runtime on Alpine 3.24 #21

build: minimize HAProxy runtime on Alpine 3.24

build: minimize HAProxy runtime on Alpine 3.24 #21

name: Security release gate
on:
push:
branches:
- main
- 'verification/load-balancer-controller-*'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: load-balancer-controller-security-${{ github.ref }}
cancel-in-progress: false
jobs:
build-test-scan:
runs-on: ubuntu-24.04
timeout-minutes: 120
env:
SERVICE_IMAGE: local/pasturestack/load-balancer-service:security-${{ github.sha }}
TRIVY_IMAGE: ghcr.io/aquasecurity/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
VERSION_OVERRIDE: security-${{ github.sha }}
steps:
- name: Check out candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install checksum-pinned Go and govulncheck
shell: bash
run: |
set -euo pipefail
archive="$RUNNER_TEMP/go1.27.0.linux-amd64.tar.gz"
curl --fail --silent --show-error --location --output "$archive" \
'https://go.dev/dl/go1.27.0.linux-amd64.tar.gz'
printf '%s %s\n' \
'675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685' \
"$archive" | sha256sum --check
tar -C "$RUNNER_TEMP" -xzf "$archive"
export PATH="$RUNNER_TEMP/go/bin:$PATH"
export GOBIN="$RUNNER_TEMP/security-bin"
export GOTELEMETRY=off
mkdir -p "$GOBIN"
go install golang.org/x/vuln/cmd/govulncheck@v1.7.0
printf '%s\n' "$RUNNER_TEMP/go/bin" "$GOBIN" >> "$GITHUB_PATH"
printf 'GOROOT=%s\n' "$RUNNER_TEMP/go" >> "$GITHUB_ENV"
- name: Test the maintained source path
shell: bash
run: |
set -euo pipefail
mkdir -p evidence
git diff --check HEAD -- . ':(exclude)vendor/**'
./scripts/test
./scripts/validate
govulncheck -show verbose ./... | tee evidence/govulncheck.txt
- name: Scan source and create source SBOM
shell: bash
run: |
set -euo pipefail
docker pull "$TRIVY_IMAGE"
cache="$RUNNER_TEMP/trivy-cache"
scan="$RUNNER_TEMP/trivy-output"
mkdir -p "$cache" "$scan"
docker run --rm -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" image --download-db-only
docker run --rm -v "$PWD:/work:ro" -v "$scan:/scan" \
-v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" fs \
--skip-db-update --offline-scan --scanners vuln,secret \
--severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --exit-code 1 \
--format json --output /scan/source-security.json /work
docker run --rm -v "$PWD:/work:ro" -v "$scan:/scan" \
-v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" fs \
--skip-db-update --offline-scan --format cyclonedx \
--output /scan/source.cdx.json /work
cp "$scan/source-security.json" "$scan/source.cdx.json" evidence/
- name: Build reproducible binary and runtime image
shell: bash
run: |
set -euo pipefail
source_epoch="$(git show -s --format=%ct HEAD)"
scripts/build
cp bin/lb-controller "$RUNNER_TEMP/lb-controller.first"
rm -f bin/lb-controller
scripts/build
cmp "$RUNNER_TEMP/lb-controller.first" bin/lb-controller
sha256sum bin/lb-controller > evidence/product-binary.sha256
ARCH=amd64 TAG="security-${GITHUB_SHA}" REPO=local/pasturestack \
SOURCE_DATE_EPOCH="$source_epoch" scripts/package
test "$(wc -l < dist/images)" -eq 1
grep -Fxq "$SERVICE_IMAGE" dist/images
docker run --rm --entrypoint /usr/bin/lb-controller "$SERVICE_IMAGE" --version \
| grep -Fq "security-${GITHUB_SHA}"
docker run --rm --entrypoint /usr/sbin/haproxy "$SERVICE_IMAGE" -vv \
| tee evidence/haproxy-version.txt
grep -Fq 'HAProxy version 3.4.3' evidence/haproxy-version.txt
test "$(docker image inspect --format '{{.Config.User}}' "$SERVICE_IMAGE")" = '10001:10001'
- name: Scan binary and runtime image
shell: bash
run: |
set -euo pipefail
cache="$RUNNER_TEMP/trivy-cache"
docker run --rm -v "$PWD/bin:/product:ro" -v "$PWD/evidence:/evidence" \
-v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" rootfs \
--skip-db-update --offline-scan --scanners vuln \
--severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --exit-code 1 \
--format json --output /evidence/product-security.json /product
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD/evidence:/evidence" -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" image \
--skip-db-update --offline-scan --scanners vuln \
--severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --exit-code 1 \
--format json --output /evidence/image-security.json "$SERVICE_IMAGE"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD/evidence:/evidence" -v "$cache:/root/.cache/trivy" "$TRIVY_IMAGE" image \
--skip-db-update --offline-scan --format cyclonedx \
--output /evidence/image.cdx.json "$SERVICE_IMAGE"
python3 - <<'PY'
import json
from pathlib import Path
for name in ('source-security.json', 'product-security.json', 'image-security.json'):
report = json.loads((Path('evidence') / name).read_text())
vulnerabilities = sum(len(item.get('Vulnerabilities') or []) for item in report.get('Results') or [])
secrets = sum(len(item.get('Secrets') or []) for item in report.get('Results') or [])
assert vulnerabilities == 0, (name, vulnerabilities)
assert secrets == 0, (name, secrets)
for name in ('source.cdx.json', 'image.cdx.json'):
bom = json.loads((Path('evidence') / name).read_text())
assert bom.get('serialNumber')
assert len(bom.get('components') or []) > 0
assert len(bom.get('dependencies') or []) > 0
PY
- name: Upload verification evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: load-balancer-controller-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 "$SERVICE_IMAGE" "$TRIVY_IMAGE" 2>/dev/null
docker builder prune --all --force >/dev/null 2>&1
sudo rm -rf bin dist