Zum Inhalt springen
servercert

servercert – woher das TLS-Zertifikat eines Daemons kommt

go-authn/servercert stellt ein TLS-Serverzertifikat aus Dateien bereit, die bei Änderung neu gelesen werden, oder von einer ACME-CA über autocert, External Account Binding eingeschlossen.

Woher kommt das TLS-Serverzertifikat eines Daemons? Ein Zertifikat und ein Schlüssel auf der Festplatte, neu gelesen, wenn sie sich ändern, oder eine ACME-CA über autocert, External Account Binding eingeschlossen.

go get github.com/go-authn/servercert@v0.5.2

Eine Config nennt genau eine Quelle – CertFile und KeyFile oder ACME –, und New gibt eine *Source zurück, deren TLSConfig() sie bereitstellt, mit MinVersion TLS 1.2.

Beispiel

Aus Dateien:

package main

import (
	"log"
	"net/http"

	"github.com/go-authn/servercert"
)

func main() {
	src, err := servercert.New(servercert.Config{
		CertFile: "/etc/fileshare/tls/fullchain.pem",
		KeyFile:  "/etc/fileshare/tls/privkey.pem",
		OnError:  func(err error) { log.Print(err) }, // a renewal that did not load
	})
	if err != nil {
		log.Fatal(err) // unreadable, unparsable, or a key that is not the certificate's
	}
	srv := &http.Server{Addr: ":443", TLSConfig: src.TLSConfig()}
	log.Fatal(srv.ListenAndServeTLS("", ""))
}

Config.Check lehnt eine Konfiguration ab, die nicht funktionieren kann, und rührt nichts an – keine Datei wird gelesen, kein Verzeichnis angelegt:

package main

import (
	"fmt"

	"github.com/go-authn/servercert"
)

func main() {
	// Check touches nothing: no file is read, no directory created.
	for _, c := range []servercert.Config{
		{ACME: &servercert.ACME{Domains: []string{"*.example.org"}, CacheDir: "/var/lib/fileshare/acme"}},
		{ACME: &servercert.ACME{Domains: []string{"192.0.2.10"}, CacheDir: "/var/lib/fileshare/acme"}},
		{ACME: &servercert.ACME{Domains: []string{"files.example.org"}, CacheDir: "acme"}},
		{CertFile: "/etc/fileshare/tls/fullchain.pem"},
		{ACME: &servercert.ACME{Domains: []string{"files.example.org"}, CacheDir: "/var/lib/fileshare/acme",
			DirectoryURL: "http://acme.example.org/directory"}},
		{ACME: &servercert.ACME{Domains: []string{"files.example.org"}, CacheDir: "/var/lib/fileshare/acme"}},
	} {
		fmt.Println(c.Check())
	}
}
servercert: ACME domain "*.example.org" is a wildcard; that needs dns-01, which autocert does not do
servercert: ACME domain "192.0.2.10" is an IP address; autocert issues only for DNS names
servercert: ACME.CacheDir "acme" is not an absolute path
servercert: CertFile and KeyFile go together; one of them is empty
servercert: ACME.DirectoryURL "http://acme.example.org/directory" is not an https URL (RFC 8555 §6.1)
<nil>

Die letzte ist Let’s Encrypt in Produktion: DirectoryURL leer.

Dateien

New liest das Paar und schlägt sofort fehl, wenn eines unlesbar ist, sich nicht parsen lässt oder dessen Schlüssel nicht der des Zertifikats ist. Danach werden die Dateien höchstens einmal alle 10 Sekunden und während eines Handshakes erneut gelesen; ein geändertes Paar wird geparst, ohne eine Sperre zu halten, und eingewechselt. Ein geändertes Paar, das sich nicht laden lässt – das Zertifikat vor seinem Schlüssel geschrieben, eine abgeschnittene Datei –, wird einmal an OnError gemeldet, und das letzte gute Paar bleibt im Dienst, sodass ein Erneuerungsjob, der die beiden Dateien nacheinander schreibt, den Server nie lahmlegt.

ACME

autocert beantwortet zwei Challenge-Typen und keine anderen: tls-alpn-01 (RFC 8737, Port 443, ALPN acme-tls/1) und, mit HTTPChallenge, http-01 (RFC 8555 §8.3, Port 80, bereitgestellt von Source.HTTPHandler). Es gibt kein dns-01, daher kein Wildcard- und kein IP-Adressen-Zertifikat.

