Skip to content

Commit 9551f2a

Browse files
committed
TUN-9776: Support signing Debian packages with two keys for rollover
* TUN-9776: Support signing Debian packages with two keys for rollover Debian Trixie doesn't support the SHA-1 algo for GPG keys. This commit leverages the ability of providing two keys in the reprepro configuration in order to have two signatures in InRelease and Release.gpg files. This allows users that have the old key to continue fetching the binaries with the old key while allowing us to provide a new key that can be used in Trixie. Unfortunately current versions of RPM (since 2002) don't support double signing, so we can't apply the same logic for RPM Closes TUN-9776
1 parent 71448c1 commit 9551f2a

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ gitlab-release:
246246
r2-linux-release:
247247
python3 ./release_pkgs.py
248248

249+
.PHONY: r2-next-linux-release
250+
# Publishes to a separate R2 repository during GPG key rollover, using dual-key signing.
251+
r2-next-linux-release:
252+
python3 ./release_pkgs.py --upload-repo-file
253+
249254
.PHONY: capnp
250255
capnp:
251256
which capnp # https://capnproto.org/install.html

cfsetup.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ bookworm: &bookworm
211211
- make github-release
212212
r2-linux-release:
213213
build_dir: *build_dir
214-
builddeps:
214+
builddeps: &r2-linux-release-deps
215215
- *pinned_go
216216
- build-essential
217217
- fakeroot
@@ -231,4 +231,13 @@ bookworm: &bookworm
231231
- pip install pynacl==1.4.0 pygithub==1.55 boto3==1.22.9 python-gnupg==0.4.9
232232
- make r2-linux-release
233233

234+
r2-next-linux-release:
235+
build_dir: *build_dir
236+
builddeps: *r2-linux-release-deps
237+
post-cache:
238+
- python3 -m venv env
239+
- . env/bin/activate
240+
- pip install pynacl==1.4.0 pygithub==1.55 boto3==1.22.9 python-gnupg==0.4.9
241+
- make r2-next-linux-release
242+
234243
trixie: *bookworm

release_pkgs.py

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,19 @@ def create_rpm_pkgs(self, artifacts_path, gpg_key_name):
133133
"""
134134

135135
def create_repo_file(self, file_path, binary_name, baseurl, gpgkey_url):
136-
with open(os.path.join(file_path, binary_name + '.repo'), "w+") as repo_file:
136+
repo_file = os.path.join(file_path, binary_name + '.repo')
137+
with open(repo_file, "w+") as repo_file:
137138
repo_file.write(f"[{binary_name}-stable]")
138139
repo_file.write(f"{binary_name}-stable")
139140
repo_file.write(f"baseurl={baseurl}/rpm")
140141
repo_file.write("enabled=1")
141142
repo_file.write("type=rpm")
142143
repo_file.write("gpgcheck=1")
143144
repo_file.write(f"gpgkey={gpgkey_url}")
145+
return repo_file
144146

145-
def _sign_rpms(self, file_path):
147+
148+
def _sign_rpms(self, file_path, gpg_key_name):
146149
p = Popen(["rpm", "--define", f"_gpg_name {gpg_key_name}", "--addsign", file_path], stdout=PIPE, stderr=PIPE)
147150
out, err = p.communicate()
148151
if p.returncode != 0:
@@ -176,7 +179,7 @@ def _setup_rpm_pkg_directories(self, artifacts_path, gpg_key_name, archs=["aarch
176179
old_path = os.path.join(root, file)
177180
new_path = os.path.join(new_dir, file)
178181
shutil.copyfile(old_path, new_path)
179-
self._sign_rpms(new_path)
182+
self._sign_rpms(new_path, gpg_key_name)
180183

181184
"""
182185
imports gpg keys into the system so reprepro and createrepo can use it to sign packages.
@@ -192,6 +195,18 @@ def import_gpg_keys(self, private_key, public_key):
192195
data = gpg.list_keys(secret=True)
193196
return (data[0]["fingerprint"], data[0]["uids"][0])
194197

198+
def import_multiple_gpg_keys(self, primary_private_key, primary_public_key, secondary_private_key=None, secondary_public_key=None):
199+
"""
200+
Import one or two GPG keypairs. Returns a list of (fingerprint, uid) with the primary first.
201+
"""
202+
results = []
203+
if primary_private_key and primary_public_key:
204+
results.append(self.import_gpg_keys(primary_private_key, primary_public_key))
205+
if secondary_private_key and secondary_public_key:
206+
# Ensure secondary is imported and appended
207+
results.append(self.import_gpg_keys(secondary_private_key, secondary_public_key))
208+
return results
209+
195210
"""
196211
basically rpm --import <key_file>
197212
This enables us to sign rpms.
@@ -247,11 +262,13 @@ def upload_from_directories(pkg_uploader, directory, release, binary):
247262
"""
248263

249264

