Rechnungsdaten revisionssicher ablegen mit Git

Aus LinuxUser 06/2025

Rechnungsdaten revisionssicher ablegen mit Git

© Eugenio Marongiu / 123RF.com

Strenger Wächter

Mit Git behalten Sie den Überblick über alle Änderungen an Programmcode und Daten. Etwas angepasst, hilft die Software sogar, mit überschaubarem Aufwand einen angemessenen Schutz gegen Veränderungen von Rechnungsdaten zu erreichen.

Aufgrund der seit Januar 2025 geltenden gesetzlichen Änderungen gilt es, elektronisch gestellte und empfangene Rechnungen zwischen Unternehmen besonders zu handhaben. Wie Sie diese auf Vollständigkeit und Korrektheit prüfen sowie selbst erstellen, haben wir bereits in vorherigen [1] Beiträgen [2] besprochen.

Als Selbstständiger und Unternehmer sind Sie ebenso angehalten, die Daten zur Rechnungslegung, also alle versendeten und empfangenen elektronischen Daten, vollständig und revisionssicher abzulegen. Dabei haben Sie für einen angemessenen Schutz gegen Veränderungen der gespeicherten Daten zu sorgen, also gegen versehentliches Löschen oder Überschreiben. Dahinter steht der Gedanke, dass der Ablauf des Geldflusses für Sie selbst und andere über einen längeren Zeitraum nachvollziehbar bleiben muss, beispielsweise für eine Überprüfung durch Externe.

Allerdings legen Sie Ihre Daten auf einem wiederbeschreibbaren Speichermedium ab. Es stellt sich die Frage, wie Sie nachträgliche Änderungen auf einem solchen Datenspeicher zuverlässig unterbinden.

Revisionssicher

Eine Möglichkeit sind WORM-Speicher, wobei WORM [3] für “write once, read many” steht, also für “einmal schreiben, vielfach lesen”. Das trifft auf eine Vielzahl von traditionellen Aufzeichnungsmaterialien zu, wobei Papier, Stein, Glas, Metall, Kunststoff oder Keramik [4] aufgrund der heutigen Datenmenge sowie der Geschwindigkeit beim Lesen und Schreiben oft nicht mehr den tatsächlichen Anforderungen im Alltag entsprechen. Zur digitalen Ära gehören Bandlaufwerke, CD- und DVD-ROMs sowie optische Medien wie magnetooptische Disks (Abbildung 1).

Abbildung 1: Magnetooptische Disks waren in Europa nie sonderlich populär und sind heute kaum noch erhältlich. Quelle: ocrho, Wikimedia, Public Domain

Abbildung 1: Magnetooptische Disks waren in Europa nie sonderlich populär und sind heute kaum noch erhältlich. Quelle: ocrho, Wikimedia, Public Domain

Sind solche Speichermedien und Methoden überhaupt noch zeitgemäß? Mittlerweile ist schon die Verfügbarkeit passender Laufwerke recht überschaubar. Viele Hersteller von Computersystemen liefern ihre Geräte seit Langem nicht (mehr) mit passenden Laufwerken oder Einschüben aus. Zur Nutzung bedarf es somit eines externen, zusätzlichen Laufwerks. Hinzu kommen noch die vergleichsweise teuren Speichermedien samt deren Lagerung.

Wie lässt sich heute also das Problem angemessen lösen? Welche alternativen Wege gibt es, den Rechtspflichten zu genügen und eine revisionssichere Datenablage mit zeitgemäßen Technologien umzusetzen? Hier gilt es vor allem, die begrenzte Lesbarkeit und Haltbarkeit des Mediums im Blick zu behalten, da die derzeitige Aufbewahrungsfrist für entsprechende Unterlagen 8 Jahre beträgt.

Wunsch und Realität

Haben Sie als Nutzer Schreibrechte in einem Verzeichnis beziehungsweise auf einem Speichermedium, lassen sich die dort abgelegten Daten jederzeit beliebig ändern. Das erfordert einen zusätzlichen Mechanismus, der auf den Schreibprozess Einfluss nimmt. Er muss zwischen verschiedenen Schreibvorgängen unterscheiden können: Einerseits soll er das Ablegen komplett neuer Daten erlauben, andererseits muss er das Ändern oder Löschen bereits bestehender Daten verhindern.

