Skip to content

Commit 83e4ce6

Browse files
refactor: streamline ephemeral GPG key generation and signing process (#49)
* refactor: streamline ephemeral GPG key generation and signing process Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> * refactor: update GPG key handling for RPM signing process Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> * refactor: enhance RPM signing configuration and verification process Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> * refactor: improve RPM signing and verification process with enhanced GPG configurations Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> * refactor: enhance RPM signing workflow with improved subkey management and documentation Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> * refactor: streamline RPM signing process by improving key management and removing subkey expiration check Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> * ci: eliminate version variable redundancy Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: add gpg debugging on stderr for signing Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: switch to gpg-signing-manager Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: remove GPG_SIGNING_KEYID in favor of GPG_SIGNING_FINGERPRINT Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: install rpmdevtools Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: import signing key so signature checking passes Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: set up gh cli Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: export repo key so it is found for artifact upload Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> * ci: export repo pubkey from privkey Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> --------- Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov> Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> Co-authored-by: Devon Bautista <17506592+synackd@users.noreply.github.com>
1 parent e44203b commit 83e4ce6

2 files changed

Lines changed: 141 additions & 75 deletions

File tree

.github/workflows/build-rpm.yaml

Lines changed: 134 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -26,38 +26,42 @@ jobs:
2626

2727
steps:
2828
- name: Install dependencies
29+
shell: bash
2930
run: |
30-
dnf install -y \
31-
rpm-build rpm-sign rpmlint gcc make redhat-rpm-config \
32-
gh gpg gpg2 gnupg2 expect rpmdevtools createrepo
31+
if command -v sudo >/dev/null 2>&1; then
32+
SUDO=sudo
33+
else
34+
SUDO=
35+
fi
36+
37+
$SUDO dnf install -y rpmdevtools gh
3338
3439
- name: Checkout repository
3540
uses: actions/checkout@v4
3641

37-
- name: Import GPG key
38-
run: |
39-
echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --import --batch --yes
40-
env:
41-
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
42-
43-
- name: Show signing subkey expiration
44-
shell: bash
45-
run: |
46-
gpg --list-secret-keys --with-colons \
47-
| awk -F: '
48-
$1=="ssb" && $12 ~ /s/ {
49-
keyid = $5
50-
expires = $7
51-
if (expires == "" || expires == "0") {
52-
edate = "never"
53-
} else {
54-
cmd = "date -u -d @" expires " +\"%Y-%m-%d %H:%M:%S UTC\""
55-
cmd | getline edate
56-
close(cmd)
57-
}
58-
printf "signing subkey %s expires: %s\n", keyid, edate
59-
}
60-
'
42+
- name: Check repo key expiration
43+
uses: OpenCHAMI/gpg-signing-manager/actions/check-key-expiration@main
44+
with:
45+
key-armored-b64: ${{ secrets.GPG_REPO_KEY_B64 }}
46+
warn-days: '30'
47+
48+
- name: Generate ephemeral release key
49+
id: ephemeral
50+
uses: OpenCHAMI/gpg-signing-manager/actions/gpg-ephemeral-key@main
51+
with:
52+
cert-key-armored-b64: ${{ secrets.GPG_REPO_CERT_KEY_B64 }}
53+
name: '${{ github.repository }} Release'
54+
comment: 'Ephemeral key for ${{ github.ref_name }}'
55+
email: 'release@packages.openchami.org'
56+
expire: '1d'
57+
58+
- name: Setup RPM signing (ephemeral key)
59+
id: rpm_signing
60+
uses: OpenCHAMI/gpg-signing-manager/actions/setup-rpm-signing@main
61+
with:
62+
repo-key-armored-b64: ${{ secrets.GPG_REPO_KEY_B64 }}
63+
ephemeral-fingerprint: ${{ steps.ephemeral.outputs.ephemeral-fingerprint }}
64+
public-key-output: public_gpg_key.asc
6165

6266
- name: Get version
6367
id: get_version
@@ -69,7 +73,7 @@ jobs:
6973
else
7074
VERSION=0.0.0
7175
fi
72-
echo "VERSION=${VERSION}" >> $GITHUB_ENV
76+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
7377
echo "Version is ${VERSION}"
7478
7579
- name: Setup RPM build environment
@@ -80,114 +84,169 @@ jobs:
8084
8185
- name: Create source tarball
8286
run: |
83-
mkdir -p ~/rpmbuild/SOURCES/openchami-${{ env.VERSION }}
84-
cp -r ./* ~/rpmbuild/SOURCES/openchami-${{ env.VERSION }}/
85-
tar -czf ~/rpmbuild/SOURCES/openchami-${{ env.VERSION }}.tar.gz \
86-
-C ~/rpmbuild/SOURCES openchami-${{ env.VERSION }} \
87-
--transform "s|openchami-${{ env.VERSION }}-${{ env.COMMIT_SHA }}|openchami-${{ env.VERSION }}|"
87+
mkdir -p ~/rpmbuild/SOURCES/openchami-${{ steps.get_version.outputs.version }}
88+
cp -r ./* ~/rpmbuild/SOURCES/openchami-${{ steps.get_version.outputs.version }}/
89+
tar -czf ~/rpmbuild/SOURCES/openchami-${{ steps.get_version.outputs.version }}.tar.gz \
90+
-C ~/rpmbuild/SOURCES openchami-${{ steps.get_version.outputs.version }}
8891
8992
- name: Sign source tarball
93+
env:
94+
GPG_SIGNING_FINGERPRINT: ${{ steps.ephemeral.outputs.ephemeral-fingerprint }}
9095
run: |
91-
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 --pinentry-mode loopback \
96+
set -euo pipefail
97+
unset GPG_TTY
98+
echo "Signing source tarball with key: ${GPG_SIGNING_FINGERPRINT}"
99+
gpg --batch --list-secret-keys --keyid-format LONG "${GPG_SIGNING_FINGERPRINT}"
100+
gpg --batch --yes \
101+
--no-tty --pinentry-mode loopback \
102+
--status-fd 2 \
92103
--armor --detach-sign \
93-
--local-user admin@openchami.org \
94-
--output ~/rpmbuild/SOURCES/openchami-${{ env.VERSION }}.tar.gz.asc \
95-
~/rpmbuild/SOURCES/openchami-${{ env.VERSION }}.tar.gz
96-
env:
97-
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
104+
--local-user "${GPG_SIGNING_FINGERPRINT}" \
105+
--output ~/rpmbuild/SOURCES/openchami-${{ steps.get_version.outputs.version }}.tar.gz.asc \
106+
~/rpmbuild/SOURCES/openchami-${{ steps.get_version.outputs.version }}.tar.gz
98107
99108
- name: Build RPM package
100109
run: |
101110
rpmbuild -ba ~/rpmbuild/SPECS/*.spec \
102-
--define "version ${{ env.VERSION }}" \
111+
--define "version ${{ steps.get_version.outputs.version }}" \
103112
--define "rel 1"
104113
105114
- name: Sign RPM packages
115+
shell: bash
116+
env:
117+
GPG_SIGNING_FINGERPRINT: ${{ steps.ephemeral.outputs.ephemeral-fingerprint }}
106118
run: |
107-
for rpm in $(find ~/rpmbuild/RPMS/ -type f -name "*.rpm"); do
108-
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 --pinentry-mode loopback \
109-
--detach-sign --armor "$rpm"
110-
rpm --define "_gpg_name admin@openchami.org" --addsign "$rpm"
111-
done
119+
set -euo pipefail
120+
unset GPG_TTY
121+
echo "Using RPM signing key: ${GPG_SIGNING_FINGERPRINT}"
122+
gpg --batch --list-secret-keys --keyid-format LONG "${GPG_SIGNING_FINGERPRINT}"
123+
sed -i '/^%__gpg_sign_cmd/d' ~/.rpmmacros
124+
echo 'Configured ~/.rpmmacros:'
125+
cat ~/.rpmmacros
126+
127+
found=0
128+
while IFS= read -r -d '' rpm_file; do
129+
found=1
130+
echo "Signing RPM: $rpm_file"
131+
rpmsign --key-id "${GPG_SIGNING_FINGERPRINT}" --addsign "$rpm_file"
132+
done < <(find ~/rpmbuild/RPMS/ -type f -name "*.rpm" -print0)
133+
134+
if [ "$found" -eq 0 ]; then
135+
echo 'No RPM packages were produced to sign' >&2
136+
exit 1
137+
fi
138+
139+
- name: Verify RPM signatures
140+
shell: bash
112141
env:
113-
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
142+
GPG_SIGNING_FINGERPRINT: ${{ steps.ephemeral.outputs.ephemeral-fingerprint }}
143+
run: |
144+
rpm --import <(gpg --export --armor "${GPG_SIGNING_FINGERPRINT}")
145+
found=0
146+
while IFS= read -r -d '' rpm_file; do
147+
found=1
148+
output=$(rpm --checksig -v "$rpm_file" 2>&1)
149+
printf '%s\n' "$output"
150+
151+
if printf '%s\n' "$output" | grep -q 'SIGNATURES NOT OK'; then
152+
echo "RPM signature verification failed for $rpm_file" >&2
153+
exit 1
154+
fi
155+
156+
if ! printf '%s\n' "$output" | grep -Eiq 'Key ID|RSA/SHA|RSA/sha|EdDSA|pgp'; then
157+
echo "RPM appears unsigned: $rpm_file" >&2
158+
exit 1
159+
fi
160+
done < <(find ~/rpmbuild/RPMS/ -type f -name "*.rpm" -print0)
161+
162+
if [ "$found" -eq 0 ]; then
163+
echo 'No RPM packages were produced to verify' >&2
164+
exit 1
165+
fi
166+
167+
- name: Export ephemeral public key
168+
run: |
169+
printf '%s' '${{ steps.ephemeral.outputs.ephemeral-public-key }}' \
170+
| base64 -d > repo-ephemeral-public.asc
171+
172+
- name: Export repo public key
173+
run: |
174+
gpg --export --armor '${{ steps.ephemeral.outputs.repo-cert-keyid }}' > repo-public.asc
114175
115176
- name: Find RPM file
116-
if: env.VERSION != '0.0.0'
177+
if: steps.get_version.outputs.version != '0.0.0'
117178
id: find_rpm
118179
run: |
119180
rpm_file=$(ls ~/rpmbuild/RPMS/noarch/*.rpm)
120181
echo "rpm_file=${rpm_file}" >> $GITHUB_ENV
121-
echo "::set-output name=path::${rpm_file}"
182+
echo "path=${rpm_file}" >> "$GITHUB_OUTPUT"
122183
123184
- name: Compute RPM Checksum
124-
if: env.VERSION != '0.0.0'
185+
if: steps.get_version.outputs.version != '0.0.0'
125186
id: compute_checksum
126187
run: |
127188
rpm_file=$(ls ~/rpmbuild/RPMS/noarch/*.rpm)
128189
checksum=$(sha256sum "$rpm_file" | awk '{print $1}')
129190
echo "checksum=${checksum}" >> $GITHUB_ENV
130-
echo "::set-output name=checksum::${checksum}"
131-
132-
- name: Export Public GPG Key
133-
if: env.VERSION != '0.0.0'
134-
run: |
135-
gpg --armor --export admin@openchami.org > public_gpg_key.asc
136-
137-
- name: Get Public GPG Key Content
138-
if: env.VERSION != '0.0.0'
139-
id: get_pubkey
140-
run: |
141-
key=$(cat public_gpg_key.asc)
142-
escaped_key=$(echo "$key" | sed ':a;N;$!ba;s/\n/\\n/g')
143-
echo "::set-output name=pubkey::${escaped_key}"
191+
echo "checksum=${checksum}" >> "$GITHUB_OUTPUT"
144192
145193
- name: Genereate release notes
146-
if: env.VERSION != '0.0.0'
194+
if: steps.get_version.outputs.version != '0.0.0'
147195
id: gen_rel_notes
148196
env:
149197
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
150198
run: |
151199
{
152-
echo '# OpenCHAMI v${{ env.VERSION }}'
200+
echo '# OpenCHAMI v${{ steps.get_version.outputs.version }}'
153201
echo ''
154202
echo '**RPM SHA256 Checksum:**'
155203
echo '`${{ steps.compute_checksum.outputs.checksum }}`'
156204
echo ''
157-
gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" -F tag_name='v${{ env.VERSION }}' --jq .body
205+
gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" -F tag_name='v${{ steps.get_version.outputs.version }}' --jq .body
158206
} > CHANGELOG.md
159207
160208
- name: Create GitHub Release
161-
if: env.VERSION != '0.0.0'
209+
if: steps.get_version.outputs.version != '0.0.0'
162210
id: create_release
163211
uses: actions/create-release@v1
164212
env:
165213
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
166214
with:
167-
tag_name: v${{ env.VERSION }}
168-
release_name: v${{ env.VERSION }}
215+
tag_name: v${{ steps.get_version.outputs.version }}
216+
release_name: v${{ steps.get_version.outputs.version }}
169217
draft: false
170218
prerelease: false
171219
body_path: CHANGELOG.md
172220

173221
- name: Upload RPM to GitHub Release
174-
if: env.VERSION != '0.0.0'
222+
if: steps.get_version.outputs.version != '0.0.0'
175223
uses: actions/upload-release-asset@v1
176224
env:
177225
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178226
with:
179227
upload_url: ${{ steps.create_release.outputs.upload_url }}
180228
asset_path: ${{ steps.find_rpm.outputs.path }}
181-
asset_name: openchami-${{ env.VERSION }}.rpm
229+
asset_name: openchami-${{ steps.get_version.outputs.version }}.rpm
182230
asset_content_type: application/x-rpm
183231

184-
- name: Upload Public GPG Key to GitHub Release
185-
if: env.VERSION != '0.0.0'
232+
- name: Upload Repo GPG Key to GitHub Release
233+
if: steps.get_version.outputs.version != '0.0.0'
234+
uses: actions/upload-release-asset@v1
235+
env:
236+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
237+
with:
238+
upload_url: ${{ steps.create_release.outputs.upload_url }}
239+
asset_path: repo-public.asc
240+
asset_name: repo_public.asc
241+
asset_content_type: text/plain
242+
243+
- name: Upload Repo Ephemeral GPG Key to GitHub Release
244+
if: steps.get_version.outputs.version != '0.0.0'
186245
uses: actions/upload-release-asset@v1
187246
env:
188247
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
189248
with:
190249
upload_url: ${{ steps.create_release.outputs.upload_url }}
191-
asset_path: public_gpg_key.asc
192-
asset_name: public_gpg_key.asc
250+
asset_path: repo-ephemeral-public.asc
251+
asset_name: repo_ephemeral_public.asc
193252
asset_content_type: text/plain

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ Clean built RPMs in repo directory:
2727
make clean
2828
```
2929

30+
## Automated RPM Signing
31+
32+
The GitHub release workflow signs built RPMs with the repository signing subkey
33+
stored in the `GPG_SUBKEY_B64` repository secret. The workflow also exports the
34+
matching ASCII-armored public key as a release asset so downstream consumers can
35+
verify the published RPM signature.
36+
3037
## Current Release
3138

3239
OpenCHAMI is in development without an initial release. We expect a first supported release in Q1 2025. If you would like to follow the most current, stable configuration, each of the partners maintains a deployment recipe that will become a release candidate in our [Deployment Recipes](https://github.com/OpenCHAMI/deployment-recipes) Repository.

0 commit comments

Comments
 (0)