Gratulation! gmail signiert durch domain.tld

Mailserver SPF, DKIM, DMARC – Was wie und warum?


Domain-based Authentifizerung für Emails mit SPF, DKIM und DMARC

Vorwort:

Als Admininstrator von Servern stößt man immer wieder auf die Schlagwörter SPF, DKIM, DMARC. Da ich mich nun aufgrund eines aktuellen Projekts ebenfalls mit Email Versand und nicht-als-spam-markiert-werden beschäftigt habe möchte ich hier eine kleine Erklärung und Anleitung zu diesem Thema bereitstellen.

Die Beispielkonfiguration in diesem Artiel verwendet debian Linux mit postfix und opendkim und bind als DNS Server.

Was ist SPF, DKIM, DMARC

Zuerst einmal zur Begriffserklärung:

SPF = Sender Policy Framework – http://en.wikipedia.org/wiki/Sender_Policy_Framework

DKIM = Domain Key Identified Mail – http://en.wikipedia.org/wiki/DomainKeys_Identified_Mail

DMARC = Domain-based Message Authentication, Reporting and Conformance – http://en.wikipedia.org/wiki/DMARC

Wozu?

Alle 3 Methoden helfen, dass die Gegenstelle (Mailempfänger) überprüfen kann ob der Absender (Server) der Email authorisiert ist, Emails unter dieser Domain zu versenden.

Was braucht man dazu und wie funktionierts?

  • opendkim (=milter für Postfix)
  • dns TXT Records

Wird eine Email verschickt hängt der opendkim Dämon (ACHTUNG: nicht mehr dkim-filter verwenden, wird nicht mehr weiterentwickelt) an jede ausgehende Email einen Key an. D.h. er signiert jede Email einer Domain für die er authorisiert ist mit dem hinterlegten Schlüssel.

Dieser Key wird dann vom Empfänger überprüft indem er den entsprechenden DKIM TXT Record (mail._domainkey.domain.tld. IN TXT “v=DKIM1; …”) aus dem DNS abruft und die Signatur prüft.

Zusätzlich kann der Empfänger auch prüfen ob der Versenderserver vom Domaininhaber authorisiert wurde, Emails für diese Domain zu versenden. Dies geschieht indem er den SPF TXT Record (domain.tld. IN TXT “v=spf1 ….”) überprüft. Dort ist z.B. hinterlegt, welcher Mailserver mit welchen Hostnamen und welchen IP Adressen Mails verschicken darf.

Zuletzt kann der Empfänger über den DMARC TXT Record (_dmarc.domain.tld. IN TXT “v=DMARC1; …”) eine Regel einholen, wie er denn mit den Emails verfahren soll, die die Prüfung nicht bestanden haben (z.B. von einem Host geschickt, der im SPF Record nicht erlaubt ist oder die DKIM Signatur nicht überprüft werden konnte).

ran ans Eingemachte – die Konfiguration

Um die zusammengehörigen Konfigurationseinstellungen besser ersichtlich zu machen wurden einzelne zusammengehörige Teile farblich markiert. z.B. die Domain, der Selektor, usw. …

Konfiguration opendkim Daemon

Wie bereits beschrieben sind die Rahmenbedingungen eine funktionierende postfix Installation und voller Zugriff auf die DNS Einträge einer Domain (zum Anlegen von TXT Records).

Installation des opendkim Pakets (debian):

root@shmail:/etc# apt-get install opendkim

Nun muss für den opendkim Daemon und für Postfix noch die Schnittstelle konfiguriert werden, über die beide miteinander kommunizieren. Dies geschieht im /etc/default/opendkim Konfigurationsfile:

root@shmail:/etc/default# cat opendkim
# 20141108 leo.eibler add listener
SOCKET="inet:8891@localhost" # listen on loopback on port 8891

In diesem Beispiel hört der opendkim Daemon auf den localhost Socket Port 8891.

Dazu passend muss nun postfix im Konfigurationsfile /etc/postfix/main.cf konfiguriert werden:

root@shmail:/etc/postfix# cat main.cf
...
# 20141108 leo.eibler add DKIM support
milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

Die Konfigurationsdatei des opendkim Pakets selbst liegt in /etc/opendkim.conf . Hier sind nun einige Anpassungen durchzuführen:

root@shmail:/etc# cat opendkim.conf
# Log to syslog
Syslog                  yes
SyslogSuccess           yes
LogWhy                  yes
# Required to use local socket with MTAs that access the socket as a non-privileged user (e.g. Postfix)
UMask                   002

# 20141109 leo.eibler this is a multidomain setup (use different keys for different domains)
# run command for each domain to generate keys:
#   cd /etc/mail
#   opendkim-genkey -r -s mail -d otherdomain.com
#   mv mail.private otherdomain.com.dkim.private
#   mv mail.txt otherdomain.com.dkim.txt
#   chmod ugo+r otherdomain.com.dkim.*
#   /etc/init.d/opendkim restart
# add TXT record to dns from file otherdomain.com.dkim.txt
# append domain to these 2 files:
KeyTable                /etc/mail/DkimKeyTable
SigningTable            refile:/etc/mail/DkimSigningTable

