jarld checks the DoT and DoH listeners every 10 minutes as a client would (page, dashboard, jarl encrypted-dns health, jarl_encrypted_dns_up). The Compare views tab and jarl zone diff show what differs between a zone's views, with Use this version. --local works on unlinked zones. A changed certificates.dir is followed by encrypted DNS's tls statements. Sharing an already shared version replaces other views' overrides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
3.8 KiB
Go
116 lines
3.8 KiB
Go
package encdns
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"io"
|
|
"math/big"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func testCert(t *testing.T, names ...string) (tls.Certificate, *x509.Certificate) {
|
|
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
tmpl := &x509.Certificate{SerialNumber: big.NewInt(0xabc123), Subject: pkix.Name{CommonName: names[0]}, DNSNames: names,
|
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}}
|
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
leaf, _ := x509.ParseCertificate(der)
|
|
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key, Leaf: leaf}, leaf
|
|
}
|
|
|
|
// answer answers every question with 192.0.2.53.
|
|
var answer = dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.Answer = append(m.Answer, &dns.A{Hdr: dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60}, A: net.ParseIP("192.0.2.53")})
|
|
w.WriteMsg(m)
|
|
})
|
|
|
|
func listeners(t *testing.T, cert tls.Certificate) (dot, doh Endpoint) {
|
|
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
ln, err := tls.Listen("tcp", "127.0.0.1:0", cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv := &dns.Server{Listener: ln, Net: "tcp-tls", Handler: answer}
|
|
go srv.ActivateAndServe()
|
|
t.Cleanup(func() { srv.Shutdown() })
|
|
hl, err := tls.Listen("tcp", "127.0.0.1:0", cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hs := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/dns-query" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
b, _ := io.ReadAll(r.Body)
|
|
q := new(dns.Msg)
|
|
if q.Unpack(b) != nil {
|
|
http.Error(w, "bad", 400)
|
|
return
|
|
}
|
|
m := new(dns.Msg)
|
|
m.SetReply(q)
|
|
m.Rcode = dns.RcodeSuccess
|
|
out, _ := m.Pack()
|
|
w.Header().Set("Content-Type", "application/dns-message")
|
|
w.Write(out)
|
|
})}
|
|
go hs.Serve(hl)
|
|
t.Cleanup(func() { hs.Close() })
|
|
port := func(l net.Listener) int { return l.Addr().(*net.TCPAddr).Port }
|
|
return Endpoint{"127.0.0.1", port(ln)}, Endpoint{"127.0.0.1", port(hl)}
|
|
}
|
|
|
|
func TestHealth(t *testing.T) {
|
|
ctx := context.Background()
|
|
cert, leaf := testCert(t, "dns.example.com")
|
|
dot, doh := listeners(t, cert)
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(leaf)
|
|
st := Status{Enabled: true, DoT: []Endpoint{dot}, DoH: []Endpoint{doh}}
|
|
|
|
h := Check(ctx, st, "dns.example.com", "abc123", roots)
|
|
if !h.OK() || len(h.Probes) != 2 {
|
|
t.Fatalf("working setup: %+v", h.Probes)
|
|
}
|
|
for _, p := range h.Probes {
|
|
if !p.Trusted || p.RCode != "NOERROR" || p.Warning != "" || p.CertSerial != "abc123" {
|
|
t.Errorf("%s: %+v", p.Kind, p)
|
|
}
|
|
}
|
|
// named hasn't reread a renewed certificate.
|
|
if h := Check(ctx, st, "dns.example.com", "def456", roots); h.OK() || !strings.Contains(h.Probes[0].Problem, "older certificate") {
|
|
t.Errorf("stale: %+v", h.Probes[0])
|
|
}
|
|
// The wrong name.
|
|
if h := Check(ctx, st, "other.example.com", "", roots); h.OK() || !strings.Contains(h.Probes[0].Problem, "not other.example.com") {
|
|
t.Errorf("wrong name: %+v", h.Probes[0])
|
|
}
|
|
// A CA this server doesn't trust: works, with a warning.
|
|
if h := Check(ctx, st, "dns.example.com", "abc123", x509.NewCertPool()); !h.OK() || !strings.Contains(h.Probes[1].Warning, "staging") {
|
|
t.Errorf("untrusted: %+v", h.Probes[1])
|
|
}
|
|
// Nothing listening.
|
|
l, _ := net.Listen("tcp", "127.0.0.1:0")
|
|
gone := l.Addr().(*net.TCPAddr).Port
|
|
l.Close()
|
|
if h := Check(ctx, Status{Enabled: true, DoT: []Endpoint{{"127.0.0.1", gone}}}, "dns.example.com", "", roots); h.OK() || !strings.Contains(h.Probes[0].Problem, "nothing listens") {
|
|
t.Errorf("nothing listening: %+v", h.Probes[0])
|
|
}
|
|
}
|