Let’s Encrypt und Puppet

lets-encrypt-logoIch habe früher fast alle meine Zertifikate von CAcert bezogen. Ich habe sie regelmäßig aktualisiert und für das deployen habe ich ein Puppet Modul, dass die Zertifikate verteilt.

Nach und nach bin ich auf Let’s Encrypt Zertifikate umgestiegen. Da ein hoher Grad an Automatisierung bei Let’s Encrypt immer wieder propagiert wird dachte ich mir, dass sollte auch mit Puppet dann kein zu großes Problem sein. Hier die Lösung, die bei mir nun in Betrieb ist:

Als erstes habe ich das Puppet Modul bzed/letsencrypt installiert. Das Modul benötigt eine PuppetDB. Über exported resources  transportiert es die CSRs auf den puppetmaster, macht dort die gesamte Abwicklung und transferiert dann die Ergebnisse wieder zurück auf den Node. Das funktioniert auch sehr gut.

Auf dem Puppetmaster habe ich die Klasse eingebunden und einige wenige Einstellungen gesetzt (hiera). Bei dem Hook handelt sich m übrigen um einen letsencrypt.sh Hook:

---
classes:
  - letsencrypt

letsencrypt::challengetype: 'http-01'
letsencrypt::hook_source: 'puppet:///modules/helper/%{::fqdn}/le_hook.sh'

Auf den Nodes habe ich die Klasse eingebunden und die Domains definiert für die ein Zertifikat bezogen werden soll:

---
classes:
- letsencrypt

letsencrypt::domains:
- 'foo.example.net'
- 'bar.example.net'
- 'baz.example.net'

PL_logo_vertical_RGB_lgIn meinem recht simplen Szenario ist es so, dass ich einen Gate-Server habe, hinter dem sich alle anderen Maschinen „verstecken“. Dort läuft ein Apache Server als Proxy, der Anfragen über HTTP nach hinten weiterreicht. Die Konfiguration von Apache erfolgt ebenfalls über Puppet. Eine Authentifizierung über DNS für ACME klappt bei mir leider nicht, weswegen ich HTTP nehmen muss. Das habe ich so gelöst, dass ich für die Apache vhosts einfach über ProxyPassMatch den .well-known/acme-challenge/ auf einen Apache Vhost auf dem Puppetmaster weiterreiche. Auf dem Gate-Server sieht das für einen Vhost wie folgt aus:

apache::vhost:
  'proxy-foo.example.net':
    servername: 'foo.example.net'
    serveradmin: 'webmaster@example.net'
    port: '80'
    docroot: '/var/www/empty'
    proxy_dest: 'http://10.20.30.1'
    proxy_pass_match:
      -
        path: '^/.well-known/acme-challenge/(.*)$'
        url: 'http://10.20.30.2/$1'
        params:
          retry: '0'
    headers:
      - 'unset X-Powered-By'
    proxy_preserve_host: true

Auf dem Puppetmaster wiederum läuft ein Vhost, der einfach alle Domains als ServerAlias eingetragen hat.

Nun kommt noch der Hook für letsencrypt.sh ins Spiel den ich oben bereits erwähnt habe. Das Skript schreibt die ACME-Challenge in eine Textdatei in den DocRoot und löscht sie nach dem Erfolg wieder:

#!/bin/bash
 
#
# http-01 hook
#
 
CHALLENGEDIR="/var/www/example.net/letsencrypt"
 
done="no"
if [[ "$1" = "deploy_challenge" ]]; then
    echo "${4}" > "${CHALLENGEDIR}/${3}"
    chmod 644 "${CHALLENGEDIR}/${3}"
    done="yes"
fi
 
if [[ "$1" = "clean_challenge" ]]; then
    rm "${CHALLENGEDIR}/${3}"
    done="yes"
fi
 
if [[ "${1}" = "deploy_cert" ]]; then
    # do nothing for now
    done="yes"
fi
 
if [[ ! "${done}" = "yes" ]]; then
    echo Unkown hook "${1}"
    exit 1
fi
 
exit 0

Voila! Es braucht ein paar Durchläufe bis alles über die Puppetdb jeweils transportiert wurde, aber alles läuft vollautomatisch ab. Sehr cool!

Wartungsseite für alle Apache vhosts eines Webservers realisieren

Über eine Lösung um einen einzelnen Apache Vhost mit einer Wartungsseite auszustatten habe ich bereits vor einigen Jahren hier geschrieben.

Jetzt stand ich vor der Aufgabe wegen Wartungsarbeiten auf meinem Webserver eine Wartungsseite für alle dort gehosteten Apache Vhosts zu schalten. Davor sitzt ein Gate-Server mit Apache und mod_proxy. Eine Lösung das zu realisieren wäre auf dem Gate-Server mit mod_rewrite nach dem Vorhandensein einer Maintenance-Datei zu schauen und wenn die Bedingung zutrifft alle Anfragen auf die Wartungsseite weiterzuleiten. Eine solche Lösung ist in Ansätzen unter anderem hier skizziert. Unschön finde ich dabei die unnötig vielen Prüfungen nach der Maintenance-Datei.

Ich habe nun eine andere Lösung realisiert, die in meinen Augen deutlich einfacher und eleganter ist. Auf dem Gate-Server lasse ich einen Apache-Vhost auf einem gesonderten Port laufen auf dem die Wartungsseite angezeigt wird. Für den Fall, dass ich die Wartungsseite vorschalten möchte, leite ich einfach allen HTTP und HTTPS Traffic auf die IP des Gate-Servers und den gesonderten Port um. Mit nur einer iptables Regel ist somit für alle vhosts auf dem Webserver eine Wartungsseite vorgeschaltet:

iptables -t nat -A OUTPUT -p tcp -d INTERNALWEBSERVERIP --match multiport --dports 80,443 -j DNAT --to-destination EXTERNALGATESERVERIP:PORT

Bei dem Apache Vhost auf dem Gateserver ist noch zu beachten, dass man nach Möglichkeit den aufrufenden Browsern / Bots etc. mitteilt, dass es sich um etwas temporäres handelt. Aus diesem Grund habe ich in dem Vhost zwei Dinge beachtet:

  1. Es wird ein HTTP Status Code 503 (Temporarily Unavailable) gesendet
  2. Es wird der HTTP Header Retry-After gesetzt

Im vhost sieht das wie folgt aus. Spannend sind die letzten 3 Zeilen. Meine Wartungsseite heißt übrigens auch 503.html

<VirtualHost *:PORT>
  ServerName maintenance.example.net
  ServerAdmin webmaster@example.net
 
  DocumentRoot "/var/www/maintenance"
 
  <Directory "/var/www/maintenance">
    Options -Indexes
    AllowOverride None
    Require all granted
  </Directory>
 
 
  ErrorLog "/var/log/apache2/maintenance.example.net_error.log"
  CustomLog "/var/log/apache2/maintenance.example.net_access.log" combined 
 
  RedirectMatch 503  ^/(?!503.html)  
  ErrorDocument 503 /503.html
  Header always set Retry-After "18000"
</VirtualHost>

Apache: OCSP Stapling aktivieren

Ab Apache Version 2.4 (in Debian Jessie enthalten) wird OCSP Stapling unterstützt. OCSP steht für Online Certificate Status Protocol. Damit kann die Gültigkeit eines Zertifikates abgefragt werden. Das ganze sieht in der Praxis dann so aus, dass wenn ein Nutzer eine Webseite über HTTPS aufruft, der Webbrowser dann eine im Zertifikat enthaltene OCSP Responder Adresse abfragt um festzustellen, ob das Zertifikat noch gültig ist. Das ist natürlich für die Sicherheit gut, hat aber zwei Nachteile:

  • Die OCSP Responder Server werden beim Verbindungsaufbau zusätzlich abgefragt, das führt je nach Auslastung und Verfügbarkeit des Responders zu einer Verzögerung
  • Der OCSP Responder Server bekommt mit, welche Webseite von welcher IP-Adresse aufgerufen wird

Ob die OCSP Responder letzteres nun loggen oder nicht ist nicht immer eindeutig zu klären, deswegen gehe ich davon aus, dass das im Zweifel so ist.

Abhilfe bringt OCSP Stapling. Dabei fragt der Webserver selbst regelmäßig den OCSP Responder für seine Zertifikate ab und behält diese Information in einem Zwischenspeicher. Beim Verbindungsaufbau sendet er dann die OCSP Antwort gleich mit. Da es sich hierbei um eine komplett signierte Kommunikation handelt, vom OCSP Responder über den Webserver bis hin zum Browser und der Browser die Signatur überprüft ist die Antwort verifiziert und der Browser muss keine Verbindung mehr zum OCSP Responder aufnehmen.

In Apache ist das ganze recht einfach eingerichtet. Ich habe einfach unter /etc/apache2/conf.d/ eine Datei OCSP_Stapling.conf angelegt mit dem folgenden Inhalt:

SSLUseStapling on
SSLStaplingCache 'shmcb:/tmp/stapling-cache(102400)'
SSLStaplingReturnResponderErrors off

Dabei ist wichtig, dass der SSLStaplingCache außerhalb eines vhosts definiert wird. Prüfen kann man das ganze dann bei dem von mir gern genommenen SSL Server Test von Qualys. Da sieht das dann bei Erfolg so aus:

screenshot-www ssllabs com 2015-08-30 14-35-20

Weitere Informationen zu dem Thema:

SSL: Zertifikate auf sha2 Umstellen…

Key und CSR neu erzeugen. Das -sha256 ist das entscheidende:

openssl req -new -nodes -newkey rsa:2048 -sha256 -keyout sub.example.org.sha2.key -out sub.example.org.sha2.csr

Wenn das Zertifikat im Apache eingebunden ist auch gleich prüfen ob SSLProtocol und SSLCipherSuite aktuell ist. Aktuelle Empfehlung von BetterCrypto.org lautet:

SSLProtocol All -SSLv2 -SSLv3
 
SSLCipherSuite 'EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:
	EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:
	+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:
	!ECDSA:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA'

Ob alles gut ist, vor allem auch mit der Chain kann man dann sehr gut beim Qualys SSL Labs Server Test sehen.

An dieser Stelle möchte ich auch einmal auf den sehr guten Service von domaindiscount24 hinweisen. Ich habe meine Zertifikate (sofern nicht von CAcert) dort gekauft. Im Backend gab es bei Zertifikaten die vor der Heartbleed-Lücke gekauft wurden dann eine neue Spalte, in der man diese kostenlos erneuern konnte. Bei SHA1 vs. SHA2 ist nun das gleiche, man kann bei den Zertifikaten kostenlos dazwischen hin und her wechseln. Klasse!

dd24-Zertifikate