Nach der Konfiguration des opendkim Pakets müssen die entsprechenden Signatur Einträge für die jeweilige Domain erzeugt werden. Hierzu gibt es das opendkim-genkey tool, welches im opendkim Paket mitinstalliert wird:

root@shmail:/etc/mail# opendkim-genkey -r -s mail -d domain.tld

Nach der Ausführung des Kommandos finden sich 2 neue Dateien im Verzeichnis /etc/mail:

  • mail.private = enthält den Schlüssel mit dem die versendete Email vom opendkim/postfix Gespann signiert wird
  • mail.txt = enthält den TXT DNS record mit der Signatur, die vom Empfänger geprüft werden kann

Nun muss opendkim noch mitgeteilt werden, wie es die Keys findet. Der erste Schritt dazu ist die SigningTable Datei. Sie enthält das Mapping von Versender Adresse (FROM: ….@domain.tld) zu Keyfile das für die Signatur verwendet wird:

root@shmail:/etc/mail# cat /etc/mail/DkimSigningTable
# format:
#   $pattern    $keyname
*@domain.tld             domain.tld
*@otherdomain.tld        otherdomain.tld

Der Einfachheit halber wird der keyname gleich wie der Domainname gewählt. Hier könnte man z.B. auch 2 Domains mit dem selben Key signieren, indem der gleiche keyname verwendet wird.

Die zweite Konfigurationsdatei KeyTable enthält mehrere Komponenten:

  • den Verweis auf die Datei mit der Signatur (die Datei mail.private mit opendkim-genkey erstellt wurde):
  • die Domain für die signiert wird
  • den Selektor der vom Empfänger in der DNS Abfrage verwendet wird um die Signatur zu überprüfen
root@shmail:/etc/mail# cat /etc/mail/DkimKeyTable
# format:
#   $keyname    $domain:$selector:$keypath
domain.tld               domain.tld:mail:/etc/mail/domain.tld.dkim.private
otherdomain.tld          otherdomain.tld:mail:/etc/mail/otherdomain.tld.dkim.private

Nach Abschluss der Konfiguration (oder hinzufügen einer Domain) muss der opendkim Daemon neu gestartet werden:

root@shmail:/etc# /etc/init.d/opendkim restart

Häufige Fehler:

  • Die Signaturdatei kann aufgrund der Dateirechte von opendkim nicht gelesen werden (chmod ugo+r … oder chown postfix …)
  • opendkim wurde nicht neugestartet: /etc/init.d/opendkim restart
  • postfix wurde nicht neugestartet: /etc/init.d/postfix restart

Konfiguration DNS Records

Nach der Konfiguration des opendkim Daemons wird nun jede Email von Postfix entsprechend um eine DKIM Signatur ergänzt. Nun fehlen die DNS Einträge, mit denen der Empfänger nun den Versender überprüfen kann.

DNS: Sender Policy Framework (SPF)

Hierzu ist im DNS Zone Record (in diesem Beispiel bind) ein entsprechender TXT Record für die Domain einzurichten:

; TXT records for SPF
domain.tld. IN TXT  (
	 "v=spf1 mx a ip4:10.1.1.40/29 ip4:192.168.0.64/29 "
	 "a:www.domain.tld a:subdomain.domain.tld "
	 "include:mail.hoster.tld"
	 )

Was bedeuten diese Angaben im SPF Record?

  • Der TXT record wird durch die Angabe v=spf1 als SPF Version 1 Record identifiziert
  • mx und a bedeuten, dass der in den MX Records eingetragene Mailserver und die Domain selbst berechtigt ist Emails zu versenden
  • Die beiden ip4:…. Einträge bedeuten, dass Server mit einer IP Adresse in den angegebenen Subnetzen berechtigt sind, Emails für diese Domain zu versenden. Es können durch Hinzufügen mehrerer ip4 Abschnitte mehrere IP Nutze angegeben werden (in diesem Beispiel 2). Hier müssen z.B. die IP Adressen der Mailrelays des Hosters eingetragen werden
  • Die a:<subdomain>.domain.tld Einträge erlauben das Versenden von Mails von den angegebenen Subdomains. Auch hier sind mehrere Einträge für mehrere Subdomains erlaubt
  • Die include:<andere Domain> Einträge fügen weitere Domains bzw. Hosts hinzu, denen es erlaubt ist, Emails zu versenden. Hier wird z.B. das Mailrelay des Hosters eingetragen. Auch hier sind mehrere Einträge für mehrere andere Domains erlaubt

