Maven: Gleicher Quellcode, jedes Mal eine andere JAR-Prüfsumme
Zwei Builds, ein Commit, zwei Hashes. Maven stempelt die Bauzeit in jeden Archiveintrag – project.build.outputTimestamp friert sie ein.
Aufwand:
ca. 30 Min.
Gilt für:
Maven 3, Maven 4
Das Symptom
Zweimal mvn clean package aus demselben Commit, zweimal sha256sum über das
Ergebnis – und zwei verschiedene Prüfsummen. Kein Byte Quellcode hat sich geändert,
kein Abhängigkeitsstand, nichts. Die JAR-Dateien sind fast gleich groß und
verhalten sich identisch, aber signieren, vergleichen oder gegen ein Artefakt aus
der CI prüfen lässt sich damit nichts.
Unangenehm wird das dort, wo jemand nachweisen soll, dass das ausgelieferte Artefakt
wirklich aus dem angegebenen Quellstand stammt.
Die „Layer 8“-Wahrheit
Ein JAR ist ein ZIP-Archiv, und jeder Eintrag in einem ZIP-Archiv trägt einen
Zeitstempel. Ohne anderslautende Vorgabe schreibt der Build dort die aktuelle
Bauzeit hinein. Die Klassendateien selbst sind byteweise identisch – nur eben in
einen Umschlag verpackt, auf dem steht, wann verpackt wurde. Die Prüfsumme bildet
man aber über den Umschlag. Das ist die ganze Geschichte, und sie klingt banal,
bis jemand eine Lieferkette absichern soll.
Die Gegenmaßnahme heißt project.build.outputTimestamp, eine Eigenschaft in der
pom.xml, die den Zeitstempel der Archiveinträge auf einen festen Wert legt. Wer
sie einträgt, in einem Zug zwei Builds macht und dann immer noch verschiedene
Prüfsummen sieht, hat nichts falsch gemacht: Reproduzierbarkeit geschieht laut
Apache auf Plugin-Ebene. Jedes am Bau beteiligte Plugin muss die Eigenschaft
auswerten, und ältere Versionen tun das nicht. Für genau diese Diagnose gibt es
mvn artifact:check-buildplan, das den Bauplan durchgeht und die Nachzügler
benennt – der interessante Teil der Arbeit ist danach das Hochziehen von
Plugin-Versionen, nicht das Setzen der Eigenschaft.
Die Sache hat ein Ablaufdatum, und das ist eine gute Nachricht. Ab Maven 4.0.0-beta-5
ist der Reproducible-Builds-Modus standardmäßig aktiv, ohne Änderung an der
pom.xml. Bis der Umstieg vollzogen ist, bleibt es dabei: Wer Reproduzierbarkeit
behauptet, ohne zweimal gebaut und die Hashes verglichen zu haben, behauptet nur
etwas.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.
Den Unterschied überhaupt erst festnageln
Vor der Konfiguration steht der Nachweis. Zweimal aus demselben Stand bauen und die Prüfsummen vergleichen – unterscheiden sie sich, weiß man, dass man ein Reproduzierbarkeitsproblem hat und nicht einen unsauberen Arbeitsbaum.
Die Eigenschaft project.build.outputTimestamp legt den Zeitstempel der Archiveinträge fest, statt die Bauzeit zu nehmen. Ein fester Wert in ISO-8601-Form mit Z für UTC genügt; sinnvoll ist das Datum des letzten inhaltlichen Standes, nicht ein beliebiges.
Die Eigenschaft allein genügt nicht, weil laut Apache alles auf Plugin-Ebene geschieht – ein Plugin, das sie nicht kennt, schreibt weiter die aktuelle Zeit. Das mitgelieferte Werkzeug geht den Bauplan durch und nennt genau die Plugins, deren Version nicht reicht.
mvn artifact:check-buildplan
4.
Die gemeldeten Plugins hochziehen
Jedes vom Prüflauf beanstandete Plugin auf eine Version anheben, die reproduzierbar baut. Das ist der eigentliche Arbeitsanteil und der Grund, warum die Umstellung selten in fünf Minuten erledigt ist – die Versionen müssen einzeln in die pom.xml, festgenagelt und nicht geerbt.
5.
Den Nachweis wiederholen
Dieselbe Doppelmessung wie am Anfang. Erst wenn beide Läufe dieselbe Prüfsumme liefern, ist der Build reproduzierbar – die Konfiguration allein ist eine Absicht, keine Eigenschaft.
?Der Quellcode ist identisch. Warum unterscheiden sich die JAR-Dateien trotzdem?
Weil ein JAR ein ZIP-Archiv ist und jeder Eintrag darin einen Zeitstempel trägt. Ohne Vorgabe schreibt Maven dort die aktuelle Bauzeit hinein. Die Klassendateien sind byteweise gleich, das Archiv drumherum ist es nicht – und die Prüfsumme wird über das Archiv gebildet, nicht über den Inhalt.
?Reicht es, die Eigenschaft in die pom.xml zu setzen?
Meistens nicht auf Anhieb. Reproduzierbarkeit passiert laut Apache auf Plugin-Ebene: Jedes beteiligte Plugin muss die Eigenschaft auch auswerten, und ältere Versionen tun das nicht. Genau dafür gibt es mvn artifact:check-buildplan, das den Bauplan durchgeht und die Plugins meldet, die noch aktualisiert werden müssen.
?Muss ich das in Maven 4 noch selbst konfigurieren?
Nein. Ab Maven 4.0.0-beta-5 ist der Reproducible-Builds-Modus laut Dokumentation standardmäßig aktiv, ohne Eingriff in die pom.xml. Für alles darunter bleibt die ausdrückliche Konfiguration nötig.
Belege
Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.