diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c7d5d3e0d..dc3f0f08fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,6 +74,9 @@ jobs: tar -C "$RUNNER_TEMP/microsoft" -xf "$RUNNER_TEMP/msgo.tgz" echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" + - name: Install rpm (provides rpmsign for RPM package signing) + run: sudo apt-get install -y rpm gnupg2 + - name: Release Notes run: ./resources/scripts/release_notes.sh > ./release_notes.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 385f2287e8..e25fb82263 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,91 @@ jobs: skip-cache: true skip-save-cache: true + test-push-to-gcp-ar: + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install rpm tools + run: sudo apt-get install -y rpm gnupg2 + + - name: Set up test GPG key and test RPM + run: | + # Generate a throwaway GPG key — no passphrase, expires never + printf '%s\n' \ + '%no-protection' \ + 'Key-Type: RSA' \ + 'Key-Length: 2048' \ + 'Name-Real: Test Signing Key' \ + 'Name-Email: test@redpanda.com' \ + 'Expire-Date: 0' \ + | gpg --batch --gen-key + # Export private key so the aws mock can serve it + gpg --armor --export-secret-keys > /tmp/test-signing-key.asc + + # Build a minimal empty RPM + mkdir -p /tmp/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + printf '%s\n' \ + 'Name: test-pkg' \ + 'Version: 1.0.0' \ + 'Release: 1' \ + 'Summary: Test package for RPM signing CI' \ + 'License: MIT' \ + 'BuildArch: noarch' \ + '%description' \ + 'Minimal test RPM.' \ + '%install' \ + 'mkdir -p %{buildroot}' \ + '%files' \ + > /tmp/rpmbuild/SPECS/test-pkg.spec + rpmbuild --define "_topdir /tmp/rpmbuild" -bb /tmp/rpmbuild/SPECS/test-pkg.spec + cp /tmp/rpmbuild/RPMS/noarch/test-pkg-1.0.0-1.noarch.rpm /tmp/test.rpm + + - name: Mock aws and gcloud CLIs + run: | + # aws: return the test private key for any secretsmanager get-secret-value call + printf '#!/bin/bash\ncat /tmp/test-signing-key.asc\n' \ + | sudo tee /usr/local/bin/aws > /dev/null + sudo chmod +x /usr/local/bin/aws + # gcloud: echo arguments so we can assert routing + printf '#!/bin/bash\necho "gcloud $*"\n' \ + | sudo tee /usr/local/bin/gcloud > /dev/null + sudo chmod +x /usr/local/bin/gcloud + + - name: Test RPM GA — signed and routed to redpanda-yum + env: + AWS_DEFAULT_REGION: us-east-1 + run: | + out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.rpm 1.0.0) + echo "$out" + echo "$out" | grep -q "artifacts yum upload redpanda-yum" + + - name: Test RPM RC — routed to redpanda-unstable-yum + env: + AWS_DEFAULT_REGION: us-east-1 + run: | + out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.rpm 1.0.0-rc1) + echo "$out" + echo "$out" | grep -q "artifacts yum upload redpanda-unstable-yum" + + - name: Test DEB — skips signing, routed to redpanda-apt + run: | + touch /tmp/test.deb + out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.deb 1.0.0) + echo "$out" + echo "$out" | grep -q "artifacts apt upload redpanda-apt" + + - name: Test missing AWS region — exits with error + env: + AWS_DEFAULT_REGION: "" + AWS_REGION: "" + run: | + out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.rpm 1.0.0 2>&1) && { + echo "ERROR: expected non-zero exit but got 0" >&2; exit 1 + } || echo "$out" | grep -q "AWS_DEFAULT_REGION" + test-push-to-cloudsmith: runs-on: ubuntu-latest env: diff --git a/resources/scripts/push_pkg_to_gcp_ar.sh b/resources/scripts/push_pkg_to_gcp_ar.sh index d6bf93ef45..7239d0b9be 100755 --- a/resources/scripts/push_pkg_to_gcp_ar.sh +++ b/resources/scripts/push_pkg_to_gcp_ar.sh @@ -2,7 +2,7 @@ # Push a rpm or deb to GCP Artifact Registry -set -ex +set -euo pipefail PKG_FILE=$1 PKG_VERSION=$2 @@ -42,8 +42,49 @@ else repo="redpanda-unstable" fi +sign_rpm() { + local pkg=$1 + + # Pre-flight: AWS_DEFAULT_REGION (or AWS_REGION) must be set; the aws CLI + # reads it from the environment, but we check early for a clear error message. + local region=${AWS_DEFAULT_REGION:-${AWS_REGION:-}} + if [[ -z "$region" ]]; then + echo "ERROR: AWS_DEFAULT_REGION or AWS_REGION must be set for RPM signing" + exit 1 + fi + + # Run in a subshell so the EXIT trap fires while temp vars are still in scope. + # A RETURN trap on the outer function would not fire on set -e exits. + ( + gnupghome=$(mktemp -d) + trap 'rm -rf "$gnupghome" "${tmpdb:-}" "${pubkey:-}"' EXIT + tmpdb=$(mktemp -d) + pubkey=$(mktemp) + + aws secretsmanager get-secret-value \ + --secret-id sdlc/prod/github/rpm_signing_key_private \ + --query SecretString \ + --output text | GNUPGHOME="$gnupghome" gpg --batch --import + + key_id=$(GNUPGHOME="$gnupghome" gpg --list-secret-keys --with-colons | awk -F: '/^sec/{print $5}' | head -1) + if [[ -z "$key_id" ]]; then + echo "ERROR: no secret key found after import — check the secret contents" + exit 1 + fi + + GNUPGHOME="$gnupghome" rpmsign --define "_gpg_name $key_id" --resign "$pkg" + + # Verify against our key using a temp RPM database — rpm --checksig checks + # the RPM keyring (not GNUPGHOME), so we must import the key into a temp db. + GNUPGHOME="$gnupghome" gpg --armor --export "$key_id" >"$pubkey" + rpm --dbpath "$tmpdb" --import "$pubkey" + rpm --dbpath "$tmpdb" --checksig "$pkg" + ) +} + if [[ "$PKG_TYPE" == "deb" ]]; then gcloud artifacts apt upload "${repo}-apt" --location=us-central1 --source="$PKG_FILE" --project=production-devprod else + sign_rpm "$PKG_FILE" gcloud artifacts yum upload "${repo}-yum" --location=us-central1 --source="$PKG_FILE" --project=production-devprod fi