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!

Puppet Snippets

  • Wenn man den Katalog von einem Node anschauen möchte, kann man sich diesen auf dem Master mit folgendem Befehl kompilieren:
    puppet master --compile nodename
  • In Hiera kann man Werte wie folgt auslesen:
    hiera -c /etc/puppet/hiera.yaml apt::sources ::fqdn="puppetnode.example.net"

    Weitere Variablen kann man einfach so wie den fqdn auch übergeben:

    hiera -c /etc/puppet/hiera.yaml apt::sources ::fqdn="puppetnode.example.net"  ::provider="foobar"

 

Puppet: SSL Zertifikate neu erzeugen für Master, Agent und PuppetDB

Wenn man „ausversehen“ mal auf dem Puppetmaster Server ein

puppet cert clean --all

ausgeführt hat und dann versucht das ganze Schlamassel wieder auszubaden sind folgende Informationen gut zu wissen:

  • Neue Zertifikate auf den Nodes erzeugt man, in dem man das Verzeichnis /var/lib/puppet/ssl einfach umbenennt und den Agent neu laufen lässt:
    $ mv /var/lib/puppet/ssl /var/lib/puppet/ssl_old
    $ puppet agent --test --server puppet.example.org
  • Wenn man auf dem Puppetmaster ebenfalls ein neues Zertifikat erzeugt und es dann Fehlermeldungen auf den Nodes gibt nach dem Motto „hostname was not match with the server certificate“ helfen folgende Schritte:
    1. Auf dem Puppetmaster Server den Certificate Hostname anzeigen:
      $ puppet master --configprint certname
    2. Puppetmaster ausstellen und altes Zertifikat löschen:
      $ service puppetmaster stop
      $ find $(puppet master --configprint ssldir) -name "$(puppet master --configprint certname).pem" -delete
    3. Den Zertifikatsnamen und die Alternativen DNS-Namen in der /etc/puppet/puppet.conf eintragen mit den folgenden Optionen:
      [master]
      certname=puppet.example.org
      certdnsname=puppet.example.org
      dns_alt_names=puppetmaster.example.org
    4. Puppetmaster Server im Vordergrund starten und zusehen wie die neuen Zertifikate generiert werden, wenn die Meldung „notice: Starting Puppet master version…“ dort steht mit Strg+c beenden:
      $ puppet master --no-daemonize --verbose
      Strg+c
      $ service puppetmaster start
  • Wenn es dann von PuppetDB eine Fehlermeldung gibt nach dem Motto „‚replace facts‘ to PuppetDB: certificate verify failed“ hilft folgendes:
    $ service puppetdb stop
    $ mv /etc/puppetdb/ssl /etc/puppetdb/ssl_old
    $ /usr/sbin/puppetdb-ssl-setup -f
    $ service puppetdb start

Infos von hier und da.