Files
Joachim Schlöffel fc965e072c
Build Alpine Packages / preflight (pull_request) Successful in 15s
Build Alpine Packages / build (gitea) (pull_request) Successful in 16s
Build Alpine Packages / build (lavinmq) (pull_request) Successful in 2m57s
Build Alpine Packages / build (seaweedfs) (pull_request) Successful in 20m41s
Build Alpine Packages / build (greptimedb) (pull_request) Successful in 1h14m12s
Build Alpine Packages / verify-and-publish (pull_request) Successful in 1m47s
fix trivy db download n CI
2026-09-29 09:33:26 +02:00

184 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck source=scripts/apk/package-lib.sh
. "${repo_root}/scripts/apk/package-lib.sh"
repo_name="${ALPINE_REPO_NAME:-local}"
alpine_version="${ALPINE_VERSION:-3.24}"
arches="${ALPINE_SECURITY_ARCHES:-${ALPINE_ARCHES:-x86_64}}"
reports_dir="${APK_SECURITY_REPORTS_DIR:-${repo_root}/packages/security-reports}"
trivy_bin="${TRIVY_BIN:-trivy}"
# The GCR pull-through mirror can serve a manifest whose layer has already
# expired, returning 404 rather than letting Trivy continue to its fallback.
# Use the publisher's registry directly; callers may still select an internal
# mirror by setting TRIVY_DB_REPOSITORY.
trivy_db_repository="${TRIVY_DB_REPOSITORY:-ghcr.io/aquasecurity/trivy-db:2}"
require_command() {
command -v "$1" >/dev/null 2>&1 || {
printf 'missing required command: %s\n' "$1" >&2
exit 2
}
}
platform_for_arch() {
case "$1" in
x86_64) printf '%s\n' 'linux/amd64' ;;
aarch64) printf '%s\n' 'linux/arm64' ;;
*) printf 'unsupported Alpine architecture: %s\n' "$1" >&2; return 2 ;;
esac
}
install_rootfs() {
local rootfs="$1"
local arch="$2"
shift 2
local -a specs=("$@")
local -a apk_args=(--root "${rootfs}/installed" --initdb --no-scripts --arch "${arch}")
local key_dir repo_file native_arch
key_dir="${rootfs}/keys"
repo_file="${rootfs}/repositories"
mkdir -p "${key_dir}"
cp /etc/apk/keys/*.pub "${key_dir}/"
cp "${repo_root}/packages/${repo_name}/${arch}"/*.rsa.pub "${key_dir}/"
{
printf 'https://dl-cdn.alpinelinux.org/alpine/v%s/main\n' "${alpine_version}"
printf 'https://dl-cdn.alpinelinux.org/alpine/v%s/community\n' "${alpine_version}"
printf 'file://%s/packages/%s\n' "${repo_root}" "${repo_name}"
} > "${repo_file}"
if [[ "$(id -u)" != 0 ]]; then
apk_args+=(--usermode)
fi
native_arch="$(apk --print-arch)"
if [[ "${arch}" != "${native_arch}" ]]; then
# A native Alpine image does not necessarily carry the current signing keys
# for another architecture. Bootstrap that architecture's alpine-keys over
# HTTPS, then use the refreshed key set for the actual dependency install.
apk "${apk_args[@]}" --keys-dir "${key_dir}" --repositories-file "${repo_file}" \
--allow-untrusted add --no-cache alpine-keys
cp "${rootfs}/installed/etc/apk/keys/"*.pub "${key_dir}/"
fi
apk "${apk_args[@]}" --keys-dir "${key_dir}" --repositories-file "${repo_file}" \
add --no-cache "${specs[@]}"
}
install_rootfs_docker() {
local rootfs="$1"
local arch="$2"
shift 2
local platform
platform="$(platform_for_arch "${arch}")"
docker run --rm --platform "${platform}" \
--user "$(id -u):$(id -g)" \
-e REPO_NAME="${repo_name}" \
-e ALPINE_ARCH="${arch}" \
-e ALPINE_VERSION="${alpine_version}" \
-v "${repo_root}:/work:ro" \
-v "${rootfs}:/scan" \
"alpine:${alpine_version}" \
sh -ec '
mkdir -p /scan/keys
cp /etc/apk/keys/*.pub /scan/keys/
cp /work/packages/"$REPO_NAME"/"$ALPINE_ARCH"/*.rsa.pub /scan/keys/
{
printf "https://dl-cdn.alpinelinux.org/alpine/v%s/main\\n" "$ALPINE_VERSION"
printf "https://dl-cdn.alpinelinux.org/alpine/v%s/community\\n" "$ALPINE_VERSION"
printf "file:///work/packages/%s\\n" "$REPO_NAME"
} > /scan/repositories
apk --root /scan/installed --usermode --initdb --no-scripts --arch "$ALPINE_ARCH" \
--keys-dir /scan/keys --repositories-file /scan/repositories \
add --no-cache "$@"
' security-report "$@"
}
write_summary() {
local report="$1"
local summary="$2"
local package_name="$3"
local arch="$4"
local total
total="$(jq '[.Results[]?.Vulnerabilities[]?] | length' "${report}")"
{
printf '# %s (%s) security report\n\n' "${package_name}" "${arch}"
printf 'Installed APK rootfs scanned with Trivy. Findings are advisory.\n\n'
printf '| Severity | Count |\n| --- | ---: |\n'
for severity in CRITICAL HIGH MEDIUM LOW UNKNOWN; do
printf '| %s | %s |\n' "${severity}" \
"$(jq --arg severity "${severity}" '[.Results[]?.Vulnerabilities[]? | select(.Severity == $severity)] | length' "${report}")"
done
printf '\nTotal vulnerabilities: %s\n' "${total}"
} > "${summary}"
}
print_report() {
local report_dir="$1"
local package_name="$2"
local arch="$3"
printf '\nSecurity reports for %s (%s):\n' "${package_name}" "${arch}"
printf ' Trivy: file://%s/trivy.json\n' "${report_dir}"
printf ' SBOM: file://%s/sbom.cdx.json\n' "${report_dir}"
printf ' Summary: file://%s/summary.md\n' "${report_dir}"
cat "${report_dir}/summary.md"
printf '\n'
}
require_command "${trivy_bin}"
require_command jq
if ! command -v apk >/dev/null 2>&1; then
require_command docker
fi
tmpdir="$(mktemp -d)"
cleanup() {
rm -rf "${tmpdir}"
}
trap cleanup EXIT
while IFS= read -r package_dir; do
apk_validate_package_dir "${package_dir}"
package_name="$(apk_package_name "${package_dir}")"
for arch in ${arches}; do
apk_package_supports_arch "${package_dir}" "${arch}" || continue
repo_dir="${repo_root}/packages/${repo_name}/${arch}"
if [[ ! -f "${repo_dir}/APKINDEX.tar.gz" ]] || ! compgen -G "${repo_dir}/*.rsa.pub" >/dev/null; then
printf 'missing signed local repository for %s: %s\n' "${package_name}" "${repo_dir}" >&2
exit 1
fi
mapfile -t specs < <(
apk_package_versioned_names "${package_dir}" "${arch}" | sed 's/=.*//'
)
specs=(alpine-baselayout "${specs[@]}")
report_dir="${reports_dir}/${package_name}/${arch}"
rootfs="${tmpdir}/${package_name}-${arch}"
mkdir -p "${report_dir}" "${rootfs}"
printf 'Generating security reports for %s (%s)\n' "${package_name}" "${arch}"
if command -v apk >/dev/null 2>&1; then
install_rootfs "${rootfs}" "${arch}" "${specs[@]}"
else
install_rootfs_docker "${rootfs}" "${arch}" "${specs[@]}"
fi
"${trivy_bin}" filesystem --db-repository "${trivy_db_repository}" --distro "alpine/${alpine_version}" --scanners vuln --format json --no-progress \
--output "${report_dir}/trivy.json" "${rootfs}/installed"
"${trivy_bin}" filesystem --db-repository "${trivy_db_repository}" --distro "alpine/${alpine_version}" --scanners vuln --format cyclonedx --no-progress \
--output "${report_dir}/sbom.cdx.json" "${rootfs}/installed"
jq -e 'type == "object" and has("ArtifactName")' "${report_dir}/trivy.json" >/dev/null
jq -e '.bomFormat == "CycloneDX"' "${report_dir}/sbom.cdx.json" >/dev/null
write_summary "${report_dir}/trivy.json" "${report_dir}/summary.md" "${package_name}" "${arch}"
print_report "${report_dir}" "${package_name}" "${arch}"
done
done < <(apk_package_dirs "${repo_root}")