diff --git a/build-scripts/package-msi b/build-scripts/package-msi index 4f009c5ab..57bda1aa5 100755 --- a/build-scripts/package-msi +++ b/build-scripts/package-msi @@ -13,6 +13,36 @@ # MSI packaging uses wixl (GNOME msitools), which builds Windows MSIs natively # on Linux - no Windows and no Wine. +# Derive a deterministic GUID from a label (ENT-13792) +deterministic_guid() { + printf '{%s}\n' "$(uuidgen --sha1 \ + --namespace B883FBCC-6F05-4AFA-98FA-CAF09BF464EA \ + --name "$1" | tr '[:lower:]' '[:upper:]')" +} + +# Make the Summary Information reproducible: wixl writes a random package code +# (property 9) and the wall-clock build time (12/13 = Created/Last saved). +# Rewrite them to a version-derived package code and SOURCE_DATE_EPOCH. +# (ENT-13792) +normalize_msi_summary() { + msi="$1" + revision="$2" + if [ -z "$SOURCE_DATE_EPOCH" ]; then + log_debug "SOURCE_DATE_EPOCH unset; skipping MSI summary normalization" + return 0 + fi + pkgcode=$(deterministic_guid "package:$revision") + datestr=$(TZ=UTC date -u -d "@$SOURCE_DATE_EPOCH" +"%Y/%m/%d %H:%M:%S") + msiinfo export "$msi" _SummaryInformation | tr -d '\r' | + awk -v u="$pkgcode" -v d="$datestr" -F '\t' 'BEGIN { OFS = "\t" } + $1 == "9" { $2 = u } + $1 == "12" { $2 = d } + $1 == "13" { $2 = d } + { print }' | sed 's/$/\r/' >_summary.idt + msibuild "$msi" -i _summary.idt + rm -f _summary.idt +} + # Determine build directory name based on Jenkins job or version/arch log_debug "Determining build directory name (JOB_NAME=$JOB_NAME, VERSION=$VERSION, ARCH=$ARCH)" if [ -z "$JOB_NAME" ]; then @@ -85,6 +115,13 @@ pre() { if [ "$ARCH" = "x86" ]; then sed -i '/lib\(crypto\|ssl\)/s/[_-]x64//g' "$P"/cfengine-nova.wxs fi + + # Reproducible builds (ENT-13792): pin every packaged file's mtime to + # SOURCE_DATE_EPOCH so the CAB stores a fixed timestamp per file instead + # of the time each file was created. + if [ -n "$SOURCE_DATE_EPOCH" ]; then + find "$P" -exec touch -h -d "@$SOURCE_DATE_EPOCH" {} + + fi } # wixl_build() - Build the MSI from the WiX source using wixl (msitools). @@ -108,7 +145,13 @@ wixl_build() { log_debug "Running wixl with REVISION=$REVISION, ARCH=$ARCH" wixl -a "$wixl_arch" \ -D CfSourceDir=. -D CfVersion="$REVISION" -D CfArch="$ARCH" \ + -D CfProductCode="$(deterministic_guid "product:$REVISION")" \ -o cfengine-nova.msi cfengine-nova.wxs + + # wixl still bakes a random package code and the wall-clock build time into + # the Summary Information; rewrite them for a byte-reproducible MSI. + # (ENT-13792) + normalize_msi_summary cfengine-nova.msi "$REVISION" } # package() - Main packaging function that creates the MSI installer diff --git a/ci/cfengine-build-host-setup.cf b/ci/cfengine-build-host-setup.cf index 53c880a41..0770bf875 100644 --- a/ci/cfengine-build-host-setup.cf +++ b/ci/cfengine-build-host-setup.cf @@ -91,6 +91,10 @@ bundle agent cfengine_build_host_setup "wixl"; "msitools"; + # build-scripts/package-msi derives deterministic MSI GUIDs with uuidgen + # (uuid-runtime) for reproducible builds. See ENT-13792. + "uuid-runtime"; + "binfmt-support" comment => "update-binfmts command needed for build-scripts/package-msi script"; diff --git a/ci/fix-buildhost.sh b/ci/fix-buildhost.sh index a94f8b77d..beea92f0e 100755 --- a/ci/fix-buildhost.sh +++ b/ci/fix-buildhost.sh @@ -75,12 +75,13 @@ if [ -f /etc/os-release ]; then fi # MinGW hosts build the MSI with wixl (build-scripts/package-msi) and inspect it -# with msiinfo (msitools). Installed by the build-host-setup policy at image -# time; install here too so not-yet-reimaged mingw hosts get them without a -# reimage. See ENT-13868. +# with msiinfo (msitools). uuidgen (uuid-runtime) derives deterministic MSI +# GUIDs for reproducible builds (ENT-13792). Installed by the build-host-setup +# policy at image time; install here too so not-yet-reimaged mingw hosts get +# them without a reimage. See ENT-13868. if [ -f /etc/cfengine-mingw-build-host.flag ]; then - if ! command -v wixl >/dev/null 2>&1 || ! command -v msiinfo >/dev/null 2>&1; then + if ! command -v wixl >/dev/null 2>&1 || ! command -v msiinfo >/dev/null 2>&1 || ! command -v uuidgen >/dev/null 2>&1; then sudo apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y wixl msitools + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y wixl msitools uuid-runtime fi fi diff --git a/container/Dockerfile.mingw b/container/Dockerfile.mingw index f18022ae5..a8b9faec9 100644 --- a/container/Dockerfile.mingw +++ b/container/Dockerfile.mingw @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y \ autoconf automake binutils bison build-essential curl debhelper dpkg-dev \ expat fakeroot flex gdb git libexpat1-dev libmodule-load-conditional-perl \ libpam0g-dev libtool libncurses6 libncurses-dev pkg-config psmisc \ - python3-pip rsync sudo systemd-coredump unzip wget \ + python3-pip rsync sudo systemd-coredump unzip uuid-runtime wget \ && rm -rf /var/lib/apt/lists/* # MinGW-w64 cross toolchain. diff --git a/packaging/cfengine-nova/cfengine-nova.wxs b/packaging/cfengine-nova/cfengine-nova.wxs index 4ea11c506..19129f7b5 100644 --- a/packaging/cfengine-nova/cfengine-nova.wxs +++ b/packaging/cfengine-nova/cfengine-nova.wxs @@ -26,7 +26,9 @@ - + - NOT Installed + + +