250-
def create_deb_packaging(pkg_creator, pkg_uploader, releases, gpg_key_id, binary_name, archs, package_component,
265+
def create_deb_packaging(pkg_creator, pkg_uploader, releases, primary_gpg_key_id, secondary_gpg_key_id, binary_name, archs, package_component,
251266
release_version):
252267
# set configuration for package creation.
253268
print(f"initialising configuration for {binary_name} , {archs}")
254269
Path("./conf").mkdir(parents=True, exist_ok=True)
270+
# If in rollover mode (secondary provided), tell reprepro to sign with both keys.
271+
sign_with_ids = primary_gpg_key_id if not secondary_gpg_key_id else f"{primary_gpg_key_id} {secondary_gpg_key_id}"
255272
pkg_creator.create_distribution_conf(
256273
"./conf/distributions",
257274
binary_name,
@@ -260,7 +277,7 @@ def create_deb_packaging(pkg_creator, pkg_uploader, releases, gpg_key_id, binary
260277
archs,
261278
package_component,
262279
f"apt repository for {binary_name}",
263-
gpg_key_id)
280+
sign_with_ids)
264281

265282
# create deb pkgs
266283
for release in releases:
@@ -287,15 +304,19 @@ def create_rpm_packaging(
287304
gpg_key_name,
288305
base_url,
289306
gpg_key_url,
307+
upload_repo_file=False,
290308
):
291309
print(f"creating rpm pkgs...")
292310
pkg_creator.create_rpm_pkgs(artifacts_path, gpg_key_name)
293-
pkg_creator.create_repo_file(artifacts_path, binary_name, base_url, gpg_key_url)
311+
repo_file = pkg_creator.create_repo_file(artifacts_path, binary_name, base_url, gpg_key_url)
312+
313+
print("Uploading repo file")
314+
pkg_uploader.upload_pkg_to_r2(repo_file, binary_name + "repo")
294315

295316
print("uploading latest to r2...")
296317
upload_from_directories(pkg_uploader, "rpm", None, binary_name)
297318

298-
if release_version:
319+
if upload_repo_file:
299320
print(f"uploading versioned release {release_version} to r2...")
300321
upload_from_directories(pkg_uploader, "rpm", release_version, binary_name)
301322

@@ -336,11 +357,23 @@ def parse_args():
336357
signing packages"
337358
)
338359

360+
# Optional secondary keypair for key rollover
361+
parser.add_argument(
362+
"--gpg-private-key-2", default=os.environ.get("LINUX_SIGNING_PRIVATE_KEY_2"), help="Secondary GPG private key for rollover"
363+
)
364+
parser.add_argument(
365+
"--gpg-public-key-2", default=os.environ.get("LINUX_SIGNING_PUBLIC_KEY_2"), help="Secondary GPG public key for rollover"
366+
)
367+
339368
parser.add_argument(
340369
"--gpg-public-key-url", default=os.environ.get("GPG_PUBLIC_KEY_URL"), help="GPG public key url that\
341370
downloaders can use to verify signing"
342371
)
343372

373+
parser.add_argument(
374+
"--gpg-public-key-url-2", default=os.environ.get("GPG_PUBLIC_KEY_URL_2"), help="Secondary GPG public key url for rollover"
375+
)
376+
344377
parser.add_argument(
345378
"--pkg-upload-url", default=os.environ.get("PKG_URL"), help="URL to be used by downloaders"
346379
)
@@ -355,6 +388,10 @@ def parse_args():
355388
it is the caller's responsiblity to ensure that these debs are already present in a directory. This script\
356389
will not build binaries or create their debs."
357390
)
391+
392+
parser.add_argument(
393+
"--upload-repo-file", action='store_true', help="Upload RPM repo file to R2"
394+
)
358395
args = parser.parse_args()
359396

360397
return args
@@ -368,21 +405,45 @@ def parse_args():
368405
exit(1)
369406

370407
pkg_creator = PkgCreator()
371-
(gpg_key_id, gpg_key_name) = pkg_creator.import_gpg_keys(args.gpg_private_key, args.gpg_public_key)
408+
# Import one or two keypairs; primary first
409+
key_results = pkg_creator.import_multiple_gpg_keys(
410+
args.gpg_private_key,
411+
args.gpg_public_key,
412+
args.gpg_private_key_2,
413+
args.gpg_public_key_2,
414+
)
415+
if not key_results or len(key_results) == 0:
416+
raise SystemExit("No GPG keys were provided for signing")
417+
primary_gpg_key_id, primary_gpg_key_name = key_results[0]
418+
secondary_gpg_key_id = None
419+
secondary_gpg_key_name = None
420+
if len(key_results) > 1:
421+
secondary_gpg_key_id, secondary_gpg_key_name = key_results[1]
422+
# Import RPM public keys (one or two)
372423
pkg_creator.import_rpm_key(args.gpg_public_key)
373424

374425
pkg_uploader = PkgUploader(args.account, args.bucket, args.id, args.secret)
375-
print(f"signing with gpg_key: {gpg_key_id}")
376-
create_deb_packaging(pkg_creator, pkg_uploader, args.deb_based_releases, gpg_key_id, args.binary, args.archs,
377-
"main", args.release_tag)
426+
print(f"signing with primary gpg_key: {primary_gpg_key_id} and secondary gpg_key: {secondary_gpg_key_id}")
427+
create_deb_packaging(
428+
pkg_creator,
429+
pkg_uploader,
430+
args.deb_based_releases,
431+
primary_gpg_key_id,
432+
secondary_gpg_key_id,
433+
args.binary,
434+
args.archs,
435+
"main",
436+
args.release_tag,
437+
)
378438

379439
create_rpm_packaging(
380440
pkg_creator,
381441
pkg_uploader,
382442
"./built_artifacts",
383443
args.release_tag,
384444
args.binary,
385-
gpg_key_name,
386-
args.gpg_public_key_url,
445+
primary_gpg_key_name,
387446
args.pkg_upload_url,
447+
args.gpg_public_key_url,
448+
args.upload_repo_file,
388449
)

0 commit comments

Comments
 (0)