Skip to content

Commit e9d288b

Browse files
Przemek Zglinickiclaude
andcommitted
ci: sign RPM packages before pushing to GCP Artifact Registry
- Add sign_rpm() to push_pkg_to_gcp_ar.sh: fetches the Redpanda GPG private key from AWS Secrets Manager (sdlc/prod/github/rpm_signing_key_private), signs each RPM with rpmsign --resign, verifies the signature using a temp RPM database, then uploads to GCP AR - Fix signature verification to use rpm --dbpath <tmpdb> so the GPG check actually validates the signature (plain rpm --checksig ignores GNUPGHOME and checks the system keyring, returning exit 0 even for NOKEY) - Add test-push-to-gcp-ar CI job: generates a throwaway GPG key, builds a minimal RPM, mocks aws and gcloud, and asserts signing + routing for GA (redpanda-yum), RC (redpanda-unstable-yum), DEB (redpanda-apt), and missing-region error cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9ae58ec commit e9d288b

3 files changed

Lines changed: 175 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ jobs:
7474
tar -C "$RUNNER_TEMP/microsoft" -xf "$RUNNER_TEMP/msgo.tgz"
7575
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
7676
77+
- name: Install rpm (provides rpmsign for RPM package signing)
78+
run: sudo apt-get install -y rpm
79+
7780
- name: Release Notes
7881
run: ./resources/scripts/release_notes.sh > ./release_notes.md
7982

.github/workflows/test.yml

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,143 @@ jobs:
6262
skip-cache: true
6363
skip-save-cache: true
6464

65+
# Runs integration tests for any internal/impl/* packages changed in this PR.
66+
#
67+
# Trigger: add the 'run-integration-tests' label to the PR.
68+
# The label is checked at job start — if added after the workflow triggered,
69+
# re-run the workflow (or push a new commit) to pick it up.
70+
#
71+
# Package detection: diffs HEAD against the PR base branch and extracts
72+
# unique affected internal/impl/* package directories. Tests run sequentially.
73+
integration-test:
74+
if: |
75+
github.event_name == 'pull_request' &&
76+
contains(github.event.pull_request.labels.*.name, 'run-integration-tests')
77+
environment: integration-tests
78+
runs-on: ubuntu-latest-32
79+
env:
80+
CGO_ENABLED: 0
81+
steps:
82+
- name: Checkout code
83+
uses: actions/checkout@v6
84+
with:
85+
fetch-depth: 0
86+
87+
- name: Install Go
88+
uses: actions/setup-go@v6
89+
with:
90+
go-version-file: "go.mod"
91+
92+
- name: Install Task
93+
uses: ./.github/actions/setup-task
94+
95+
- name: Pull Latest Redpanda Image
96+
run: task docker:pull-redpanda
97+
98+
- name: Run Integration Tests
99+
run: |
100+
mapfile -t pkgs < <(
101+
git diff --name-only "$(git merge-base HEAD origin/${{ github.base_ref }})"...HEAD \
102+
| { grep '^internal/impl/' || true; } \
103+
| sed 's|/[^/]*$||' \
104+
| sort -u
105+
)
106+
failed=0
107+
for pkg in "${pkgs[@]}"; do
108+
task test:integration-package PKG="./$pkg/..." || failed=1
109+
done
110+
exit $failed
111+
# 30m total: normally a single package is changed per PR, so 15m per-package
112+
# timeout plus overhead fits comfortably. Keeps CI fast-failing instead of
113+
# hanging for hours when Docker networking is degraded.
114+
timeout-minutes: 30
115+
116+
test-push-to-gcp-ar:
117+
if: ${{ github.repository == 'redpanda-data/connect' || github.event_name != 'schedule' }}
118+
runs-on: ubuntu-latest
119+
steps:
120+
121+
- name: Checkout code
122+
uses: actions/checkout@v6
123+
124+
- name: Install rpm tools
125+
run: sudo apt-get install -y rpm rpm-build gnupg2
126+
127+
- name: Set up test GPG key and test RPM
128+
run: |
129+
# Generate a throwaway GPG key — no passphrase, expires never
130+
printf '%s\n' \
131+
'%no-protection' \
132+
'Key-Type: RSA' \
133+
'Key-Length: 2048' \
134+
'Name-Real: Test Signing Key' \
135+
'Name-Email: test@redpanda.com' \
136+
'Expire-Date: 0' \
137+
| gpg --batch --gen-key
138+
# Export private key so the aws mock can serve it
139+
gpg --armor --export-secret-keys > /tmp/test-signing-key.asc
140+
141+
# Build a minimal empty RPM
142+
mkdir -p /tmp/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
143+
printf '%s\n' \
144+
'Name: test-pkg' \
145+
'Version: 1.0.0' \
146+
'Release: 1' \
147+
'Summary: Test package for RPM signing CI' \
148+
'License: MIT' \
149+
'BuildArch: noarch' \
150+
'%description' \
151+
'Minimal test RPM.' \
152+
'%install' \
153+
'mkdir -p %{buildroot}' \
154+
'%files' \
155+
> /tmp/rpmbuild/SPECS/test-pkg.spec
156+
rpmbuild --define "_topdir /tmp/rpmbuild" -bb /tmp/rpmbuild/SPECS/test-pkg.spec
157+
cp /tmp/rpmbuild/RPMS/noarch/test-pkg-1.0.0-1.noarch.rpm /tmp/test.rpm
158+
159+
- name: Mock aws and gcloud CLIs
160+
run: |
161+
# aws: return the test private key for any secretsmanager get-secret-value call
162+
printf '#!/bin/bash\ncat /tmp/test-signing-key.asc\n' \
163+
| sudo tee /usr/local/bin/aws > /dev/null
164+
sudo chmod +x /usr/local/bin/aws
165+
# gcloud: echo arguments so we can assert routing
166+
printf '#!/bin/bash\necho "gcloud $*"\n' \
167+
| sudo tee /usr/local/bin/gcloud > /dev/null
168+
sudo chmod +x /usr/local/bin/gcloud
169+
170+
- name: Test RPM GA — signed and routed to redpanda-yum
171+
env:
172+
AWS_DEFAULT_REGION: us-east-1
173+
run: |
174+
out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.rpm 1.0.0)
175+
echo "$out"
176+
echo "$out" | grep -q "artifacts yum upload redpanda-yum"
177+
178+
- name: Test RPM RC — routed to redpanda-unstable-yum
179+
env:
180+
AWS_DEFAULT_REGION: us-east-1
181+
run: |
182+
out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.rpm 1.0.0-rc1)
183+
echo "$out"
184+
echo "$out" | grep -q "artifacts yum upload redpanda-unstable-yum"
185+
186+
- name: Test DEB — skips signing, routed to redpanda-apt
187+
run: |
188+
touch /tmp/test.deb
189+
out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.deb 1.0.0)
190+
echo "$out"
191+
echo "$out" | grep -q "artifacts apt upload redpanda-apt"
192+
193+
- name: Test missing AWS region — exits with error
194+
env:
195+
AWS_DEFAULT_REGION: ""
196+
AWS_REGION: ""
197+
run: |
198+
out=$(./resources/scripts/push_pkg_to_gcp_ar.sh /tmp/test.rpm 1.0.0 2>&1 || true)
199+
echo "$out"
200+
echo "$out" | grep -q "AWS_DEFAULT_REGION"
201+
65202
test-push-to-cloudsmith:
66203
runs-on: ubuntu-latest
67204
env:

