Add APK security reports
This commit is contained in:
@@ -36,10 +36,16 @@ jobs:
|
|||||||
CARGO_HOME: "/cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}/greptimedb/cargo"
|
CARGO_HOME: "/cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}/greptimedb/cargo"
|
||||||
RUSTUP_HOME: "/cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}/greptimedb/rustup"
|
RUSTUP_HOME: "/cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}/greptimedb/rustup"
|
||||||
CARGO_TARGET_DIR: "/cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}/greptimedb/target"
|
CARGO_TARGET_DIR: "/cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}/greptimedb/target"
|
||||||
|
TRIVY_VERSION: "0.74.0"
|
||||||
|
TRIVY_SHA256: "2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a"
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: |
|
run: |
|
||||||
apk add --no-cache --update abuild-rootbld alpine-sdk atools-apkbuild-lint bash ca-certificates curl doas git jq nodejs rsync shellcheck sudo tar
|
apk add --no-cache --update abuild-rootbld alpine-sdk atools-apkbuild-lint bash ca-certificates curl doas git jq nodejs rsync shellcheck sudo tar
|
||||||
|
trivy_archive="trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
|
||||||
|
curl -fsSL -o "/tmp/${trivy_archive}" "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/${trivy_archive}"
|
||||||
|
echo "${TRIVY_SHA256} /tmp/${trivy_archive}" | sha256sum -c -
|
||||||
|
tar -xzf "/tmp/${trivy_archive}" -C /usr/local/bin trivy
|
||||||
mkdir -p "$ALPINE_APK_REPO_CACHE" "$CARGO_HOME" "$RUSTUP_HOME" "$CARGO_TARGET_DIR"
|
mkdir -p "$ALPINE_APK_REPO_CACHE" "$CARGO_HOME" "$RUSTUP_HOME" "$CARGO_TARGET_DIR"
|
||||||
chown -R 1000:1000 /cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}
|
chown -R 1000:1000 /cache/public-alpine-packages/${{ matrix.alpine_registry_branch }}
|
||||||
|
|
||||||
@@ -101,6 +107,23 @@ jobs:
|
|||||||
if: github.event_name != 'schedule'
|
if: github.event_name != 'schedule'
|
||||||
run: scripts/apk/ci-smoke.sh
|
run: scripts/apk/ci-smoke.sh
|
||||||
|
|
||||||
|
- name: Generate security reports
|
||||||
|
if: github.event_name != 'schedule' && env.ALPINE_SKIP_BUILD != '1'
|
||||||
|
run: |
|
||||||
|
scripts/apk/security-report.sh
|
||||||
|
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||||
|
find packages/security-reports -name summary.md -type f -print | sort | while IFS= read -r summary; do
|
||||||
|
cat "$summary"
|
||||||
|
done >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload security reports
|
||||||
|
if: github.event_name != 'schedule' && env.ALPINE_SKIP_BUILD != '1' && steps.artifact-upload.outputs.available == 'true'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: apk-security-reports-${{ matrix.alpine_registry_branch }}
|
||||||
|
path: packages/security-reports/
|
||||||
|
|
||||||
- name: Runtime test
|
- name: Runtime test
|
||||||
if: github.event_name != 'schedule' && env.ALPINE_SKIP_BUILD != '1'
|
if: github.event_name != 'schedule' && env.ALPINE_SKIP_BUILD != '1'
|
||||||
run: scripts/apk/runtime-test.sh
|
run: scripts/apk/runtime-test.sh
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ apk:packages List built Alpine packages and dependencies
|
|||||||
apk:publish-check Run lint, multi-arch build, package listing, and smoke test
|
apk:publish-check Run lint, multi-arch build, package listing, and smoke test
|
||||||
apk:publish-gitea Publish built packages to the Gitea Alpine package registry
|
apk:publish-gitea Publish built packages to the Gitea Alpine package registry
|
||||||
apk:outdated Check packaged upstream versions and release summaries
|
apk:outdated Check packaged upstream versions and release summaries
|
||||||
|
apk:security-report Generate Trivy and CycloneDX reports for built packages
|
||||||
apk:shell Open an Alpine package build shell in Docker
|
apk:shell Open an Alpine package build shell in Docker
|
||||||
apk:smoke Install-test built packages from the local repository in Docker
|
apk:smoke Install-test built packages from the local repository in Docker
|
||||||
apk:test-install Test README installation instructions in a fresh Alpine container
|
apk:test-install Test README installation instructions in a fresh Alpine container
|
||||||
@@ -234,6 +235,20 @@ APK_UPDATE_CHECK_MODE=fail mise run apk:outdated
|
|||||||
When an update is available, the command also lists compact release-note
|
When an update is available, the command also lists compact release-note
|
||||||
summaries for every release between the packaged and latest versions.
|
summaries for every release between the packaged and latest versions.
|
||||||
|
|
||||||
|
### Security Reports and Roadmap
|
||||||
|
|
||||||
|
The [project review and development roadmap](docs/project-review.md) tracks
|
||||||
|
release-reliability goals. Generate advisory Trivy and CycloneDX reports for
|
||||||
|
built packages with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ALPINE_PACKAGE=seaweedfs mise run apk:security-report
|
||||||
|
```
|
||||||
|
|
||||||
|
Reports are written to `packages/security-reports/` and are uploaded by CI for
|
||||||
|
each selected package build. Local runs scan the newest compatible package set
|
||||||
|
available in the signed local repository.
|
||||||
|
|
||||||
### Gitea Workflow
|
### Gitea Workflow
|
||||||
|
|
||||||
`.gitea/workflows/build.yml` builds and publishes `v3.24`. Pull requests
|
`.gitea/workflows/build.yml` builds and publishes `v3.24`. Pull requests
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Project Review and Development Roadmap
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
This repository packages four upstream services for Alpine 3.24, with source
|
||||||
|
builds, binary repackages, OpenRC integration, Docker-based local builds, and
|
||||||
|
CI publication to the Gitea Alpine registry. Package selection avoids rebuilding
|
||||||
|
unchanged versions already present in the registry.
|
||||||
|
|
||||||
|
The main release risks are missing retained build artifacts, permissive duplicate
|
||||||
|
publish handling, rolling toolchain inputs, and limited end-to-end service
|
||||||
|
validation. Documentation and test defaults must remain synchronized with each
|
||||||
|
`APKBUILD` version.
|
||||||
|
|
||||||
|
## Prioritized Goals
|
||||||
|
|
||||||
|
1. **Reproducible, auditable releases.** Split build and publish work so publish
|
||||||
|
consumes a retained APK artifact plus a checksum manifest; verify registry
|
||||||
|
contents after upload and reject mismatched duplicate versions.
|
||||||
|
2. **Build security visibility.** Generate an installed-rootfs CycloneDX SBOM
|
||||||
|
and advisory Trivy report for every package and architecture selected by CI.
|
||||||
|
Retain reports as Gitea workflow artifacts.
|
||||||
|
3. **Service-level confidence.** Add bounded readiness checks for Gitea,
|
||||||
|
LavinMQ, and GreptimeDB; retain SeaweedFS role startup coverage.
|
||||||
|
4. **Automation correctness.** Add fixture-based tests for package selection,
|
||||||
|
APKINDEX parsing, metadata/version drift, and registry error paths.
|
||||||
|
5. **Developer feedback and cost.** Record build durations/cache outcomes,
|
||||||
|
keep package-scoped validation, and add a scheduled update-report workflow.
|
||||||
|
|
||||||
|
## Security Reports
|
||||||
|
|
||||||
|
`mise run apk:security-report` scans the installed root filesystem for each
|
||||||
|
selected local package. It writes advisory reports under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/security-reports/<package>/<architecture>/
|
||||||
|
├── trivy.json
|
||||||
|
├── sbom.cdx.json
|
||||||
|
└── summary.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The scan includes the package, its selected subpackages, and resolved runtime
|
||||||
|
dependencies from the signed local repository. Findings do not block a build yet; report generation,
|
||||||
|
package installation, and report validation do.
|
||||||
@@ -3,6 +3,7 @@ act = "latest"
|
|||||||
docker-cli = "latest"
|
docker-cli = "latest"
|
||||||
jq = "latest"
|
jq = "latest"
|
||||||
shellcheck = "latest"
|
shellcheck = "latest"
|
||||||
|
trivy = "latest"
|
||||||
|
|
||||||
[tasks."apk:build"]
|
[tasks."apk:build"]
|
||||||
description = "Build the Alpine package in Docker for the default architecture"
|
description = "Build the Alpine package in Docker for the default architecture"
|
||||||
@@ -69,6 +70,10 @@ run = "scripts/apk/list-packages.sh"
|
|||||||
description = "Check packaged upstream versions against latest GitHub releases"
|
description = "Check packaged upstream versions against latest GitHub releases"
|
||||||
run = "scripts/apk/outdated.sh"
|
run = "scripts/apk/outdated.sh"
|
||||||
|
|
||||||
|
[tasks."apk:security-report"]
|
||||||
|
description = "Generate Trivy and CycloneDX reports for built selected packages"
|
||||||
|
run = "scripts/apk/security-report.sh"
|
||||||
|
|
||||||
[tasks."apk:publish-check"]
|
[tasks."apk:publish-check"]
|
||||||
description = "Run lint, multi-arch build, package listing, and smoke test"
|
description = "Run lint, multi-arch build, package listing, and smoke test"
|
||||||
run = "scripts/apk/publish-check.sh"
|
run = "scripts/apk/publish-check.sh"
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ mise run apk:lint-shell
|
|||||||
mise run apk:build-all
|
mise run apk:build-all
|
||||||
mise run apk:packages
|
mise run apk:packages
|
||||||
mise run apk:smoke
|
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