Skip to content

Commit c9f3ce7

Browse files
author
Conor Schaefer
committed
Ensures source tarballs are reproducible
When building tarballs dynamically, let's take the time to ensure that they're fully reproducible. We still run 'python setup.py sdist', but since that tool doesn't (yet) support SOURCE_DATE_EPOCH, we'll manually repack the archive with native tar & gzip, forcing predictable timestamps from the git info, resulting in a deterministic build.
1 parent 2a03bcc commit c9f3ce7

File tree

1 file changed

+34
-3
lines changed

1 file changed

+34
-3
lines changed

scripts/build-debianpackage

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ if [[ -z "${PKG_NAME:-}" ]]; then
3737
fi
3838

3939

40+
# Look up most recent release from GitHub repo
4041
function find_latest_version() {
4142
repo_url="https://github.com/freedomofpress/${PKG_NAME}/releases"
4243
curl -s "$repo_url" \
@@ -58,15 +59,45 @@ fi
5859
# Copy over the debian directory (including new changelog) from repo
5960
cp -r "$CUR_DIR/$PKG_NAME/" "$TOP_BUILDDIR/"
6061

62+
# Ensures that a given git tag is signed with the prod release key
63+
# If "rc" is in the tag name, this will fail.
64+
function verify_git_tag() {
65+
local d
66+
local t
67+
d="$1"
68+
t="$2"
69+
prod_fingerprint="22245C81E3BAEB4138B36061310F561200F4AD77"
70+
git -C "$build_dir" tag --verify "$PKG_VERSION" 2>&1 \
71+
| grep -q -F "using RSA key $prod_fingerprint"
72+
}
73+
74+
# Dynamically generate a tarball, from the Python source code,
75+
# that is byte-for-byte reproducible. Use timestamps from the git tag.
6176
function build_source_tarball() {
6277
repo_url="https://github.com/freedomofpress/${PKG_NAME}"
6378
build_dir="/tmp/${PKG_NAME}"
6479
rm -rf "$build_dir"
6580
git clone "$repo_url" "$build_dir"
66-
git -C "$build_dir" tag --verify "$PKG_VERSION" 1>&2
67-
git -C "$build_dir" checkout "$PKG_VERSION" 1>&2
81+
82+
# Verify tag, using only the prod key
83+
verify_git_tag "$build_dir" "$PKG_VERSION" || exit 1
84+
85+
# Tag is verified, proceed with checkout
86+
git -C "$build_dir" checkout "$PKG_VERSION" 1>&2 || exit 1
6887
(cd "$build_dir" && python setup.py sdist 1>&2)
69-
find "${build_dir}/dist/" | grep -P '\.tar.gz$' | head -n1
88+
89+
# Initial tarball will contain timestamps from NOW, let's repack
90+
# with timestamps from the signed tag.
91+
raw_tarball="$(find "${build_dir}/dist/" | grep -P '\.tar.gz$' | head -n1)"
92+
tag_time="$(git -C "$build_dir" log --format="%ai" --no-patch -n1 "$PKG_VERSION")"
93+
(cd "$build_dir" && tar -xzf "dist/$(basename $raw_tarball)" 1>&2)
94+
tarball_basename="$(basename "$raw_tarball")"
95+
# Repack with tar only, so env vars are respected
96+
(cd "$build_dir" && tar -cf "${tarball_basename%.gz}" --mode=go=rX,u+rw,a-s --mtime="$tag_time" --sort=name --owner=root:0 --group=root:0 "${tarball_basename%.tar.gz}" 1>&2)
97+
# Then gzip it separately, so we can pass args
98+
(cd "$build_dir" && gzip --no-name "${tarball_basename%.gz}" 1>&2)
99+
(cd "$build_dir" && mv "$tarball_basename" dist/ 1>&2)
100+
echo "$raw_tarball"
70101
}
71102

72103
# If the package is contained in the list, it should be a python package. In

0 commit comments

Comments
 (0)