Häufige Fehler:

  • Der Punkt nach dem Domainnamen fehlt: domain<PUNKT>tld<PUNKT> IN TXT ”….”
  • Der TXT Record ist zu lange. Hier in diesem Beispiel wurde der TXT Record auf mehrere Zeilen aufgetrennt. Die Syntax hierfür lautet: Klammer auf ( danach in Anführungszeichen “erster Teil<Leerzeichen>” danach nächste Zeile in Anführungszeichen “zweiter Teil” usw. und abschließend Klammer zu ). Wichtig hierbei: Die Leerzeichen innerhalb der einzelnen in Anführungszeichen eingeschlossenen Abschnitte nicht vergessen: “erster Teil” “zweiter Teil” wird sonst zu “erster Teilzweiter Teil” zusammengesetzt.
  • Die Serial Number des Zone Records wurde nicht erhöht
  • Die Ablaufzeit des Zone Records ist sehr hoch gesetzt und die anderen Server haben die Änderungen noch nicht mitbekommen

DNS: Domain Key Identified Mail (DKIM)

Für das DKIM Signatur Verfahren sind ebenfalls im DNS Zone Record (in diesem Beispiel bind) ein entsprechender TXT Record für die Domain einzurichten:

mail._domainkey.domain.tld. IN TXT  (
	 "v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvkDET02OqKcvgkpSxRvGMVzqwj5fxNFcuLWhLCcMsdx7hxlquRppBjbirvEV0HgRHS/za+KKp45edd4qBeChASEbJJ2NpNGIyL+Jy0jmpCK1E5AZKopjSLnSMo78lkaZDj/t5XRqj0qhnldUgtOGj6M8PHvN7AH9UBpXxhXAe1QIDAQAB"
	 )

Nun ist der Selektormail” wichtig, der in der opendkim Konfiguration angegeben wurde:

  • siehe opendkim-genkey -r -s mail -d domain.tld Aufruf mit -s mail = Selektor “mail
  • siehe Konfigurationsdatei KeyTable mit domain.tld domain.tld:mail:/etc/mail/domain.tld.dkim.private = Selektor :mail:

Der TXT Record wird also mit mail._domainkey.domain.tld. angelegt.

Was bedeuten diese Angaben im DKIM Record?

  • Der TXT record wird durch die Angabe v=DKIM1 als DKIM Version 1 Record identifiziert
  • Das Verschlüsselungsverfahren RSA wird mit k=rsa festgelegt
  • Der Schlüssel zum Überprüfen wird mit p=<key> festgelegt. Dies ist der Schlüssel der durch opendkim-genkey in der mail.txt Datei erzeugt wurde

Häufige Fehler:

  • Der Punkt nach dem Domainnamen fehlt: mail<PUNKT>_domainkey<PUNKT>domain<PUNKT>tld<PUNKT> IN TXT ”….”
  • Der Selektor (hier im Beispiel mail) ist falsch bzw. stimmt nicht mit der opendkim Konfiguration überein
  • Serial Number oder Caching des Zone Records

DNS: Domain-based Message Authentication, Reporting and Conformance (DMARC) Richtlinie

Die DMARC Richtlinie sagt dem Empfänger, wie er denn mit einer Email die nicht die Prüfungen für SPF und/oder DKIM bestanden hat umzugehen hat.

Die DMARC Richtlinie wird ebenfalls im DNS Zone Record (in diesem Beispiel bind) über einen entsprechenden TXT Record für die Domain veröffentlicht:

_dmarc.domain.tld. IN TXT “v=DMARC1; p=quarantine; rua=mailto:postmaster@domain.tld”

Der TXT Record wird also mit _dmarc.domain.tld. angelegt.

Was bedeuten diese Angaben in der DMARC Richtlinie?

  • Der TXT record wird durch die Angabe v=DMARC1 als DMARC Version 1 Record identifiziert
  • Die Angabe p=quarantine bedeutet, dass eine Email die nicht die Prüfungen besteht entsprechend markiert (z.B. als Spam) werden soll. Weitere Alternativen hierzu wären p=none – dies wird als TEST Mode bezeichnet (der Empfänger prüft zwar ignoriert aber das Ergebnis und behandelt die Domain wie wenn keine SPF und DKIM Records existieren würden). Die härteste Variante ist mit p=reject anzugeben – Emails werden bei fehlerhafter Überprüfung abgewiesen
  • An die im optionalen Parameter rua=<Email-Adresse> angegebene Email Adresse wird täglich ein XML Report versendet mit einer Zusammenfassung über die fehlerhaften Emails die beim Empfänger eingelangt sind

Weitere Parameter und Angaben finden sich auf der DMARC Webseite: http://www.dmarc.org

Häufige Fehler:

  • Der Punkt nach dem Domainnamen fehlt: _dmarc<PUNKT>domain<PUNKT>tld<PUNKT> IN TXT ”….”
  • Es ist der Test Modus aktiv p=none
  • Serial Number oder Caching des Zone Records

