Next Level (Step 2: Renewal á la Cron)

Seit Dezember ist nun ganz offiziell die Public Beta von Let’s Encrypt verfügbar. Die ersten Zertifikate laufen nun innerhalb der nächsten 2 Wochen aus, also auch bei mir (ich hatte ja auch Ende November bereits alles auf SSL-only umgestellt).

Mit einem Blick in den Kalender wurde es daher auch mal zeitig, den automatischen Renewalprozess ans Laufen zu kriegen, denn die Zertifikate sind ja bekanntlich immer nur 90 Tage gültig. Manuelles Eingreifen als Dauerlösung scheidet aus. Ich bin ja Entwickler, da ist jede Wiederholung eines Arbeitsschrittes schon eine zu viel.

Das Szenario

Ein Multi-Site-Konfiguration verschiedener Domains für einen Nginx, jede Domain hat ihre eigenes Zertifikat und Private Key. Diese Paare müssen jeweils validiert und ggf. erneuert werden.

Für den initialen Einrichtung hatte ich auch den offiziellen Client verwendet. Aber erstens ist dieser zu mächtig mit vielen Abhängigkeiten, denn ich will nur (neue) Zertifikate und keine Modifizierung irgendwelcher Konfigurationsdateien. Das Zweite wiegt noch schwerer: Dieser funktioniert (aktuell) nur als root vernünftig, was unter anderem auch daran liegt, dass er kurzfristig Port 80 nutzen muss. Das ist aber natürlich mit einem laufenden Webserver „unhandlich“.

Aber es gibt Alternativen: Und mal gar nicht so wenige. Prinzipiell sind Ideen wie der Caddy Server (ein „Webserver“ mit eingebautem ACME-Client) interessant, aber nicht für diese Situation angebracht. Ein ander Mal vielleicht…

Exkurs: Der gesamte Prozess der Verifizierung der Domain geschieht über das Protokoll „Automated Certificate Management Environment“ oder kurz ACME. Das wurde ursprünglich von Let’s Encrypt entwickelt und aktuell ist es als IETF Protokoll im Gespräch. Das Protokoll beschreibt, wie Clients (bspw. für Webserver) ein Zertifikat anfragen und bestätigen können. Daher kann prinzipiell jeder einen solchen Client (und natürlich auch Server!) bauen; fertige Plugins „out-of-the-box“ für Webserver sind also auch bald möglich — Caddy macht es sogar schon vor.

Meine Wahl fällt auf letsencrypt.sh von Lukas Schauer: ein kleines und überschaubares Bashscript, welches sich ebenfalls einfach konfigurieren lässt. Da ich ja bereits „Bestandsdaten“ hatte, wollte ich natürlich gerne die vorhandenen Zertifikate und Schlüssel „importieren“. Netterweise hat Lukas dafür schon etwas vorbereitet. 😎