Um das möglichst praktikabel zu halten, läuft das auf einen Dienst in Form eines Programms hinaus, der den Datenbestand verwaltet und dessen Änderungen überwacht. Dieser Dienst muss wissen, wo die zu verwaltenden Daten liegen (Verzeichnis oder Partition), was der Bestand bereits umfasst und wie er mit Datenanfragen umgehen soll. Er muss prüfen, ob die Daten schon vorhanden sind, und darf sie nur dann hinzufügen, wenn das nicht der Fall ist. Der Vergleich erfolgt anhand zuvor erfasster Metadaten wie Dateinamen, Erstellzeitpunkt, Besitzer und Inhalt.

Unterschiede finden

Zu diesem Zweck rücken zunächst bekannte Werkzeuge wie Cmp und Diff ins Rampenlicht, die zwei oder mehr Dateien byte- oder zeilenweise miteinander vergleichen. Letzteres funktioniert bei Textdaten bestens.

Um uns hinsichtlich der Dateiinhalte nicht von vorneherein zu beschränken, weichen wir auf ein anderes Verfahren aus, das besser und gegebenenfalls auch schneller arbeitet: die Berechnung eines Hash-Werts über den Dateiinhalt. Als Verfahren eignen sich beispielsweise Message Digest 5 (MD5 [7]) oder Secure Hash Algorithm (SHA [8]) mit einer Länge von 160, 224, 256, 384 oder 512 Bit. Bei identischem Hash-Wert stimmen die verglichenen Dateien inhaltlich überein, selbst wenn die Metadaten vollkommen anders aussehen.

Je nach Hash-Algorithmus fallen die Hash-Werte unterschiedlich lang aus. Je kürzer ein Hash-Wert, desto weniger Rechenzeit benötigt der entsprechende Algorithmus dafür. Der Nachteil: Damit steigt gleichzeitig die Chance auf falsche Treffer, das Verfahren ist also nicht kollisionsresistent. Eine Kollision tritt auf, wenn der Algorithmus nicht nur für identische, sondern auch für unterschiedliche Daten denselben Hash-Wert errechnet.

Bei längeren Hashes sinkt die Wahrscheinlichkeit einer solchen so weit, dass der errechnete Wert als zuverlässiges Vergleichskriterium genügt. Daher raten wir von der Verwendung von MD5 ab, auch wenn der Algorithmus heute noch bei zahlreichen Anwendungen zum Einsatz kommt. Das gilt analog für SHA-1 mit einer Länge von lediglich 40 Bit. Deshalb wechseln bereits jetzt viele Anwendungen Schritt für Schritt zu größeren Bitlängen.

Zum Standardumfang jedes Linux-Systems zählen die Kommandozeilenwerkzeuge Md5sum, Sha1sum, Sha224sum, Sha256sum, Sha384sum und das im Folgenden präferierte Sha512sum. Wie die beiden Aufrufe in Listing 1 zeigen, identifiziert es die zwei Dateien rechnung-1234.pdf und rechnung-1234-new.pdf als inhaltlich identisch, obwohl sich die Namen unterscheiden: Sha512sum errechnet für beiden Dateiinhalte denselben Hash-Wert.

Listing 1

Gleichheit prüfen per Hash

$ sha512sum rechnung-1234.pdf rechnung-1234-new.pdf
4cfe8828ad93ec38ffa35344bab587c4e0a10c691e6a9cd6a4a0b189646f785b33935362fa994e5d6ded22032d7eeff7d88954402a279401fc94a0983f89d793  rechnung-1234.pdf
4cfe8828ad93ec38ffa35344bab587c4e0a10c691e6a9cd6a4a0b189646f785b33935362fa994e5d6ded22032d7eeff7d88954402a279401fc94a0983f89d793  rechnung-1234-new.pdf

Allerdings lassen sich die beiden Hashes aufgrund der Länge von einem Menschen nur schwer fehlerfrei vergleichen. Deshalb geben wir die Aufgabe an den Rechner weiter – er kann das besser. Der Aufruf in Listing 2 kombiniert Sha512sum mit den drei Werkzeugen Cut, Uniq und Wc in einer Pipe, um den Datenstrom zu bearbeiten. Zunächst kürzt Cut jede Zeile auf eine Spalte. Uniq fasst die aufeinanderfolgenden Zeilen zusammen, falls sie identisch sind. Am Schluss zählt Wc die noch übrig gebliebenen Zeilen. Liefert das Kommando als Ergebnis eine 1, bleibt nur noch eine Zeile übrig, da beide Hash-Werte identisch sind. Somit sind dann auch beide Dateien inhaltlich gleich.