Testen der Einrichtung

Testen der DNS Konfiguration

Zuerst kann die DNS Einrichtung der TXT Records z.B. mit dig oder nslookup überprüft werden (alle Ausgaben gekürzt).

Überprüfen des DNS Servers ns1.hoster.tld auf dem die TXT Records für die Domain angelegt wurden – SPF:

root@soyuz:~# dig @ns1.hoster.tld. TXT domain.tld
; <<>> DiG 9.7.3 <<>> @ns1.hoster.tld. TXT domain.tld
domain.tld. 21600 IN TXT  "v=spf1 mx a ip4:10.1.1.40/29 ip4:192.168.0.64/29 a:www.domain.tld a:subdomain.domain.tld include:mail.hoster.tld"

Man sieht hier schön, dass die einzelnen Teile in Anführungszeichen zusammengefügt wurden und als ein ganzer Block ausgegeben werden. Hier kann man nun kontrollieren ob man z.B. ein Leerzeichen in einem Block vergessen hat.

Überprüfen des DNS Servers ns1.hoster.tld auf dem die TXT Records für die Domain angelegt wurden – DKIM:

root@soyuz:~# dig @ns1.hoster.tld. TXT mail._domainkey.domain.tld
; <<>> DiG 9.7.3 <<>> @ns1.hoster.tld. TXT mail._domainkey.domain.tld
mail._domainkey.domain.tld. 21600 IN TXT  "v=DKIM1\; g=*\; k=rsa\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvkDET02OqKcvgkpSxRvGMVzqwj5fxNFcuLWhLCcMsdx7hxlquRppBjbirvEV0HgRHS/za+KKp45edd4qBeChASEbJJ2NpNGIyL+Jy0jmpCK1E5AZKopjSLnSMo78lkaZDj/t5XRqj0qhnldUgtOGj6M8PHvN7AH9UBpXxhXAe1QIDAQAB"

Überprüfen des DNS Servers ns1.hoster.tld auf dem die TXT Records für die Domain angelegt wurden – DMARC:

root@soyuz:~# dig @ns1.hoster.tld. TXT _dmarc.domain.tld
; <<>> DiG 9.7.3 <<>> @ns1.hoster.tld. TXT _dmarc.domain.tld
_dmarc.domain.tld. 21600  IN      TXT     "v=DMARC1\; p=quarantine\; rua=mailto:postmaster@domain.tld"

Testen der Mail Konfiguration

Verschicken einer Email von der jeweiligen Domain an einen Test Service wie z.B. check-auth@verifier.port25.com

root@test:~# echo "Dies ist ein Test" | mail -s "test email" check-auth@verifier.port25.com -- -f postmaster@domain.tld

Nun sollte auf der Absender Email Adresse postmaster@domain.tld eine Antwort eintreffen die hoffentlich so aussieht:

...
==========================================================
Summary of Results
==========================================================
SPF check:          pass
DomainKeys check:   neutral
DKIM check:         pass
DKIM check:         pass
Sender-ID check:    pass
SpamAssassin check: ham
...

Falls bei SPF, DKIM und Sender-ID jeweils pass steht funktioniert die Konfiguration.

Gratulation! gmail signiert durch domain.tld
Gratulation! gmail signiert durch domain.tld

Eine Prüfung ist auch einfach mittels gmail möglich.

Hat alles geklappt zeigt gmail Signiert durch: domain.tld

 

 

Gratulation!

Über den Autor

Ing. Leo Eibler ist hauptberuflich im Bereich Beratung und IT Service Management tätig. Neben seiner beruflichen Tätigkeit entwickelt er Webapplikationen und kümmert sich auf freiwilliger Basis um den Betrieb von diversen Servern.

http://www.eibler.at/

Ähnliche Artikel:

Jenkins – Manueller Neustart


Kurz und knapp und zur Dokumentation für mich selbst, die Möglcihkeiten Jenkins manuell neuzustarten (abgesehen von der klassischen Möglichkeit an der Kommandozeile)

(jenkins_url)/safeRestart

Alle im moment laufenden Jobs werden noch beendet. Neue Jobs werden in einer Queue vorgehalten um sie nach dem Neustart durchzuführen.

(jenkins_url)/restart

Wenns schneller gehen soll. Laufende Jobs werden ignoriert und einfach abgebrochen.

Ähnliche Artikel:

  • Noch keine vorhanden.

Refactoring: Eine Methode mit einem Methodenobjekt ersetzen


Heute möchte ich meine neue Artikelserie einläuten: Refactoring. Dabei werde ich in unregelmäßigen Abständen Tipps und Tricks dazu veröffentlichen. Der erste geht im speziellen an meinen Freund Eggi, der viel mit von Kollegen geerbten Legacy Code zu kämpfen hat:

