mirror of
https://github.com/coredns/coredns.git
synced 2026-08-20 23:08:28 -04:00
plugin/tls: manage certificates with ACME DNS-01 (#8310)
* plugin/tls: add automatic ACME certificates Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com> * chore: add houyuwushang to CODEOWNERS Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com> * chore: regenerate maintainer owners Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com> --------- Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>
This commit is contained in:
161
test/acme_test.go
Normal file
161
test/acme_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/pebble/v2/ca"
|
||||
"github.com/letsencrypt/pebble/v2/db"
|
||||
"github.com/letsencrypt/pebble/v2/va"
|
||||
"github.com/letsencrypt/pebble/v2/wfe"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestACMEDNS01CertificateManagement(t *testing.T) {
|
||||
t.Setenv("PEBBLE_VA_NOSLEEP", "1")
|
||||
t.Setenv("PEBBLE_WFE_NONCEREJECT", "0")
|
||||
t.Setenv("PEBBLE_AUTHZREUSE", "0")
|
||||
|
||||
dnsPort := freeTCPUDPPort(t)
|
||||
dotPort := freeTCPPort(t)
|
||||
resolver := net.JoinHostPort("127.0.0.1", fmt.Sprint(dnsPort))
|
||||
|
||||
logger := log.New(io.Discard, "", 0)
|
||||
store := db.NewMemoryStore()
|
||||
pebbleCA := ca.New(logger, store, "", "ecdsa", 0, 1, map[string]ca.Profile{
|
||||
"default": {Description: "default", ValidityPeriod: 3600},
|
||||
})
|
||||
validator := va.New(logger, 5002, 5001, false, resolver, store)
|
||||
frontend := wfe.New(logger, store, validator, pebbleCA, []string{"pebble"}, false, false, 0, 0)
|
||||
acmeServer := httptest.NewTLSServer(frontend.Handler())
|
||||
defer acmeServer.Close()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
serverRoot := filepath.Join(tempDir, "acme-server-root.pem")
|
||||
serverCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: acmeServer.Certificate().Raw})
|
||||
if err := os.WriteFile(serverRoot, serverCert, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const domain = "dns.example"
|
||||
corefile := fmt.Sprintf(`dns://.:%d {
|
||||
bind 127.0.0.1
|
||||
whoami
|
||||
}
|
||||
|
||||
tls://.:%d {
|
||||
bind 127.0.0.1
|
||||
tls {
|
||||
acme %s
|
||||
ca %s%s
|
||||
ca_root %s
|
||||
storage %s
|
||||
resolver 127.0.0.1:%d
|
||||
}
|
||||
whoami
|
||||
}`, dnsPort, dotPort, domain, acmeServer.URL, wfe.DirectoryPath, serverRoot, filepath.Join(tempDir, "certificates"), dnsPort)
|
||||
|
||||
instance, err := CoreDNSServer(corefile)
|
||||
if err != nil {
|
||||
t.Fatalf("starting CoreDNS: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
instance.ShutdownCallbacks()
|
||||
instance.Stop()
|
||||
}()
|
||||
|
||||
address := net.JoinHostPort("127.0.0.1", fmt.Sprint(dotPort))
|
||||
var peerCertificates []*x509.Certificate
|
||||
deadline := time.Now().Add(20 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: time.Second}, "tcp", address, &tls.Config{
|
||||
ServerName: domain,
|
||||
InsecureSkipVerify: true, // The issued chain is verified explicitly below.
|
||||
MinVersion: tls.VersionTLS12,
|
||||
})
|
||||
if err == nil {
|
||||
peerCertificates = conn.ConnectionState().PeerCertificates
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if len(peerCertificates) == 0 {
|
||||
t.Fatal("CoreDNS did not obtain and serve an ACME certificate before the deadline")
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
if !roots.AppendCertsFromPEM(pebbleCA.GetRootCert(0).PEM()) {
|
||||
t.Fatal("could not add Pebble root certificate")
|
||||
}
|
||||
intermediates := x509.NewCertPool()
|
||||
for _, certificate := range peerCertificates[1:] {
|
||||
intermediates.AddCert(certificate)
|
||||
}
|
||||
if _, err := peerCertificates[0].Verify(x509.VerifyOptions{
|
||||
DNSName: domain,
|
||||
Roots: roots,
|
||||
Intermediates: intermediates,
|
||||
}); err != nil {
|
||||
t.Fatalf("verifying managed certificate: %v", err)
|
||||
}
|
||||
|
||||
request := new(dns.Msg)
|
||||
request.SetQuestion("whoami.example.", dns.TypeA)
|
||||
client := &dns.Client{
|
||||
Net: "tcp-tls",
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: domain,
|
||||
RootCAs: roots,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
response, _, err := client.Exchange(request, address)
|
||||
if err != nil {
|
||||
t.Fatalf("querying CoreDNS with the managed certificate: %v", err)
|
||||
}
|
||||
if response.Rcode != dns.RcodeSuccess {
|
||||
t.Fatalf("DoT response code = %d, want success", response.Rcode)
|
||||
}
|
||||
}
|
||||
|
||||
func freeTCPPort(t *testing.T) int {
|
||||
t.Helper()
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
return listener.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func freeTCPUDPPort(t *testing.T) int {
|
||||
t.Helper()
|
||||
for range 10 {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
packet, err := net.ListenPacket("udp", net.JoinHostPort("127.0.0.1", fmt.Sprint(port)))
|
||||
if err == nil {
|
||||
packet.Close()
|
||||
listener.Close()
|
||||
return port
|
||||
}
|
||||
listener.Close()
|
||||
}
|
||||
t.Fatal("could not find a free TCP/UDP port")
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user