Listing 2

Hash-Werte vergleichen

$ sha512sum \
    rechnung-1234.pdf \
    rechnung-1234-new.pdf | \
    cut -d ' ' -f 1 | \
    uniq | wc -l
1

Sagen Ihnen MD oder SHA als Hash-Verfahren nicht zu, werfen Sie am besten einen Blick auf das Werkzeug Hashdeep [9]. Es steht als gleichnamiges Paket für Debian, Ubuntu und Kali Linux bereit und unterstützt neben MD und SHA auch die moderneren Hash-Verfahren Tiger [10] und Whirlpool [11]. Zudem eignet es sich dazu, mithilfe einer Vergleichsliste Veränderungen in Datenbeständen festzustellen.

Hashdeep bildet eine Brücke zu hostbasierten Systemen zur lokalen Einbruchserkennung (HIDS). Bereits früher haben wir neben dem HIDS Integrit [12] auch Gitwatch [13] und Flashbake [14] besprochen, mit denen sich Veränderungen in Bestandsdaten [15] erkennen [16] und diese automatisiert im Versionskontrollsystem Git protokollieren lassen (Abbildung 2). Eine Alternative bietet der von der Inotify-Schnittstelle [17] bereitgestellte Service, der bei Veränderungen von Einträgen im Dateisystem (Inodes) eine Benachrichtigung versendet. Alle genannten Werkzeuge reagieren jedoch erst, nachdem die Veränderungen eingetreten sind, und können sie nicht im Vorfeld verhindern.

Abbildung 2: Das Tool Gitwatch zeigt modifizierte Dateien und den Push-Status in Echtzeit an.

Abbildung 2: Das Tool Gitwatch zeigt modifizierte Dateien und den Push-Status in Echtzeit an.

Git als Kontrollinstanz

Git [18] hat sich seit Jahren bei Entwicklern und Administratoren als verlässliches Versionskontrollsystem bewährt (Abbildung 2). Die Abläufe rund um seine Nutzung bilden einen festen Baustein im IT-Grundwissen. Von großem Vorteil ist die Tatsache, dass die Software sauber dokumentiert ist, sowohl in Buchform als auch online [19].

Abbildung 3: Das populäre Versionskontrollsystem Git lässt sich zur Realisierung eines softwarebasierten WORM-Systems einsetzen.

Abbildung 3: Das populäre Versionskontrollsystem Git lässt sich zur Realisierung eines softwarebasierten WORM-Systems einsetzen.

Für jede Änderung im Datenbestand erzeugt Git einen SHA-1-Hash, über den Sie die Änderung später referenzieren. Die Git-Entwickler wählten dieses Hash-Verfahren, da es eine gute Balance aus Geschwindigkeit und Entropie liefert [20].

Im Folgenden betrachten wir zwei Wege, wie Sie Git zur Realisierung eines softwarebasierten WORM-Systems einsetzen können. Der erste setzt auf die traditionelle Nutzung von Git, betrachtet jedoch nur den initialen Commit für eine Datei. Der zweite Weg ergänzt Git um einen speziellen Filter (einen Git Hook), der Änderungen an bereits eingepflegten Daten unterbindet.

In der ersten Variante pflegen Sie alle Dateien wie bisher gewohnt mittels git add Datei und git commit -m "Grund" in ein Git-Repository ein. Zur Auswertung greifen Sie dann später ausschließlich auf die erste Version der eingepflegten Datei zurück, den initialen Commit. Ob später Änderungen an der Datei stattgefunden haben, spielt dabei keine Rolle.

Daraus ergeben sich wiederum zwei Fragen: Wie findet man die erste Version einer im Git eingepflegten Datei heraus? Und lässt sich die Git-Historie so manipulieren, dass eine spätere Version einer eingepflegten Datei zur ersten Version werden kann, ohne dass diese Veränderung auffällt?

Original-Commit finden