Wer kennt sie nicht, viel zu lange Methoden. Mit Variablen die am anfang deklariert, in der Mitte 5x verändert und am Ende zurückgegeben werden sollen. Und dann auch noch 2 oder mehr von den Dingern!

Ok es müssen nicht 5 davon sein, und die Methode noch nichtmal lange. Ein schnelles, bewusst sehr simples Beispiel: Einfach mal versuchen die beiden Zuweisungen mittels Eclipse -> Extract Method als neue private Method rausziehen.

package test;

public class ToMuchVarMethod {
	
	public void shortExample(){
		
		int i = 1;
		int j = 1;
		
		i = 2;
	    j = 2;
	    
	    System.out.println(i +" "+ j);
	}

}

Die neue Methode wird einem mit der Meldung “Ambiguous return value: Selected block modifies more than one local variable used in subsequent code.” versagt.

Was also tun? Nun es mag in dem hier gewählten Beispiel nicht besonders schlau wirken, aber eine saubere Lösung wäre es, die Methode in ein eigenes Objekt umzuwandeln. Man muss dazu den Scope der lokalen Methodenvariablen ändern und sie zu Instanzvariablen dieses Objektes werden lassen. Als nächsten Schritt gilt es, die lange Methode in einzelne Methoden innerhalb des neuen Objekts aufzusplitten. Die neue Klasse bietet dann eine Startmethode an wie z.B. compute() oder run() und bildet darin durch die einzelnen Methodenaufrufe die alte Methode nach.

So entsehen gut testbare Units und klare logische Abgrenzungen was in den einzelnen Methoden passiert.

Auch wenn es in dem hier angeführten Beispiel zu mehr Zeilen Code führt und somit etwas aufgebläht wirkt, bedient man dabei doch folgende zwei Prinzipien, die zu einem besser lesbaren, verständlicheren und damit besser wartbaren Code führen:

Hier noch das Beispiel von oben aufgesplittet in

das Aufrufende Objekt:

package test;

public class ToMuchVarMethod {
	
	public void shortExample(){
		
		ShortExample e = new ShortExample(1, 1);
		e.compute();
	}

}

und Methodenobjekt:

package test;

public class ShortExample {
	
	private int i;
	private int j;
	
	ShortExample(int i, int j){
		this.i = i;
		this.j = j;
	}
	
	private void setToTwo(){
		this.i = 2;
		this.j = 2;
	}
	
	private void print(){
		System.out.println(this.i + " "+ this.j);
	}
	
	public void compute(){
		setToTwo();
		print();
	}

}

Ähnliche Artikel:

Full width mit dem Twenty fourteen Theme in WordPress


War euch am Blog etwas aufgefallen? Genauer am Twenty Fourteen Theme von WordPress? Lang hat es mich gestört, und beim Git Artikel dann war es mir zuviel. Da ist ein großer weißer Bereich für die Blogeinträge vorhanden, und dann wird grad mal die Hälfte davon genutzt! Die Artikel sind dadurch eine unnötig lange Wurst, Lädt nicht unbedingt zum lesen ein. Das liegt am CSS des Themes, und zwar genauer an folgendem Eintrag in der style.css :

.site-content .entry-header,
.site-content .entry-content,
.site-content .entry-summary,
.site-content .entry-meta,
.page-content {
    max-width: 474px;
}

Es sind noch andere CSS Styles an diesem Eintrag dran, ich habe sie der Übersicht halber aber nicht angeführt. Nun wäre das CSS schnell angepasst, aber mit dem nächsten Update könnte unsere Anpassung wieder dahin sein. Daher habe ich eines der vorhandenen Plugins zur Ergänzung des CSS Styles für Themes gewählt. Zur Auswahl standen

  • Custom CSS Manager, welches zum Zeitpunkt des Blogeintrags in der Version 1.5.2 vorhanden war. Und
  • Theme Junkie Custom CSS, gerade frisch in Version 0.1.1 veröffentlicht.

Beide liefen ohne Probleme unter der aktuellen WordPress-Version 3.9.2. Ich habe mich schlussendlich für Theme Junkie Custom CSS ob der Vorschaufunktion bei der Anpassung des Themes entschieden. Ein kurzes Snippet wie das dieses

.site-content .entry-header,
.site-content .entry-content,
.site-content .entry-summary,
.site-content .entry-meta, .page-content {
    max-width: 80%;
}

reicht aus und schon gehört die Platzverschwendung der Vergangenheit an.

Greets

Ähnliche Artikel:

Git Kommandos bei Trunk-Based Development


Git ist mächtig, Git ist vielseitig. Daher reicht es oft nicht aus, einfach drauf los zu committen und irgendwas daraus dann zu veröffentlichen. Um einen absoluten Top-Film zu zitieren (Tremors) “Du musst anfangen im Voraus zu planen!”