resources/scripts/push_pkg_to_gcp_ar.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,43 @@ else
4242
repo="redpanda-unstable"
4343
fi
4444

45+
sign_rpm() {
46+
local pkg=$1
47+
48+
local region=${AWS_DEFAULT_REGION:-$AWS_REGION}
49+
if [[ -z "$region" ]]; then
50+
echo "ERROR: AWS_DEFAULT_REGION or AWS_REGION must be set for RPM signing"
51+
exit 1
52+
fi
53+
54+
local gnupghome
55+
gnupghome=$(mktemp -d)
56+
trap 'rm -rf "$gnupghome"' RETURN
57+
58+
aws secretsmanager get-secret-value \
59+
--secret-id sdlc/prod/github/rpm_signing_key_private \
60+
--query SecretString \
61+
--output text | GNUPGHOME="$gnupghome" gpg --batch --import
62+
63+
local key_id
64+
key_id=$(GNUPGHOME="$gnupghome" gpg --list-secret-keys --with-colons | awk -F: '/^sec/{print $5}' | head -1)
65+
66+
GNUPGHOME="$gnupghome" rpmsign --define "_gpg_name $key_id" --resign "$pkg"
67+
68+
# Verify against our key using a temp RPM database — rpm --checksig checks
69+
# the RPM keyring (not GNUPGHOME), so we must import the key into a temp db.
70+
local tmpdb pubkey
71+
tmpdb=$(mktemp -d)
72+
pubkey=$(mktemp)
73+
trap 'rm -rf "$gnupghome" "$tmpdb" "$pubkey"' RETURN
74+
GNUPGHOME="$gnupghome" gpg --armor --export "$key_id" >"$pubkey"
75+
rpm --dbpath "$tmpdb" --import "$pubkey"
76+
rpm --dbpath "$tmpdb" --checksig "$pkg"
77+
}
78+
4579
if [[ "$PKG_TYPE" == "deb" ]]; then
4680
gcloud artifacts apt upload "${repo}-apt" --location=us-central1 --source="$PKG_FILE" --project=production-devprod
4781
else
82+
sign_rpm "$PKG_FILE"
4883
gcloud artifacts yum upload "${repo}-yum" --location=us-central1 --source="$PKG_FILE" --project=production-devprod
4984
fi

0 commit comments

Comments
 (0)