DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
The term "Reproducible Builds" refers to making sure the build process for various artifacts is so deterministic that building the same sources twice results in a bit-by-bit identical artifact. You can read more about it on https://reproducible-builds.org/.
One of the advantages of Reproducible Builds is that, when those two builds happen on independently-managed infrastructure, validating that both environments produce the same bit-by-bit artifact improves the confidence that no backdoor or other malware was injected into the artifact due to a compromise of the infrastructure.
Reproducible Builds for ASF releases
It is good practice for all artifacts released by the ASF to be reproducible. For projects that want to build and sign artifacts on CI, Reproducible Builds are required. This means:
- your builds must be deterministic enough that independent builds produce bit-by-bit identical artifacts
- you have documented how and when artifacts are actually independently rebuilt and verified in your release process
- you follow this process in practice.
Ecosystem-specific notes
See below for any ecosystem-specific notes that could be helpful for other projects to make their builds reproducible. If you have additional input to share, feel free to edit this wiki page. If you have questions or want to discuss approaches, you can use the security-discuss mailinglist or Slack channel.
Archive/artifact contents
Including information about the system or environment running the build as well as current-date/time information in Jars breaks reproducibility (1) (2). This includes attributes like:
- Current/build user
- Current/build timestamp
- Hostnames and IP addresses
- Absolute file system paths
- Full build tool versions (like Java's
java.versionorjava.vm.versionsystem properties) - CI build number
- ...
Java / Maven
If you have a project that is built with Apache Maven, refer to the Configuring for Reproducible Builds guide.
Java / Gradle
Gradle builds may require setting some options in the build to ensure reproducible artifacts. Using the file-system permissions can be fine, but consider that different users (and OS's) may have different umask settings/defaults.
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission
// This is applied to all Jar, Zip and Tar tasks.
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
// consistent directory permissions, ignoring system's umask
dirPermissions { unix("755") }
// consistent file permissions, ignoring system's umask, retaining the executable permission (either 644 or 755)
eachFile {
permissions {
val isExec = Files.getPosixFilePermissions(file.toPath()).contains(PosixFilePermission.OWNER_EXECUTE)
unix(if (isExec) "755" else "644")
}
}
}
Java / Gradle / Modifying the pom.xml content
To modify the generated pom.xml, Gradle offers a mechanism on the MavenPublication type via the withXml function.
Using Node.appendNode() can lead to non-reproducible builds, depending on whether your code can run before or after Gradle added the <dependencies> node.
Example code (Kotlin Script) to place a <parent> element at a deterministic position:
withXml {
val projectNode = asNode()
val parentNode = projectNode.appendNode("parent")
val parent = project.parent!!
// Add GAV to <parent> element
parentNode.appendNode("groupId", parent.group)
parentNode.appendNode("artifactId", parent.name)
parentNode.appendNode("version", parent.version)
// Guarantee that the <parent> element is at a deterministic location.
val groupIdElementIndex = projectNode.children().withIndex()
.filter { it.value is Node && ((it.value as Node).name() as QName).localPart == "groupId" }
.map { it.index }
.single()
projectNode.remove(parentNode)
projectNode.children().add(groupIdElementIndex, parentNode)
}Java / .properties files
All Java properties generated using java.util.Properties.store() contain a comment line with the generation timestamp. This varying content breaks reproducible builds.
Consider setting the Java system property java.properties.date to some fixed value in your build scripts.
For Gradle, consider adding a line like the following to gradle.properties.
systemProp.java.properties.date=Your project's comment
Java / character set + locales
When building with Java 17 or older, consider setting file.encoding=UTF-8. UTF-8 is the default since Java 18.
Other system properties to consider depending on your build requirements: user.language=en, user.country=US, user.variant= (empty)
Python
Modern Python tooling (such as Flit and Hatch) support reproducible builds for pure-Python projects. You can read more about reproducible build support in Flit reproducible build docs and Hatch reproducible build docs. It's a bit more complex if your assets require native compilation, but if you can assure that your native compilation produces reproducible libraries on its own the packaging tool will produce reproducible builds..
A few guidelines:
- You should be following the modern ways of packaging projects - ideally define your project's metadata in pyproject.toml (PEP-621) and specify your build requirements as pinned dependencies following PEP-518
- In order to get plausible looking packages where files have "real" modification dates, you should - in your build process - set SOURCE_DATE_EPOCH environment variable before running
hatch buildor flit build - it should be a fixed timestamp - It is recommended that you store your timestamp in the repository and update it whenever release is being prepared (so for example when release notes change). Example how it is stored (in yaml file) and updated automatically (with pre-commit) in Airflow
You can read more about reproducible build support in Flit reproducible build docs and Hatch reproducible build docs.
Helm package
The helm package command from Helm versions before 4.0.0 cannot produce reproducible archives.
Since Helm version 4.0.0, it is possible to produce reproducible archives, even with the --sign option. The package archive entries get a constant uid/gid and fixed POSIX permissions. The file modification time is set to the source files' modification time.
Consider setting a constant file modification time, for example using find $PACKAGE_CONTENTS_DIRECTORY -exec touch -d "2000-01-01 00:00:00" {} +
Preparing reproducible .tar.gz packages
If you prepare source-tarball, or another .tar.gz package you can use scripts similar to this one - which takes the same source_date_epoch and repacks the .tar.gz file to be reproducible. There are however few gotchas:
1) Make sure to remove permissions for "group" and "other" for all files that you add to the repository. This is needed because group/other permissions have different default settings (based on umask) and clearing them is the most certain way of reproducibility. This can be done in a few ways:
- if you use
git archive- "-c tar.umask=0077" removes all permissions for group/others - Just run
chmod -R og= <directory>for the directory to compress - before running the reproducible script
2) if you use git archive , you can exclude some of the directories with .gitattributes eport-ignore specification
3) Consider using a fixed mtime and not using git-archive's gzip. For example: git archive --mtime="1980-02-01 00:00:00 UTC" --format=tar HEAD | gzip -6 --no-name > my-archive.tar.gz
4) Gzip can also contain non-reproducible information. Consider using the strip-nondeterminism tool, which is available for many systems. When using the gzip tool alone, consider adding the --no-name with a fixed compression level, for example gzip --no-name -6.
5) ZIP files are not reproducible by default and the zip tool does not offer an easy way to generate reproducible zip archives. Consider using the strip-nondeterminism tool, which is available for many distributions/package managers (Ubuntu, Debian, brew and more).