Daher sollte ein Branching Model gewählt werden, dass mit der Produktumwelt am besten harmoniert. Ein paar Vorschläge dazu liefert Paul Hammant.

Da neulich ein Freund mit genau der selben Frage für ein neues Projekt auf mich zukam, habe ich ihn an Hammants Artikel verwiesen. Für ihn, und seine Projektsituation passend, wählte er als (noch) Einzelentwickler das Trunk-Based Development Modell. Als Unterstützung bot ich ihm an, die gängigsten Aktionen die hierfür in Git notwendig sind aufzuzeigen.

Trunk-Based Development

Dazu vorab nochmal kurz das Modell erklärt:

trunkbased

Alle Teammitglieder (oder, im Falle meines Freundes, nur eine Person) arbeiten am Trunk. Ein Branch wird nur für ein Major-Release angelegt. Fehlerbehebungen werden per Cherry-Pick aus dem Entwicklungsbranch gezogen. Minor-Releases kennzeichnen weitere Auslieferungen auf den Release-Branches.

Mögliche Aktionen in diesem Modell können nun sein:

  • Neues Projekt anlegen
  • Alltägliches Commit
  • Dem Remote-Branch hinzufügen
  • Major-Release & Tagging
  • Branchwechsel
  • Hotfix in einem Release
  • Minor-Release
  • Neues Major-Release
  • Branch umbenennen
  • Alle Änderungen in das letzte Release nachziehen
  • Änderungen an einem alten Release mit nicht committeten Dateien im Trunk

Zur besseren Darstellung habe ich das ganze in einem Github Repository zur Visuellen Unterstützung veröffentlicht.

https://github.com/tpummer/trunk-based-dev

Neues Projekt anlegen

Das sollte ja eigentlich die einfachste Übung sein. Trotzdem möchte ich es der Vollständigkeit halber anführen:

$ git init
 Initialized empty Git repository in /your/path/to/project/trunk-based-dev/.git/
$ git remote add origin git@github.com/tpummer/trunk-based-dev.git
$ touch README.md
$ git add .
$ git commit -m "First commit"
 [master (root-commit) ed857cc] First commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README.md
$ git push -u origin master
 Counting objects: 3, done.
 Writing objects: 100% (3/3), 217 bytes | 0 bytes/s, done.
 Total 3 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new branch]      master -> master
 Branch master set up to track remote branch master from origin.

 

Alltägliches Commit

Wie gerade eben bei der Anlage des initialen Projekts gesehen ist ein normaler Commit keine Hexerei. Mittels

$ git status

kann man sich eine Auflistung der geänderten Dateien anzeigen lassen, welche man einzeln

$ git add README.md

oder einfach alle

$ git add .

dem nächsten Commit hinzufügen kann. Hat man dann alle Dateien für einen Commit gewählt, checkt man diese in die lokale Versionsverwaltung ein.

$ git commit –m “<your commit message>”

Das Ergebnis kann mittels

$ git log

verifiziert werden.

Dem Remote-Branch hinzufügen

Will man dies dann auch noch auf den Remote Branch publishen greift man zu:

$ git push

Da wir vorher einen Upstream für den master Branch festgelegt hatten müssen wir hierbei keine genaueren Pfade angeben. Dies erfolgte mittels

$ git push -u origin master

Major-Release & Tagging

Hurra es ist soweit, wir wollen unser erstes Release an den Mann bringen!

Also müssen wir als Erstes einen Branch anlegen.

$ git checkout –b release1.0.x
 Switched to a new branch 'release1.0.x'

Da aber zukünftig weitere Minor-Releases erfolgen könnten wollen wir jedes einzelne davon taggen, um es zukünftig bequem wiederzufinden.

$ git tag –a v1.0.0 –m “Release Version 1.0.0”

Dies müssen wir natürlich dem Remote Repository wieder mitteilen. Dazu übertragen wir den neuen Branch mit

$ git push –u origin release1.0.x
 Total 0 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new branch]      release1.0.x -> release1.0.x
 Branch release1.0.x set up to track remote branch release1.0.x from origin.

Und nicht nur der neue Branch gehört bekanntgegeben, die Tags müssen extra transferiert werden! Wieder einzeln:

$ git push origin v1.0.0
 Counting objects: 1, done.
 Writing objects: 100% (1/1), 174 bytes | 0 bytes/s, done.
 Total 1 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new tag]         v1.0.0 -> v1.0.0

oder alle auf einmal

$ git push origin --tag
 Counting objects: 1, done.
 Writing objects: 100% (1/1), 174 bytes | 0 bytes/s, done.
 Total 1 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new tag]         v1.0.0 -> v1.0.0

Branchwechsel

Wir wechseln zum Abschluss noch in unseren Entwicklungsbranch um weiterarbeiten zu können:

$git checkout master

Wo wir uns befinden können wir jederzeit mittels