Den initialen Commit für eine Datei im Git-Repository finden Sie über den simplen Aufruf git log Datei [21]. Git listet das Ergebnis in zeitlich umgekehrter Reihenfolge auf, der neueste Eintrag steht also ganz oben. Mit wenigen Zeilen Shell-Code automatisieren Sie den Vorgang und passen die Ausgabe dabei an Ihre Bedürfnisse an. Wir haben uns dazu von einem Codefragment [22] auf Stack Overflow inspirieren lassen. Allerdings liegt es im Online-Forum fehlerhaft vor. Listing 3 enthält eine korrigierte Variante als vollständiges Bash-Skript mit ergänzenden Kommentaren zum besseren Verständnis. Sie speichern es unter dem Namen finde-originalversion.sh ab.

Das Skript erwartet zwei Dateinamen als Aufrufparameter: den Namen der Datei im Git-Repository und zum Vergleich den Namen der lokalen Datei, die das Original repräsentiert (Zeile 4 und 6). In einer For-Schleife (Zeile 8 bis 17) durchsucht Git zunächst alle Log-Einträge nach der angegebenen Datei und vergleicht sie mit der lokalen Datei (Zeile 10). Fördert der Vergleich mittels git diff keine Unterschiede zutage, wurde der passende Log-Eintrag gefunden. Git gibt dann den vollständigen Commit Hash samt Autor, E-Mail-Adresse und Zeitstempel aus. In Zeile 13 stehen %H, %an, %ae und %ad für die jeweiligen Platzhalter. Bei Bedarf könnten Sie stattdessen git show Commit-Hash aufrufen, um weitere Details zum Commit darzustellen.

Listing 3

Original-Commit suchen

#! /bin/bash
if [ "$#" -eq 2 ]; then
  # Parameter 1: Dateiname im Git-Repository
  filenameInGitRepo="$1"
  # Parameter 2: lokaler Dateiname
  filenameToCompare="$2"
  # gehe durch alle Logeinträge für die Datei
  for rev in $(git log --format="%H" -- "$filenameInGitRepo" ); do
    # vergleiche die Revision mit der lokalen Datei
    git diff --quiet "$rev:$filenameInGitRepo" "$filenameToCompare";
    if [ "$?" -eq 0 ]; then
      # gebe den Logeintrag aus
      git log --pretty=format:"%H by %an <%ae> on %ad" | grep "$rev"
      # beende mit Rückgabewert 0
      exit 0
    fi
  done
else
  # falscher Aufruf des Skripts
  echo "Two filenames as parameter expected. Quit."
  # beende mit Rückgabewert 1
  exit 1
fi

In Listing 4 sehen Sie zunächst die beiden Log-Einträge für die Datei rechnung-1234.pdf – den mit einer Revision oben (Zeile 2 bis 5) und darunter den ursprünglichen, initialen Commit (Zeile 6 bis 9). Aus dem Log-Eintrag zum originalen Commit ersehen Sie, dass die Datei rechnung-1234.pdf initial am 7. Februar 2025 um 0:47 Uhr in das Repository eingepflegt wurde. Ein Aufruf des Skripts aus Listing 3 (Zeile 10) gibt den Commit-Hash zur Originalversion aus (Zeile 11). Damit extrahieren Sie bei Bedarf die originale Datei in die Staging Area ihres Git-Repositorys (Zeile 12).

Listing 4

Bash-Skript in Anwendung

$ git log rechnung-1234.pdf
commit 0001a1f66a828592a3b879bc9d7daa555d6b6537
Author: Frank Hofmann <frank.hofmann@efho.de>
Date:   Fri Feb 7 00:47:28 2025 +0100
    Rechnung 1234, erneuert
commit f15af765e06626092efc8b14511950ab4c5d687f
Author: Frank Hofmann <frank.hofmann@efho.de>
Date:   Fri Feb 7 00:47:00 2025 +0100
    Rechnung 1234
$ ./finde-originalversion.sh rechnung-1234.pdf rechnung-1234.pdf.original
f15af765e06626092efc8b14511950ab4c5d687f by Frank Hofmann <frank.hofmann@efho.de> on Fri Feb 7 00:47:00 2025 +0100
$ git restore f15af765e06626092efc8b14511950ab4c5d687f -- rechnung-1234.pdf

Git-Historie ändern

Git erlaubt, die Historie eines Repositorys zu ändern [23]. Das umfasst die Korrektur des letzten Commits sowie das Löschen und Zusammenfassen mehrerer aufeinanderfolgender Commits. Die Vorgehensweise unterscheidet sich für lokale, noch nicht in das Haupt-Repository übermittelte Änderungen in der Staging Area (Fall 1) und Änderungen, die Sie bereits in das Haupt-Repo übermittelt haben (Fall 2).

