Add APK security reports
This commit is contained in:
@@ -10,3 +10,4 @@ mise run apk:lint-shell
|
||||
mise run apk:build-all
|
||||
mise run apk:packages
|
||||
mise run apk:smoke
|
||||
mise run apk:security-report
|
||||
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/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}"
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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 --distro "alpine/${alpine_version}" --scanners vuln --format json --no-progress \
|
||||
--output "${report_dir}/trivy.json" "${rootfs}/installed"
|
||||
"${trivy_bin}" filesystem --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}")
|
||||
Reference in New Issue
Block a user