mirror of
https://github.com/coredns/coredns.git
synced 2026-08-20 23:08:28 -04:00
* 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>
162 lines
4.2 KiB
Go
162 lines
4.2 KiB
Go
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
|
|
}
|