Im ersten Fall greifen Sie zu den Kommandos git commit --amend und git rebase -i Commit-Hash. Das erste ändert nur den letzten Commit. Das zweite ermöglicht interaktive Korrekturen, wobei Git den gewünschten Commit über den Commit-Hash findet. Das erlaubt beispielsweise das Entfernen des Commits (drop) und das Zusammenfassen mehrerer Commits zu einem einzigen (squash). Für Fall 2 gilt: Jedwede Änderungen sorgen für Ärger bei allen, die eine Kopie des Repositorys besitzen – lassen Sie es also besser. Es können etliche Seiteneffekte auftreten [24], die Sie lieber vermeiden wollen.

Zusammenfassend lässt sich sagen, dass lokale Änderungen in der Staging Area nicht unbedingt auffallen, während Änderungen im Haupt-Repository definitiv Arbeit nach sich ziehen und insbesondere für alle sichtbar werden. Mogeln klappt zwar immer noch, wird aber deutlich aufwendiger und lässt sich nicht mehr ohne Komplizen geheim halten. Die Revisionssicherheit von Daten auf diesem Weg auszuhebeln, erfordert Detailwissen, der Aufwand dafür lohnt sich kaum. Eine Storno-Rechnung zu schreiben und sie einzupflegen, macht wesentlich weniger Umstände.

Git Hooks nutzen

Eine bessere Variante zielt darauf ab, Änderungen an bereits bestehenden Daten zu unterbinden. Dazu kommt ein spezieller Filter zum Einsatz, ein sogenannter Git Hook [25]. Darüber ergänzen Sie die Standardfunktionalität von Git um eine Vorabprüfung, die lediglich neue Daten zulässt und die Bestandsdaten vor Veränderung schützt.

Git Hooks sind Skripte, die Git automatisch ausführt, sobald bestimmte Ereignisse auftreten. Die Tabelle “Übersicht: Git Hooks” stellt diese Ereignisse und die Bezeichnungen der dazugehörigen Git Hooks gegenüber. Aus der Liste ist im vorliegenden Fall nur der Pre-Commit Hook interessant. Darüber steuern Sie, ob ein Commit ausgeführt wird.

Bereich/Name des Hooks

Bedeutung

Patches

applypatch-msg

Beschreibung des angewendeten Patches

pre-applypatch

Aktionen vor der Anwendung eines Patches

post-applypatch

Aktionen nach der Anwendung eines Patches

Commits

commit-msg

Beschreibung zum Commit

prepare-commit-msg

Vorbereitung der Beschreibung zum Commit

pre-commit

Aktionen vor dem Commit

post-commit

Aktionen nach dem Commit

Rebase und Receive

pre-rebase

Aktionen vor einem Rebase

pre-receive

Aktionen nach einem Rebase

post-checkout

Aktionen nach einem Checkout

post-merge

Aktionen nach einem Merge

post-receive

Aktionen nach einem Receive

post-rewrite

Aktionen nach einem Umschreiben

post-update

Aktionen nach einer Aktualisierung

update

Aktionen zur Aktualisierung

Sonstiges

pre-auto-gc

Aktionen vor dem Aufräumen des lokalen Repositorys

pre-push

Aktionen vor einem Übertrag in das Haupt-Repository

Für einen Git Hook benötigen Sie Programmierkenntnisse: Sie schreiben ihn als Skript in der Programmiersprache Ihrer Wahl, beispielsweise Bash, Perl, Ruby oder Python. Die inhaltliche Gestaltung steht Ihnen frei, nur auf den Rückgabewert des Skripts müssen Sie achten. Liefert es einen Rückgabewert von 0, findet anschließend der Commit statt, bei einem Rückgabewert ungleich 0 unterbleibt er. Sinnvoll sind zusätzliche Nachrichten für den Aufrufenden, beispielsweise, was während der Anwendung des Git Hooks passiert und ob die Ausführung des Commits erfolgt ist.

Es wäre schön, das Bash-Skript aus Listing 3 einfach wiederzuverwenden. Allerdings klappt das nicht. Sie benötigen die Liste aller Dateien aus der Staging Area, also aller für Veränderungen vorgemerkten Files. Die von Ihnen hinzugefügten Dateien liefert Git mit dem Aufruf aus der ersten Zeile von Listing 5. Dabei steht das A am Ende für “added files”.