$ git branch
 * master
 release1.0.x

nachsehen. Das Sternchen markiert unseren Standort.

Hotfix in einem Release

Oh nein, es hat sich ein Fehler eingeschlichen! Korrigiert ist er ja flott, aber wie kriege ich das in das Release rüber? Nachdem der Fehler im master Branch ausgebessert und committet wurde, wollen wir nur einen speziellen Commit auch in den Release Branch übertragen.

Dazu sehen wir uns zuerst die Commit ID an

$ git log
 commit 026ec4023e9e55f290b12711918c7c512fb38350
 Author: Thomas Pummer dev@example.org
 Date:   Mon Apr 28 15:08:38 2014 +0200
Fuer Release 1.0.x
commit b5f3a9aeb48fcb0cbaef286d7569f28829192041
 Author: Thomas Pummer dev@example.org
 Date:   Mon Apr 28 15:08:25 2014 +0200
Nicht fuer Release 1.0.x

 

Wir sind also mit commit 026ec4023e an der richtigen Adresse.  Als Nächstes wechseln wir nun in den Release Branch.

$ git checkout release1.0.x
 Switched to branch 'release1.0.x'

Dannach holen wir uns mittels Cherry-Pick genau dieses eine Commit und stecken es abschließend auch in unseren Remote Branch.

$ git cherry-pick 026ec4023e
 [release1.0.x 189c204] Fuer Release 1.0.x
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
 Counting objects: 5, done.
 Delta compression using up to 4 threads.
 Compressing objects: 100% (2/2), done.
 Writing objects: 100% (3/3), 307 bytes | 0 bytes/s, done.
 Total 3 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 0a648f8..189c204  release1.0.x -> release1.0.x

 

Mittels git log sehen wir, dass der Commit eine neue Commit ID bekommen hat. Um zu identifizieren woher der Commit kam, muss man mit –x einen zusätzlichen Parameter nutzen.

$ git cherry-pick -x c9ed634
 [release1.0.x bd1bee9] cherry with x
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log
 commit bd1bee920ebee7a0ada2e5723086e8178e15d3c0
 Author: Thomas Pummer dev@example,org
 Date:   Mon Apr 28 15:16:55 2014 +0200
cherry with x
(cherry picked from commit c9ed634612f9f5f31db666d348b88a1563aa31fe)

Minor-Release

Wollen wir nun erneut veröffentlichen funktioniert es wie vorher beim Major Release: In den Branch wechseln

$ git checkout release1.0.x
 Switched to branch 'release1.0.x'

und taggen

$ git tag -a v1.0.1 -m "Release Version 1.0.1"

Auf das Hochladen in das Remote Repository nicht vergessen!

$ git push --tags
 Counting objects: 1, done.
 Writing objects: 100% (1/1), 171 bytes | 0 bytes/s, done.
 Total 1 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new tag]         v1.0.1 -> v1.0.1

Minor-Release auschecken

Was müssen wir also tun um an unser Release ranzukommen? Als erstes das Repository klonen:

$ git clone git@github.com:tpummer/trunk-based-dev.git
 Cloning into 'trunk-based-dev'...
 remote: Reusing existing pack: 28, done.
 remote: Total 28 (delta 0), reused 0 (delta 0)
 Receiving objects: 100% (28/28), done.
 Resolving deltas: 100% (1/1), done.

Dannach checken wir direkt den Tag aus:

$ git checkout v1.0.1
 Note: checking out 'v1.0.1'.
You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
 state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
 do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at bd1bee9... cherry with x

Jetzt haben wir direkt den Stand eines spezifischen getaggten Releases. Wie der Message zu entnehmen ist dies jedoch kein geeigneter Punkt weiterzuarbeiten.  Daher sollte entweder einer der Release-Branches ausgecheckt werden oder wie oben beschrieben ein neuer Branch eröffnet werden. Allerdings würde ein neuer Branch  nicht in unser Trunk-Based Modell passen.

Neues Major-Release

Haben wir nun am master Branch soweit weiterentwickelt dass wir eine neue Major Version herausbringen wollen gehen wir wie bei der ersten Version vor:

$ git checkout -b release1.1.0
 Switched to a new branch 'release1.1.0'
$ git tag -a v1.1.0 -m "Release Version 1.1.0"
$ git push -u origin release1.1.0
 Total 0 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new branch]      release1.1.0 -> release1.1.0
 Branch release1.1.0 set up to track remote branch release1.1.0 from origin.
$ git push origin v1.1.0
 Counting objects: 1, done.
 Writing objects: 100% (1/1), 174 bytes | 0 bytes/s, done.
 Total 1 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new tag]         v1.1.0 -> v1.1.0

Branch umbenennen

Oh nein der falsche Branch Name! Ist es aufgefallen? Wir wollen ja mehrere Minor Releases in einen Branch unterbringen, weshalb eine Bezeichnung mit einer Wildcard besser gewesen wäre. Also statt release1.1.0 lieber release1.1.x