Schritt-für-Schritt vom Import bis zur Cron:

  • Annahme: www-data ist die Gruppe des Webserver-Users.
  1. Wir legen einen neuen dedizierten UNIX-Account an, nennen wir ihn doch bill (Name d. Red. bekannt). Denn Bill is smart!
    1. Nicht vergessen: bill darf sich nicht einloggen können; am Besten kein Passwort und keinen Public Key hinterlegen.
    2. bill wird Mitglied der Gruppe www-data (als Default-Gruppe): usermod -g www-data bill
  2. Das Git-Repository klonen wir uns einfach in sein Home-Verzeichnis: git clone https://github.com/lukas2511/letsencrypt.sh.git
    1. In das Verzeichnis wechseln.
    2. Testweise ./letsencrypt.sh -e starten. Sollte kein Fehler kommen, erstmal alles gut. Ansonsten dafür sorgen, dass openssl und curl installiert sind.
  3. Wir legen das Verzeichnis /etc/letsencrypt.sh an und passen die Rechte so an, dass nur Mitglieder der Gruppe www-data dort lesen und bill zusätzlich schreiben darf: chown bill:www-data /etc/letsencrypt.sh && chmod 750 /etc/letsencrypt.sh
  4. Vorbereitungen
    1. In bill wechseln… su – bill
    2. … und in das eben erstellte Verzeichnis /etc/letsencrypt.sh wechseln.
    3. Eine leere domains.txt erstellen (siehe domains.txt.example)
    4. Jeweils die config.sh.example und hooks.sh.example aus dem Repository kopieren und ablegen, dabei den Suffix .example entfernen.
    5. In der nun frischen config.sh das BASE_DIR setzen: BASEDIR=/etc/letsencrypt.sh
    6. Einen symbolischen Link auf /var/www/letsencrypt erstellen: ln -s /etc/letsencrypt.sh/.acme-challenges /var/www/letsencrypt
    7. Das eben erwähnte Linkziel anlegen: mkdir /var/www/letsencrypt
    8. Aus Sicherheitsgründen sollten die Dateien und Verzeichnisse nur für bill zu schreiben sein.
  5. Letsencrypt.sh-Konfiguration
    1. Falls schon mit Hilfe des offiziellen Client Zertifikate installiert wurden:
      1. Mit dem Importscript (siehe hier) werden alle Zertifikate ermittelt und sauber in /etc/letsencrypt.sh/certs/$DOMAIN geschrieben.
      2. Einen eventuell vorhandenen Account Key kann man mit dem zweiten Importscript auch ermitteln; falls vorhanden einfach unter /etc/letsencrypt.sh/private_key.pem ablegen.
    2. Falls noch keine Zertifikate angelegt wurden, dann muss man die Datei domains.txt selber editieren — oder auch nachträglich korrigieren.
  6. Nginx-Konfiguration
    1. In Punkt 4 wurde bereits das Verzeichnis /var/www/letsencrypt für die Response-Challenges angelegt. Da das Protokoll vorsieht, das der Webserver unter Port 80 (!) und dem Pfad /.well-known/acme-challenge die Antworten liefern kann, muss jeweils bei jeder Seite eine kleine Ergänzung eingefügt werden. Relativ am Anfang innerhalb der HTTP-Direktive sollte daher ein Verweis auf eben genanntes Verzeichnis stehen. Im Folgenden ein Beispiel zusammen mit HTTPS-Always: .
      Es muss sicher gestellt werden, dass für alle(!) Domains (inkl. Subdomains) das Verzeichnis über HTTP erreichbar ist. Am Besten eine Datei mit „Hello World“ ablegen und die Erreichbarkeit versuchen. Idealerweise mit curl, denn der Browser forciert u.U. die HTTPS-Erreichbarkeit (etwa durch HSTS oder Plugins).
    2. Und schließlich muss der Pfad zum Zertifikat und zum Schlüssel umgebogen werden.
      1. Entweder: In der Nginx-Site-Konfiguration wird auf den neuen Pfad gezeigt: /etc/letsencrypt.sh/certs/$DOMAIN/fullchain.pem und ~privkey.pem
      2. Oder: Man kopiert sich die Dateien im Hook (siehe unten).
  7. Hooks
    1. Letsencrypt.sh bietet aktuell drei Hooks an, davon ist interessant: deploy_cert (nachdem ein neues Zertifikat erstellt wurde)
      1. Falls man das Zertifikat erst noch an die richtige Stelle kopieren will (als bill).
      2. Es bietet sich an, den Webserver neu zu starten: sudo service nginx reload
  8. Cron finally
    1. Anschließend runden wir das Ganze noch ab: Wir richten eine wöchentliche Cron auf, der letsencrypt.sh aufruft und die Renewals automatisch anwirft. Beispiel: 0 0 * * Sat (cd /home/bill/letsencrypt.sh; ./letsencrypt.sh -c > /home/bill/last_renew_check.log) für einen wöchentlichen Check am Samstag.

Zusammenfassung

Mit der Anleitung haben wir nun einen wöchentlichen Cronjob, der jeweils prüft, ob die konfigurierten Domains ein Zertifikat haben und ob dieses noch gültig ist. Dabei wird bereits im Voraus ein neues generiert. Die Verifikation geschieht ganz regulär über ACME, der Webserver liefert die entsprechenden Daten im laufenden Betrieb.

letsencrypt.sh liegt im Homeverzeichnis eines dedizierten Benutzers ($HOME/letsencrypt.sh), die Konfiguration unter /etc/letsencrypt.sh.

Die Zertifikate und Schlüssel liegen in /etc/letsencrypt.sh/certs (BASE_DIR).

Alle Pfade lassen sich natürlich ändern, dafür einfach in der Dokumentation nachschauen.