Zertifikate werden beim ersten Handshake bezogen, dessen SNI eine der Domains nennt, oder vor jedem Client durch Source.Prefetch, und von autocert erneuert, bevor sie ablaufen. Ein Client, der sich per IP-Adresse verbindet, sendet kein SNI und erhält das Zertifikat der ersten Domain.

An NextProtos anhängen, es nie ersetzen

Mit ACME enthält TLSConfig().NextProtos acme-tls/1, ohne das die tls-alpn-01-Validierung der CA fehlschlägt. Und anhängen müssen Sie: Ein Go-TLS-Server, der ALPN-Protokolle anbietet, lehnt einen Client ab, dessen Angebot keines davon teilt; daher fügt ein NFS-over-TLS-Server sunrpc hinzu (RFC 9289). net/http fügt http/1.1 und h2 selbst hinzu.

Eine CA, die keine Challenge braucht. autocert kehrt sofort zurück, wenn eine neue Bestellung bereits ready ist, was eine CA mit vorab validierten Domains antwortet: GÉANT TCS, bereitgestellt von HARICA, für ein Enterprise-Admin-Konto. Ein interner Server, der aus dem Internet weder auf Port 80 noch auf 443 erreichbar ist, kann trotzdem ein öffentlich vertrauenswürdiges Zertifikat erhalten. Der EAB-HMAC-Schlüssel ist ein Geheimnis, daher nennt die Konfiguration eine Datei, die ihn enthält (EABHMACKeyFile), nie den Schlüssel selbst.

Ein Defekt von x/crypto/acme wird umgangen (golang/go#77704): Nach dem Abschließen einer Bestellung, die die CA noch bearbeitet, fragt der Client die URL im Location-Header der Antwort auf das Abschließen ab, den RFC 8555 dort nicht setzt. servercert gibt dem ACME-Client einen Transport, der diesen Header hinzufügt, mit der Bestell-URL aus der früheren newOrder-Antwort.

Was es ablehnt und warum

Check lehnt ab: keine oder beide Quellen; ein halbes Dateipaar; ACME ohne Domains oder Cache-Verzeichnis; einen relativen Pfad; ein halbes EAB; eine Domain, die eine IP-Adresse oder ein Wildcard ist, aus einem einzigen Label besteht, mit einem Punkt endet oder kein gültiger Hostname ist; eine Verzeichnis-URL, die nicht https ist (RFC 8555 §6.1).

New lehnt außerdem ein Cache-Verzeichnis ab – es enthält private Schlüssel –, das ein symbolischer Link ist, für Gruppe oder andere zugänglich ist, einem anderen Benutzer gehört als dem effektiven des Prozesses oder nicht beschreibbar ist; ein fehlendes wird mit 0700 angelegt und danach geprüft. Jedes Verzeichnis darüber darf nur von root oder dem effektiven Benutzer änderbar sein, wie es StrictModes von sshd für den Pfad zu authorized_keys verlangt, und ein symbolischer Link darüber wird abgelehnt, sofern er nicht root gehört. Unter Windows werden Modus und Eigentümer nicht geprüft – ACLs entscheiden beides, und sie einzuschränken ist Ihre Sache –, und ein symbolischer Link irgendwo im Pfad wird abgelehnt.

Wogegen es geprüft wird

pebble, die Test-CA von Let’s Encrypt, im Prozess ausgeführt aus ihren Paketen ca, db, va und wfe mit einem DNS-Stub, stellt echte Zertifikate aus, die crypto/tls gegen die Root von pebble prüft: tls-alpn-01; http-01 über HTTPHandler und seine Kontrolle mit ausgeschaltetem HTTPChallenge; EAB verlangt, fehlend, falsch, dann richtig; eine vorab validierte Domain, ausgestellt ohne offenen Challenge-Port, und ihre Kontrolle. Ein Test führt dieselbe Ausstellung ohne die Umgehung für golang/go#77704 aus und verlangt, dass sie fehlschlägt. Die Coverage-Untergrenze liegt bei 100 %.

Referenz

pkg.go.dev/github.com/go-authn/servercert@v0.5.2

Erstellt am von tannevaled

Geändert am von tannevaled