Nun Lokal geht das ja flott

$ git branch -m release1.1.0 release1.1.x
$ git branch
 master
 release1.0.x
 * release1.1.x

Remote ist die Lösung schon aufwendiger, da dies ein externer Branch ist. Hier hilft nur vorheriges löschen und erneutes pushen

$ git push origin :release1.1.0
 To git@github.com:tpummer/trunk-based-dev.git
 - [deleted]         release1.1.0
$ git push -u origin release1.1.x
 Total 0 (delta 0), reused 0 (delta 0)
 To git@github.com:tpummer/trunk-based-dev.git
 * [new branch]      release1.1.x -> release1.1.x
 Branch release1.1.x set up to track remote branch release1.1.x from origin.

Alle Änderungen in das letzte Release nachziehen

Sind alle Änderungen am master Branch auch für das letzte Release vorgesehen, muss man nicht per Cherry-Pick vorgehen. Man kann den gesamten Branch mergen. Dazu wechselt man zuerst in den Ziel-Branch und holt dann mittels merge Kommando alle Änderungen.

$ git checkout release1.1.x
 Switched to branch 'release1.1.x'
$ git merge master
 Updating 4131cac..5687e2c
 Fast-forward
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

Änderungen an einem alten Release mit nicht committeten Dateien im Trunk

Manchmal hat man Datein bereits einem zukünftigen Commit hinzugefügt, dieses aber noch nicht eingecheckt. Möchte man nun den Branch wechseln kommt es zum Fehler, oder die Änderung wird mitgenommen. Zur Vermeidung dessen gibt es ein paar Optionen.

Die simpelste, und am wenigsten Git Magic-lastige, wäre ein Checkout in einem neuen Verzeichnis.

Alternativ kann man die Änderungen auch einfach wegschmeißen

$ git reset –-hard HEAD

Variante Nummer drei  ist der git stash befehl. Damit bunkert man die noch nicht versionierten Änderungen für später und kann eine andere Änderung vorziehen.

Eine mögliche Ausgangslage dafür wären die zwei Datein switch und new:

$ git status
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
 #
 #       new file:   switch
 #
 # Untracked files:
 #   (use "git add <file>..." to include in what will be committed)
 #
 #       new

Wenn wir nun git stash aufrufen kümmert sich git nur um für ein commit vorgesehene Dateien:

$ git stash
 Saved working directory and index state WIP on master: 5687e2c ready for merge
 HEAD is now at 5687e2c ready for merge
$ git status
 # On branch master
 # Untracked files:
 #   (use "git add <file>..." to include in what will be committed)
 #
 #       new
 nothing added to commit but untracked files present (use "git add" to track)

 

Wollen wir die Datei new also auch stashen müssen wir sie zuerst unter Versionskontrolle stellen:

$ git add .

Dannach können wir auch diese Datei stashen

$ git stash
 Saved working directory and index state WIP on master: 5687e2c ready for merge
 HEAD is now at 5687e2c ready for merge

Wir haben nun 2 stash Objekte bereitliegen, um nach unserer Änderung wieder hervorgeholt zu werden

$ git stash list
 stash@{0}: WIP on master: 5687e2c ready for merge
 stash@{1}: WIP on master: 5687e2c ready for merge
$ git stash apply
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
 #
 #       new file:   new
 #

 

Ein simples apply holt nur das oberste Element aus dem Stack hervor. Da wir jedoch 2 mal Datein gestashed haben müssen wir diese direkt ansprechen.

$ git stash list
 stash@{0}: WIP on master: 5687e2c ready for merge
 stash@{1}: WIP on master: 5687e2c ready for merge
$ git stash apply stash@{0}
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
 #
 #       new file:   new
 #
$ git stash apply stash@{1}
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
 #
 #       new file:   new
 #       new file:   switch
 #

 

Wie zu sehen sind die gebunkerten Änderungen durch ein apply nicht aus der stashing Liste entfernt. Das kann man mit

$ git stash drop

für jeweils wiederum das stash Objekt mit der höchsten Nummer nachholen. Oder man entfernt es direkt mittels der Referen

$ git stash drop stash@{0}

Natürlich hat Stashing eine gewisse Komplexität, und je nach bereits vollzogenen Änderungen will man auch nicht alles Wegschmeißen, weshalb ich im Bedarfsfalle zu erneutem Auschecken (wenn kein Netzwerk dann eben von der lokalen Kopie) tendieren würde. Auch wenn dies eher als Workaround anzusehen ist um Git etwas Komplexität zu nehmen.

Fehlt euch etwas? Dann bitte ab in die Comments, ich werde versuchen es zu ergänzen.
Viel Erfolg mit Git!

Ähnliche Artikel:

Entwicklung, Fotografie und mehr