Alle gelöschten, geänderten und umbenannten Dateien erhalten Sie über einen ähnlichen Aufruf, wobei sich nur der Filter am Ende auf DMR ändert (“deleted, modified, renamed”, zweite Zeile). Eine Liste aller Dateien, die Git bereits getrackt hat, die also jemals zum Repository hinzugefügt wurden, liefert der Git-Aufruf aus der dritten Zeile des Listings. Der Aufruf sort -u sortiert die Ausgabe von Git alphabetisch aufsteigend und entfernt alle Duplikate.

Listing 5

Dateien überprüfen

$ git diff --name-only --cached --diff-filter=A
$ git diff --name-only --cached --diff-filter=DMR
$ git log --pretty=format: --name-only --diff-filter=A | sort -u

Listing 6 enthält den kompletten Pre-Commit Hook und nutzt die bisher beschriebenen Schritte. Zuerst ermitteln Sie alle hinzugefügten, gelöschten, geänderten und umbenannten Dateien in der Staging Area (Zeile 3 und 5). Danach prüfen Sie, ob in dem Git Repository bereits Commits vorliegen (Zeile 7). Falls ja, beziehen Sie von Git eine Liste aller Dateien, die das Repository beinhaltet (Zeile 9).

In einer For-Schleife (Zeile 11 bis 20) prüfen Sie nun für jedes File in den hinzugefügten Dateien, ob es sich in der Liste der bereits getrackten Dateien im Repository befindet. Falls ja, beenden Sie den Pre-Commit mit dem Wert 1 und brechen somit den gesamten Commit ab (Zeile 12 bis 17). Anderenfalls fügen Sie die neue Datei dem Repository hinzu. Steht das Git-Repository noch leer, beispielsweise wenn Sie es neu erstellt haben, werden alle neuen Dateien hinzugefügt (Zeile 23 bis 26).

Abschließend prüfen Sie, ob die Liste der gelöschten, geänderten oder umbenannten Dateien leer ist (Zeile 29). Falls nein, beenden Sie den Pre-Commit mit dem Wert 2 (Zeile 31 bis 33) und brechen damit den gesamten Commit ab: Änderungen an bestehenden Daten sind nicht zulässig.

Listing 6

Pre-Commit Hook

#! /bin/bash
# hinzugefügte Dateien (Staging Area)
addedFiles=$(git diff --name-only --cached --diff-filter=A)
# gelöschte, geänderte oder umbenannte Dateien (Staging Area)
otherFiles=$(git diff --name-only --cached --diff-filter=DMR)
# sind schon Commits vorhanden?
if git log > /dev/null 2>/dev/null; then
  # Liste zuvor hinzugefügter Dateien (alphabetisch sortiert)
  allTrackedFiles=$(git log --pretty=format: --name-only --diff-filter=A sort -u)
  # gehe durch jede hinzuzufügende Datei
  for testedFile in $addedFiles; do
    if [[ "$allTrackedFiles" =~ "$testedFile" ]]; then
      # Datei schon im Repo, kann nicht erneut hinzugefügt werden:
      # weise den gesamten Commit zurück
      echo "$testedFile is already in the list of tracked files"
      exit 1
    fi
    # Datei noch nicht im Repo, wird hinzugefügt
    echo "preparing to add $testedFile"
  done
else
  # gehe durch jede hinzuzufügende Datei
  for testedFile in $addedFiles; do
    # Datei noch nicht im Repo, wird hinzugefügt
    echo "preparing to add $testedFile"
  done
fi
# Liste der anderen Dateien leer?
if [ ! -z "$otherFiles" ]; then
  # nicht leer, Commit zurückweisen
  echo "list of changed/removed files must be empty"
  echo "files: $otherFiles"
  exit 2
fi
exit 0

Damit Git den Hook nutzen kann, gilt es hinsichtlich der Benennung und des Speicherorts des Skripts einige Dinge zu beachten. Den Git Hook speichern Sie im Verzeichnis des Git-Repositorys unter .git/hooks/. Das Skript selbst benennen Sie nach der Aktion, die es ausführt, also pre-commit für einen Pre-Commit Hook. Eigene Namen sind hier nicht zulässig. Abschließend setzen Sie mit dem Aufruf aus Listing 7 die Ausführungsrechte für den Hook.

Listing 7

Berechtigungen setzen

$ chmod -v +x .git/hooks/pre-commit

