103 lines
2.6 KiB
Bash
103 lines
2.6 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# DEVOPS-ATTEST-74-002: package attestation outputs into an offline bundle with checksums.
|
|
# Determinism profile:
|
|
# - fixed locale/timezone
|
|
# - deterministic archive metadata/order
|
|
# - digest pin checks for optional toolchain inputs
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
echo "Usage: $0 <attest-dir> [bundle-out]" >&2
|
|
exit 64
|
|
fi
|
|
|
|
export LC_ALL=C
|
|
export TZ=UTC
|
|
|
|
SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-0}
|
|
if ! [[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]]; then
|
|
echo "[attest-bundle] SOURCE_DATE_EPOCH must be an integer epoch value" >&2
|
|
exit 64
|
|
fi
|
|
|
|
# Enforce digest pinning when toolchain/build images are provided.
|
|
for image_var in BUILDER_IMG TOOLCHAIN_IMAGE; do
|
|
image_value=${!image_var:-}
|
|
if [[ -n "$image_value" && "$image_value" != *@sha256:* ]]; then
|
|
echo "[attest-bundle] ${image_var} must be digest-pinned (@sha256:...): ${image_value}" >&2
|
|
exit 65
|
|
fi
|
|
done
|
|
|
|
ATTEST_DIR=$1
|
|
BUNDLE_OUT=${2:-"out/attest-bundles"}
|
|
|
|
if [[ ! -d "$ATTEST_DIR" ]]; then
|
|
echo "[attest-bundle] attestation directory not found: $ATTEST_DIR" >&2
|
|
exit 66
|
|
fi
|
|
|
|
mkdir -p "$BUNDLE_OUT"
|
|
|
|
BUNDLE_NAME=${BUNDLE_NAME:-"attestation-bundle-${SOURCE_DATE_EPOCH}"}
|
|
WORK_DIR="${BUNDLE_OUT}/${BUNDLE_NAME}"
|
|
rm -rf "$WORK_DIR"
|
|
mkdir -p "$WORK_DIR"
|
|
|
|
copy_if_exists() {
|
|
local pattern="$1"
|
|
shopt -s nullglob
|
|
local files=("$ATTEST_DIR"/$pattern)
|
|
if (( ${#files[@]} > 0 )); then
|
|
cp "${files[@]}" "$WORK_DIR/"
|
|
fi
|
|
shopt -u nullglob
|
|
}
|
|
|
|
# Collect common attestation artefacts
|
|
copy_if_exists "*.dsse.json"
|
|
copy_if_exists "*.in-toto.jsonl"
|
|
copy_if_exists "*.sarif"
|
|
copy_if_exists "*.intoto.json"
|
|
copy_if_exists "*.rekor.txt"
|
|
copy_if_exists "*.sig"
|
|
copy_if_exists "*.crt"
|
|
copy_if_exists "*.pem"
|
|
copy_if_exists "*.json"
|
|
|
|
mapfile -t MANIFEST_FILES < <(find "$WORK_DIR" -maxdepth 1 -type f -printf "%f\n" | sort)
|
|
FILES_JSON=$(printf '%s\n' "${MANIFEST_FILES[@]}" | jq -R . | jq -s .)
|
|
|
|
# Manifest
|
|
cat > "${WORK_DIR}/manifest.json" <<EOF
|
|
{
|
|
"created_at": "$(date -u -d "@${SOURCE_DATE_EPOCH}" +"%Y-%m-%dT%H:%M:%SZ")",
|
|
"source_dir": "$(basename "${ATTEST_DIR}")",
|
|
"source_date_epoch": ${SOURCE_DATE_EPOCH},
|
|
"files": ${FILES_JSON}
|
|
}
|
|
EOF
|
|
|
|
find "$WORK_DIR" -type d -exec chmod 0755 {} +
|
|
find "$WORK_DIR" -type f -exec chmod 0644 {} +
|
|
|
|
# Checksums
|
|
(
|
|
cd "$WORK_DIR"
|
|
find . -maxdepth 1 -type f -printf "%f\n" | sort | xargs -r sha256sum > SHA256SUMS
|
|
)
|
|
|
|
GZIP=-n tar \
|
|
--sort=name \
|
|
--mtime="@${SOURCE_DATE_EPOCH}" \
|
|
--owner=0 \
|
|
--group=0 \
|
|
--numeric-owner \
|
|
--pax-option=delete=atime,delete=ctime \
|
|
-C "$BUNDLE_OUT" \
|
|
-czf "${WORK_DIR}.tgz" \
|
|
"${BUNDLE_NAME}"
|
|
|
|
echo "[attest-bundle] bundle created at ${WORK_DIR}.tgz"
|