Anmerkungen

  • Man kann auch ein anderes BASE_DIR (anstatt /etc/letsencrypt.sh verwenden; die config.sh muss  weiterhin unter /etc/letsencrypt.sh (oder lokal) vorhanden sein.
  • bill is smart und darf deswegen natürlich keinen Service einfach neu starten. Das lässt sich mit sudoers lösen: bill ALL = NOPASSWD: /usr/sbin/service nginx *

Next Level

So. Dieser Blog ist wieder über HTTPS erreichbar, und bis Ende des Monats auch testweise über http2.

(Der Blog war einige Monate nicht über HTTPS erreichbar war, weil ich bei der Erstellung der (kostenlosen verfügbaren) Zertifikate einen kleinen Fehler gemacht habe. Merke: StartSSL erlaubt nur ein gültiges kostenloses Zertifikat je Domain, es sei denn natürlich, man revoked dieses gegen eine Bearbeitungsgebühr. Und als ich das generierte Zertifikat nicht ordentlich gespeichert hatte, war es weg. Dummer Fehler.)

Nun denn. Das Zertifikat ist von Let’s Encrypt und kann in allen Browsern als gültig verifiziert werden. Ich hatte mich (bzw. diese Domain) für den geschlossen Betatest beworben, aber ab nächsten Monat kann jeder mitmachen. Die Einrichtung ist sehr einfach, ich empfehle als Lektüre dazu die offizielle Dokumentation. Ich würde auch die Einstellung certonly empfehlen, siehe auch dieser Artikel.

Außerdem hat der verwendete Nginx jetzt auch http2 aktiv. Allerdings muss ich das wohl bis Anfang nächsten Monat wieder deaktivieren. Aber bald.. dann wirklich.

Update 19.11.2015: Google überdenkt die Abschaltung von NPN. Danke an @mattiasgeniar

Broken Java Key&Trust Store on OSX

Sometimes Java applications do not find the internal Key- and Truststore (where all well known SSL roots are listed). Last one was Minecraft on OS X 10.9 with installed JRE6, JDK7 and JDK8. Even a pre-defined $JAVA_HOME did not help.

javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

Hopefully, this fix should help you. 

# Sometimes some Java applications will not work because the internal references to the trusted libs are broken.
# Unless '/System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security' does not exist, this should help
$ sudo mkdir -p /System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security
$ cd /System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security
$ sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/jre/lib/security/cacerts
$ sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/jre/lib/security/blacklist
$ sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/jre/lib/security/trusted.libraries

Consume a remote Java method with AMQP using Spring Integration

After the unexpected interest about exposing a method (thank you Josh mentioning it in the weekly news) , I will go ahead. Let’s consume such a service we had exposed.

Remember: We had a service method which is integrated via an AMQP queue (in my case RabbitMQ, but you can change this into whatever you want). That means the consumer only have to connect itself against this queue and have to send and receive suitable objects in JSON.

Consuming a method with AMQP using Spring Integration

We use the same example like in „Expose a Java method with AMQP using Spring Integration“, but from the consumer perspective.

We will put the cart before the horse and start with the AMQP setup. Instead of the inbound-gateway we have to use the outbound-gateway. Well, the name  is self-explanatory. An outbound gateway integrates a channel (requestChannel) with an external service (here AMQP). And because it is a gateway, it will handle the reply message as well (put into resultChannel).

Now we could bind the channels with some Spring Integration? Well, we must not forget the messages are transported in JSON. Said this, we have to ensure the message are transformed from and to JSON correctly before connecting them with the rest.

Both transformers intercept the messages. The result are two channels (requestChannel and resultChannel) which are for the service; the other two channels are only for serializing and deserializing. Okay, let’s go.

and

That’s it.

Wait, what?! Yep.

We have already both channels (one for outgoing invocations, one for the return values). The rest is more or less boiler code, so you can use one of the built-in gateway proxies provided by Spring Integration. The Spring Integration <int:gateway> builds a proxy object for the given interface and do the „magic“ integration with the Spring Integration message flow. including waiting for the reply. You should have remember the @Payload annotation which is the same as on receiver side.

For each „call“ onto MyGateway.handle(Request):

  1. the proxy creates a new Spring Integration message containing the object „request“ as payload and put it into the channel „requestChannel“ defined in the configuration;
  2. the message will be transformed into JSON, and will be put into requestChannelJson;
  3. the message will be transmitted via AMQP (just another gateway actually) and waiting for a reply;
  4. the reply message will be received and put into resultChannelJson;
  5. the message will be transformed from JSON into a POJO and put into resultChannel;
  6. the message finally receives the waiting gateway and returns the reply like a regular method

Because this can take some time and would block the current thread, you can (better: should) use futures. The gateway proxy will take care of this!

 

 

 

Can you trust your npm dependencies?

Maybe.

OS X 10.9 Mavericks: Kein M2_HOME mehr in IntelliJ IDEA

Nach dem Update auf Mavericks hat sich auch unter der Haube einiges an Libraries getan. So ist nicht nur das System-Ruby auf Version 2 angehoben worden, sondern auch Maven wurde nebst Java jetzt wirklich komplett entfernt.

Damit man in IntelliJ IDEA nicht in jedem Projekt separat die Maven-Konfiguration nachpflegen muss, muss man nun Maven auch selber installieen (musste man vorher auch, wenn man eine andere Version als den Default haben wollte). Leider erhalten GUI-Anwendungen unter OS X nicht einfach alle Umgebungsvariablen (aus dem Enviroment, ~/.bash_profile usw.). Seit 10.8 und 10.9 muss man wohl tatsächlich, will man globale Variablen anlegen, diese im Launch Daemon registrieren. Dies wiederum kann man aber bspw. in seiner ~/.bash_profile machen.

Das sieht bei mir nun wie folgt aus:

export M2_HOME=~/bin/apache-maven-3.0.5
export M2=$M2_HOME/bin
launchctl setenv M2_HOME $M2_HOME
launchctl setenv M2 $M2

Darüber hinaus schadet es auch nicht, eine funktionierende JAVA_HOME Variable anzulegen, weil manche stumpfsinnigen OSX-Anwendungen einfach nicht in der Lage sind ohne auszukommen:

export JAVA_HOME=$(/usr/libexec/java_home)
launchctl setenv JAVA_HOME $JAVA_HOME

Und wenn wir einmal dabei sind, aktualisieren wir noch den Pfad.

launchctl setenv PATH $PATH

Wahlweise kann man hier natürlich auch das $M2 hinzufügen ($PATH:$M2), wenn man den Befehl „mvn“ auch im Pfad haben will.

Optimierte JavaScript Anwendungen mit erweiterten Debugging-Möglichkeiten

Im Firmenblog habe ich einen Artikel unter dem Titel Optimierte JavaScript-Anwendungen mit erweiterten Debugging-Möglichkeiten veröffentlicht.

Es sollte mittlerweile in jeder halbwegs ernsthaften Webanwendung gängig sein, dass das JavaScript nicht as is ausgegeben wird, sondern in einer optimierten Variante. Dabei wird in der Regel auf eine Kombination aus Konkatenation und Minifizierung zurückgegriffen.

In diesem Artikel stelle ich eine Möglichkeit vor, dies in einem Java-Projekt mit Spring einzusetzen. Als Builder wird Maven und als JS-Compiler wird der Google Closure Compiler verwendet. Als Boni werden die Source Maps in der Konbination mit Google Chrome vorgestellt. Grundsätzlich sind einige Werkzeuge zum Teil auch ohne Java oder Spring einsetzbar — Google Closure Compiler ist zwar in Java geschrieben, aber als JAR verfügbar und universell per Executable steuerbar.

Ändern einer IP-Route als User (Mac OS X) // sudoers [Update]

Im Zuge einer Überarbeitung meiner VPN-Firewall-Gateway-VM stolperte ich wieder über den Routing-Activator. Dieses kleine Script macht nichts anderes, als das externe VPN-Netzwerk über eine Gateway-VM zu routen. Da jedoch route zwingend root-Rechte benötigt, musste ich bei jedem VPN-Aufbau (sprich: Starten der VPN-Gateway-VM) oder einem Netzwerk-Neustart ein sudo /path/to/your/script via Terminal abfeuern — inklusive lästiger Passworteingabe.

Okay. Gehen wir das Problem an!

Das eigentliche Problem ist, das route nur als superuser ausgeführt werden darf. Für solche Fälle gibt es die sudoers-Datei, welche in Kurzform beschreibt, welche User welche Befehle als superuser ausführen dürfen. In der Regel landen dort manchmal Services (wenn ein Service bspw. zwar als Superuser gestartet werden muss, aber eigentlich im Kontext eines normalen Benutzers läuft und gesteuert wird) oder bestimmte Systembefehle.

Nun könnte man natürlich route für den eigenen Benutzer komplett freischießen, aber dann gelte dieser Freibrief für alle Prozesse. Nein, das will man auch nicht. Eigentlich soll ja nur die eine Route erlaubt werden. Und da praktischerweise diese Konfiguration bereits als ein Script — /path/to/your/script — vorliegt, wird einfach dieses dort eingetragen. Dabei sind jedoch einige Vorsichtsmaßnahmen zu treffen.

  1. Mit dem Befehl sudo visudo öffnet man eine spezielle Instanz von vi zum Editieren der sudoers.
  2. Ganz am Ende trägt man eine weitere Zeile an (man kann sich an den Beispielen orientieren):

    Damit erlaubt man dem Benutzer USER das Ausführen des definierten Commands ohne Passwortabfrage.
    Sollte das Script natürlich woanders liegen, so muss das angepasst werden (irgendwie klar, oder?) Man kann auch andere Muster (beispielsweise ganze Gruppen) oder andere Modi wählen (siehe dazu man sudoers).
  3. Wichtig: Um jegliches Sicherheitsrisiko zu vermeiden, sollte man unbedingt das Script danach abschotten. Dazu werden die Rechte auf das mindeste entfernt.
    1. sudo chmod -w /path/to/your/script entfernt für alle Benutzer das Schreibrecht (muss man es dennoch editieren, geht es mit sudo vi und einem abschließenden :wq! natürlich dennoch).
    2. sudo chown root /path/to/your/script übertragt das Script dem Root-Benutzer, um eine nachträgliche Rechte-Änderung ohne Passwort zu verhindern.
  4. Fertig. Das Script kann nun mit sudo /path/to/your/script nun mit Rechten des superuser gestartet werden (ohne Passwort) und ist gleichzeitig ohne administrative Rechte nicht mehr änderbar.