Skip to content

Commit 8e9616a

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 c26cde7 commit 8e9616a

File tree

1 file changed

+38
-5
lines changed

1 file changed

+38
-5
lines changed

scripts/build-debianpackage

Lines changed: 38 additions & 5 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,46 @@ 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. Infers timestamp
76+
# from the changelog, same as for the deb package.
6177
function build_source_tarball() {
6278
repo_url="https://github.com/freedomofpress/${PKG_NAME}"
6379
build_dir="/tmp/${PKG_NAME}"
6480
rm -rf "$build_dir"
6581
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
68-
(cd "$build_dir" && python setup.py sdist 1>&2)
69-
find "${build_dir}/dist/" | grep -P '\.tar.gz$' | head -n1
82+
83+
# Verify tag, using only the prod key
84+
verify_git_tag "$build_dir" "$PKG_VERSION"
85+
86+
# Tag is verified, proceed with checkout
87+
git -C "$build_dir" checkout "$PKG_VERSION"
88+
(cd "$build_dir" && LC_ALL="C.UTF-8" python setup.py sdist)
89+
90+
# Initial tarball will contain timestamps from NOW, let's repack
91+
# with timestamps from the changelog, which is static.
92+
raw_tarball="$(find "${build_dir}/dist/" | grep -P '\.tar.gz$' | head -n1)"
93+
dch_time="$(date "+%Y-%m-%d %H:%M:%S %z" -d@$(dpkg-parsechangelog --file $PKG_NAME/debian/changelog-$PLATFORM -STimestamp)) "
94+
(cd "$build_dir" && tar -xzf "dist/$(basename $raw_tarball)")
95+
tarball_basename="$(basename "$raw_tarball")"
96+
# Repack with tar only, so env vars are respected
97+
(cd "$build_dir" && tar -cf "${tarball_basename%.gz}" --mode=go=rX,u+rw,a-s --mtime="$dch_time" --sort=name --owner=root:0 --group=root:0 "${tarball_basename%.tar.gz}" 1>&2)
98+
# Then gzip it separately, so we can pass args
99+
(cd "$build_dir" && gzip --no-name "${tarball_basename%.gz}")
100+
(cd "$build_dir" && mv "$tarball_basename" dist/)
101+
echo "$raw_tarball"
70102
}
71103

72104
# If the package is contained in the list, it should be a python package. In
@@ -77,7 +109,8 @@ if [[ "${PKG_NAME}" =~ ^(securedrop-client|securedrop-proxy|securedrop-export|se
77109
if [[ -z "${PKG_PATH:-}" ]]; then
78110
# Build from source
79111
echo "PKG_PATH not set, building from source (version $PKG_VERSION)..."
80-
candidate_pkg_path="$(build_source_tarball)"
112+
build_source_tarball
113+
candidate_pkg_path="$(find /tmp/$PKG_NAME/dist -type f -iname '*.tar.gz')"
81114
if [[ -f "$candidate_pkg_path" ]]; then
82115
PKG_PATH="$candidate_pkg_path"
83116
echo "Found tarball at $PKG_PATH, override with PKG_PATH..."

0 commit comments

Comments
 (0)