Pre-Commit Hook testen

Nachdem Sie den Pre-Commit Hook als Datei erstellt haben, muss er noch einen Praxistest bestehen. Dazu erzeugen Sie zunächst ein Verzeichnis beispiel/, wechseln dorthin und erzeugen mittels git init ein leeres Git-Repository. Nun kopieren Sie den zuvor erstellten Pre-Commit Hook als Datei .git/hooks/pre-commit hinein.

Dann legen Sie im Verzeichnis beispiel/ eine Datei an (Listing 8, Zeile 2), fügen sie der Staging Area hinzu (Zeile 3) und nehmen einen Commit vor (Zeile 4). Anschließend ändern Sie die Datei, beispielsweise indem Sie eine weitere Zeile anfügen (Zeile 10). Die geänderte Datei fügen Sie der Staging Area wieder hinzu (Zeile 11) und nehmen einen Commit vor (Zeile 12). Der Commit scheitert jedoch, weil der Pre-Commit Hook seine Wirkung tut: Er verhindert, dass Git die geänderte Datei in das Repository eincheckt (Zeile 13 und 14).

Nun prüfen Sie, ob Sie Dateien aus dem Repository entfernen können. Dazu löschen Sie die Testdatei aus der Staging Area (Zeile 16) und bestätigen die Änderung (Zeile 18). Das geht jetzt schief, weil der Pre-Commit Hook verhindert, dass Sie Daten aus dem Repository löschen (Zeile 19 und 20). Dasselbe gilt analog für den Versuch, den letzten Commit per Amend zu ändern (Zeile 22). Hier greift der Pre-Commit Hook ebenfalls (Zeile 23 und 24).

Listing 8

Git-Repository mit Daten füllen

### Daten hinzufügen
$ echo "Hallo" > daten
$ git add daten
$ git commit -m "daten hinzugefügt"
preparing to add daten
[master (Root-Commit) e5b4160] daten hinzugefügt
 1 file changed, 1 insertion(+)
 create mode 100644 daten
### Geänderte Daten einchecken
$ date >> daten
$ git add daten
$ git commit -m "daten geändert"
list of changed/removed files must be empty
files: daten
### Daten löschen
$ git rm --cached daten
rm 'daten'
$ git commit -m "daten gelöscht"
list of changed/removed files must be empty
files: daten
### Letzten Commit ändern
$ git commit --amend
list of changed/removed files must be empty
files: daten

Damit haben Sie nachgewiesen, dass der Git Hook nachträgliche Änderungen der Daten erfolgreich verhindert. Um Ihnen das Erstellen eines eigenen Hooks zu erleichtern, steht der Code aus Listing 6 auf Github [26] für Sie zum Herunterladen bereit (Abbildung 4). Sie finden ihn außerdem im Download-Bereich zu diesem Artikel. Wir freuen uns über Ihr Feedback dazu.

Abbildung 4: Unser Pre-Commit-Hook-Skript steht auf Github f&uuml;r Sie zum Herunterladen bereit.

Abbildung 4: Unser Pre-Commit-Hook-Skript steht auf Github für Sie zum Herunterladen bereit.

Die hier gezeigten Aufrufe decken alle von uns als praxisrelevant eingestuften Fälle ab. Möglicherweise ist die Liste jedoch nicht ganz vollständig. Sollten Sie fehlende Sonderfälle entdecken, lassen Sie es uns bitte wissen, damit wir sie in den Pre-Commit Hook einbauen können.

Fazit

Das ursprünglich gesetzte Ziel, Daten angemessen vor Veränderungen zu bewahren, lässt sich mit der Nutzung von Git als Verwaltungsdienst erreichen. Die zwei gezeigten Wege erlauben eine Umsetzung mit handhabbaren Mitteln. Für Endanwender fehlt noch eine Bedienoberfläche, um die Nutzung zu vereinfachen und Fehlbedienungen auszuschließen.

Eine zukünftige, nicht zu unterschätzende Schwachstelle von Git sind die derzeit verwendeten Hashes. Zurzeit basieren sie auf SHA-1 mit einer Länge von lediglich 40 Bit. Die Git-Entwickler diskutieren bereits heute, SHA-1 durch ein besseres Verfahren zu ersetzen [27].

