-
Notifications
You must be signed in to change notification settings - Fork 0
feat: sign releases and publish SBOMs #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqanVKBvzbrySoIvurg2hvQ/BYcVX | ||
| oE457ag3IX77JbP3qCXoYD4Ws9NCMwhbIwE/Cu5LcZnpzxn3IyUAW95zUw== | ||
| -----END PUBLIC KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| TAG="${1:-${CI_COMMIT_TAG:-}}" | ||
| if [[ -z "${TAG}" ]]; then | ||
| echo "TAG is required (arg1 or CI_COMMIT_TAG)" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" | ||
| OUT_DIR="${ROOT}/dist/${TAG}" | ||
|
|
||
| command -v syft >/dev/null 2>&1 || { | ||
| echo "syft is required on PATH" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| mkdir -p "${OUT_DIR}" | ||
| TMP_DIR="$(mktemp -d)" | ||
| trap 'rm -rf "${TMP_DIR}"' EXIT | ||
|
|
||
| export SYFT_CHECK_FOR_APP_UPDATE=false | ||
| export XDG_CACHE_HOME="${TMP_DIR}/cache" | ||
| mkdir -p "${XDG_CACHE_HOME}" | ||
|
|
||
| artifact_stem() { | ||
| local file_name="$1" | ||
| file_name="${file_name%.tar.gz}" | ||
| file_name="${file_name%.zip}" | ||
| printf '%s\n' "${file_name}" | ||
| } | ||
|
|
||
| shopt -s nullglob | ||
| archives=("${OUT_DIR}"/profitctl_"${TAG}"_*.tar.gz "${OUT_DIR}"/profitctl_"${TAG}"_*.zip) | ||
| shopt -u nullglob | ||
|
|
||
| if [[ "${#archives[@]}" -eq 0 ]]; then | ||
| echo "no release archives found in ${OUT_DIR}" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| for archive in "${archives[@]}"; do | ||
| stem="$(artifact_stem "$(basename "${archive}")")" | ||
| syft scan "file:${archive}" \ | ||
| --quiet \ | ||
| --source-name "${stem}" \ | ||
| --source-version "${TAG}" \ | ||
| --output "spdx-json=${OUT_DIR}/${stem}.spdx.json" | ||
| done | ||
|
|
||
| syft scan "dir:${ROOT}" \ | ||
| --quiet \ | ||
| --exclude "./.git" \ | ||
| --exclude "./dist" \ | ||
| --source-name "profitctl-source" \ | ||
| --source-version "${TAG}" \ | ||
| --output "spdx-json=${OUT_DIR}/profitctl_${TAG}_source.spdx.json" | ||
|
|
||
| echo "SBOMs written in ${OUT_DIR}" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| TOOL="${1:-}" | ||||||||||||||||||||||||||||||||||
| INSTALL_DIR="${2:-$(pwd)/.release-tools}" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if [[ -z "${TOOL}" ]]; then | ||||||||||||||||||||||||||||||||||
| echo "usage: $0 <syft|cosign> [install-dir]" >&2 | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| OS_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')" | ||||||||||||||||||||||||||||||||||
| case "${OS_NAME}" in | ||||||||||||||||||||||||||||||||||
| linux|darwin) ;; | ||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||
| echo "unsupported operating system for ${TOOL}: $(uname -s)" >&2 | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case "$(uname -m)" in | ||||||||||||||||||||||||||||||||||
| x86_64|amd64) ARCH_NAME="amd64" ;; | ||||||||||||||||||||||||||||||||||
| arm64|aarch64) ARCH_NAME="arm64" ;; | ||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||
| echo "unsupported architecture for ${TOOL}: $(uname -m)" >&2 | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| mkdir -p "${INSTALL_DIR}" | ||||||||||||||||||||||||||||||||||
| TMP_DIR="$(mktemp -d)" | ||||||||||||||||||||||||||||||||||
| trap 'rm -rf "${TMP_DIR}"' EXIT | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| download() { | ||||||||||||||||||||||||||||||||||
| local url="$1" | ||||||||||||||||||||||||||||||||||
| local output="$2" | ||||||||||||||||||||||||||||||||||
| curl -fsSL --retry 5 --retry-all-errors "${url}" -o "${output}" | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case "${TOOL}" in | ||||||||||||||||||||||||||||||||||
| syft) | ||||||||||||||||||||||||||||||||||
| VERSION="${SYFT_VERSION:-v1.42.4}" | ||||||||||||||||||||||||||||||||||
| VERSION_NO_V="${VERSION#v}" | ||||||||||||||||||||||||||||||||||
| ARCHIVE="syft_${VERSION_NO_V}_${OS_NAME}_${ARCH_NAME}.tar.gz" | ||||||||||||||||||||||||||||||||||
| URL="https://github.com/anchore/syft/releases/download/${VERSION}/${ARCHIVE}" | ||||||||||||||||||||||||||||||||||
| download "${URL}" "${TMP_DIR}/${ARCHIVE}" | ||||||||||||||||||||||||||||||||||
| tar -xzf "${TMP_DIR}/${ARCHIVE}" -C "${INSTALL_DIR}" syft | ||||||||||||||||||||||||||||||||||
| chmod +x "${INSTALL_DIR}/syft" | ||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||
| cosign) | ||||||||||||||||||||||||||||||||||
| VERSION="${COSIGN_VERSION:-v3.0.6}" | ||||||||||||||||||||||||||||||||||
| URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/cosign-${OS_NAME}-${ARCH_NAME}" | ||||||||||||||||||||||||||||||||||
| download "${URL}" "${INSTALL_DIR}/cosign" | ||||||||||||||||||||||||||||||||||
| chmod +x "${INSTALL_DIR}/cosign" | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The cosign binary is fetched over HTTPS and made executable immediately, with no checksum or signature check. If the GitHub release asset is tampered with or the CDN is compromised, the binary used to sign every subsequent release artifact would be malicious — undermining the entire signing chain. Sigstore publishes
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: scripts/release/install-tool.sh
Line: 50-54
Comment:
**cosign binary downloaded without integrity verification**
The cosign binary is fetched over HTTPS and made executable immediately, with no checksum or signature check. If the GitHub release asset is tampered with or the CDN is compromised, the binary used to sign every subsequent release artifact would be malicious — undermining the entire signing chain. Sigstore publishes `cosign-checksums.txt` alongside each release; verifying the SHA-256 of the downloaded binary against that file (or a pinned hash in this repo) would close this gap.
```suggestion
cosign)
VERSION="${COSIGN_VERSION:-v3.0.6}"
CHECKSUM_URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/cosign-checksums.txt"
BINARY="cosign-${OS_NAME}-${ARCH_NAME}"
URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/${BINARY}"
download "${URL}" "${TMP_DIR}/${BINARY}"
download "${CHECKSUM_URL}" "${TMP_DIR}/cosign-checksums.txt"
(cd "${TMP_DIR}" && grep "${BINARY}" cosign-checksums.txt | sha256sum --check --status)
cp "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/cosign"
chmod +x "${INSTALL_DIR}/cosign"
;;
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||
| echo "unsupported tool: ${TOOL}" >&2 | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| echo "${INSTALL_DIR}/${TOOL}" | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| TAG="${1:-${CI_COMMIT_TAG:-}}" | ||
| if [[ -z "${TAG}" ]]; then | ||
| echo "TAG is required (arg1 or CI_COMMIT_TAG)" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ -z "${COSIGN_PRIVATE_KEY:-}" ]]; then | ||
| echo "COSIGN_PRIVATE_KEY is required" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ -z "${COSIGN_PASSWORD:-}" ]]; then | ||
| echo "COSIGN_PASSWORD is required" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" | ||
| OUT_DIR="${ROOT}/dist/${TAG}" | ||
|
|
||
| command -v cosign >/dev/null 2>&1 || { | ||
| echo "cosign is required on PATH" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| TMP_DIR="$(mktemp -d)" | ||
| trap 'rm -rf "${TMP_DIR}"' EXIT | ||
| export HOME="${TMP_DIR}/home" | ||
| mkdir -p "${HOME}" | ||
|
|
||
| sign_blob() { | ||
| local blob_path="$1" | ||
| rm -f "${blob_path}.sig" | ||
| cosign sign-blob \ | ||
| --key env://COSIGN_PRIVATE_KEY \ | ||
| --tlog-upload=false \ | ||
| --use-signing-config=false \ | ||
| --new-bundle-format=false \ | ||
| --output-signature "${blob_path}.sig" \ | ||
| --yes \ | ||
| "${blob_path}" >/dev/null | ||
| } | ||
|
|
||
| targets=() | ||
| while IFS= read -r target; do | ||
| targets+=("${target}") | ||
| done < <( | ||
| find "${OUT_DIR}" -maxdepth 1 -type f \ | ||
| \( -name "profitctl_${TAG}_*.tar.gz" -o -name "profitctl_${TAG}_*.zip" -o -name "profitctl_${TAG}_*.spdx.json" -o -name "SHA256SUMS" \) \ | ||
| | sort | ||
| ) | ||
|
|
||
| if [[ "${#targets[@]}" -eq 0 ]]; then | ||
| echo "no release assets found to sign in ${OUT_DIR}" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| for target in "${targets[@]}"; do | ||
| sign_blob "${target}" | ||
| done | ||
|
|
||
| echo "Signatures written in ${OUT_DIR}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curl | shinstaller runs before signing secrets are injectedcurl -Ls https://cli.doppler.com/install.sh | shexecutes an unverified remote script in the same shell session that subsequently callsdoppler run ... -- bash scripts/release/sign-release-assets.sh. A compromised or MITMed installer could plant adopplershim onPATHor create bash hooks that exfiltrateCOSIGN_PRIVATE_KEYandCOSIGN_PASSWORDthe momentdoppler runinjects them. The same pattern appears in thesmoke-published-releasestep (line ~53). Pinning the Doppler CLI to a known version and verifying its checksum before execution, or pre-baking it into the CI image, would remove this risk.Prompt To Fix With AI