Das von uns skizzierte Verfahren hindert Sie nicht daran, auf Dateiebene in den Metadaten von Git Änderungen vorzunehmen, beispielsweise Hashes auszutauschen oder Einträge zu löschen. Eine solche Manipulation setzt allerdings einiges Fachwissen voraus. Um Veränderungen noch weiter zu erschweren, bringt Git-annex [28] zusätzliche Sperrmechanismen für (Binär-)Dateien mit. Wir testen derzeit noch, inwieweit das Werkzeug eine brauchbare Alternative oder Ergänzung zu Pre-Commit Hooks darstellt, und werden Ihnen darüber berichten, sobald diesbezüglich Klarheit besteht. (jlu)

Danksagung

Der Autor bedankt sich bei Werner Heuser, Wolfram Eifler und Axel Beckert für deren Anmerkungen und Unterstützung bei der Erstellung dieses Artikels.

Der Autor

Frank Hofmann arbeitet zumeist von unterwegs aus als Entwickler, Trainer und Autor. Bevorzugte Arbeitsorte sind Berlin, Genf und Kapstadt. Er gehört zu den Verfassern des Debian-Paketmanagement-Buchs [29].

Infos

  1. ZUGFeRD/XRechnung (Teil 1): Frank Hofmann, “Holpriger Galopp”, LU 06/2025, S. 16, https://www.linux-community.de/51811

  2. ZUGFeRD/XRechnung (Teil 2): Frank Hofmann, “Davongetrabt”, LU 06/2025, S. 22, https://www.linux-community.de/51936

  3. WORM: https://en.wikipedia.org/wiki/Write_once_read_many

  4. Friedrich Naumann, “Vom Abakus zum Internet”: https://www.e-sights-publishing.de/vom-abakus-zum-internet-die-geschichte-der-informatik.html

  5. MO-Disk: https://de.wikipedia.org/wiki/Magneto_Optical_Disk

  6. MD5: https://de.wikipedia.org/wiki/Message-Digest_Algorithm_5

  7. SHA: https://de.wikipedia.org/wiki/Secure_Hash_Algorithm

  8. “HASHDEEP: A Digital Forensics Tool in Kali Linux”: https://www.geeksforgeeks.org/hashdeep-a-digital-forensics-tool-in-kali-linux/

  9. Tiger: https://de.wikipedia.org/wiki/Tiger_(Hashfunktion)

  10. Whirlpool: https://de.wikipedia.org/wiki/Whirlpool_(Algorithmus)

  11. Integrit: https://github.com/integrit/integrit

  12. Gitwatch: https://github.com/gitwatch/gitwatch

  13. Flashbake: https://github.com/cmdln/flashbake

  14. HIDS im Einsatz: Frank Hofmann, “Erbsenzählerei”, LU 09/2016, S. 82, https://www.linux-community.de/37228

  15. Data Integrity Check: Frank Hofmann, “Revision”, LU 05/2019, S. 70, https://www.linux-community.de/42478

  16. Inotify: https://wiki.ubuntuusers.de/inotify/

  17. Git: https://git-scm.com

  18. “Git. Verteilte Versionskontrolle für Code und Dokumente”: http://gitbu.ch/git.pdf

  19. “Understanding Git commit SHAs”: https://graphite.dev/guides/git-hash

  20. “Git Basics – Viewing the Commit History”: https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-History

  21. “Finding first commit of a file in git”: https://stackoverflow.com/questions/9232363/finding-first-commit-of-a-file-in-git

  22. “Git Tools – Rewriting History”: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History

  23. “Side effects of rewriting history”: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository

  24. Git Hooks: https://githooks.com

  25. Pre-Commit Hook zum Download: https://github.com/hofmannedv/training-git/blob/master/examples/hooks/pre-commit

  26. “Migrate Git from SHA-1 to a stronger hash function”: https://git-scm.com/docs/hash-function-transition

  27. Git-annex: https://git-annex.branchable.com

  28. Debian-Paketmanagement-Buch: https://dpmb.org

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 06/2025 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

1 Kommentar
Älteste
Neuste Beste Bewertung
Lars
8 Monate her

Enttäuschender Artikel. Fehlende Betrachtung rechtlicher Anforderungen. GoBD
/ EU-Richtlinien fordern nachprüfbare Unveränderbarkeit,
Nachvollziehbarkeit und Protokollierung. Der Artikel behandelt diese
Standards nicht und zeigt nicht, ob/wie Git diese Anforderungen erfüllt.

Nach oben