Repository: miekg/dns
Pull request #1533 (2024-01-31 14:25:27)

Add option to do a zone transfer via TLS
Add a new helper func InTLS to start a zone transfer using a TLS connection

miekg: [ Quoting ***@***.***> in "Re: [miekg/dns] New func InTLS: sta..." ]
>@cesarkuroiwa requested your review on: #1533 New func InTLS: start a zone
>transfer via TLS as a code owner.

Thanks, this is nice to have, but I feel this should not be a new function, but be folded
into the current In function. I.e. add a *tls.Config to *Transfer structure and then
rework In() a bit.

Regards,
Miek

cesarkuroiwa: > [ Quoting ***@***.***> in "Re: [miekg/dns] New func InTLS: sta..." ]
> @cesarkuroiwa requested your review on: #1533 New func InTLS: start a zone transfer via TLS as a code owner.
> Thanks, this is nice to have, but I feel this should not be a new function, but be folded into the current In function. I.e. add a *tls.Config to *Transfer structure and then rework In() a bit. Regards, Miek

Actually I kind of thought of something like that too. Maybe add a *tls.Config to Transfer, and check if it's not nil, then call DialTimeoutTLS

Would that be better?
miekg: [ Quoting ***@***.***> in "Re: [miekg/dns] New func InTLS: sta..." ]
>    [ Quoting @.***> in "Re: [miekg/dns] New func InTLS: sta..." ]
>    @cesarkuroiwa requested your review on: #1533 New func InTLS: start a zone
>    transfer via TLS as a code owner.
>    Thanks, this is nice to have, but I feel this should not be a new function,
>    but be folded into the current In function. I.e. add a *tls.Config to
>    *Transfer structure and then rework In() a bit. Regards, Miek
>
>Actually I kind of thought of something like that too. Maybe add a *tls.Config
>to Transfer, and check if it's not nil, then call DialTimeoutTLS
>
>Would that be better?

yep.

With quic support (whenever that lands in Go), there is probably another way to connect,
so doing it (sort of) transparantly in In() has my preference. We don't have
transport-specific functions anywhere else .

Cheers,
Miek


Patch:
diff --git a/README.md b/README.md
index e57d86af..bca1a55a 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ A not-so-up-to-date-list-that-may-be-actually-current:
 * https://github.com/egbakou/domainverifier
 * https://github.com/semihalev/sdns
 * https://github.com/wintbiit/NineDNS
+* https://linuxcontainers.org/incus/
 
 
 Send pull request if you want to be listed here.
diff --git a/xfr.go b/xfr.go
index 05b3c5ad..91080109 100644
--- a/xfr.go
+++ b/xfr.go
@@ -1,6 +1,7 @@
 package dns
 
 import (
+	"crypto/tls"
 	"fmt"
 	"time"
 )
@@ -20,6 +21,7 @@ type Transfer struct {
 	TsigProvider   TsigProvider      // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
 	TsigSecret     map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
 	tsigTimersOnly bool
+	TLS            *tls.Config // TLS config. If Xfr over TLS will be attempted
 }
 
 func (t *Transfer) tsigProvider() TsigProvider {
@@ -57,7 +59,11 @@ func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
 	}
 
 	if t.Conn == nil {
-		t.Conn, err = DialTimeout("tcp", a, timeout)
+		if t.TLS != nil {
+			t.Conn, err = DialTimeoutWithTLS("tcp-tls", a, t.TLS, timeout)
+		} else {
+			t.Conn, err = DialTimeout("tcp", a, timeout)
+		}
 		if err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/xfr_test.go b/xfr_test.go
index f6c5e98c..04801a2e 100644
--- a/xfr_test.go
+++ b/xfr_test.go
@@ -1,6 +1,7 @@
 package dns
 
 import (
+	"crypto/tls"
 	"testing"
 	"time"
 )
@@ -87,6 +88,27 @@ func TestSingleEnvelopeXfr(t *testing.T) {
 	axfrTestingSuite(t, addrstr)
 }
 
+func TestSingleEnvelopeXfrTLS(t *testing.T) {
+	HandleFunc("miek.nl.", SingleEnvelopeXfrServer)
+	defer HandleRemove("miek.nl.")
+
+	cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock)
+	if err != nil {
+		t.Fatalf("unable to build certificate: %v", err)
+	}
+
+	tlsConfig := tls.Config{
+		Certificates: []tls.Certificate{cert},
+	}
+	s, addrstr, _, err := RunLocalTLSServer(":0", &tlsConfig)
+	if err != nil {
+		t.Fatalf("unable to run test server: %s", err)
+	}
+	defer s.Shutdown()
+
+	axfrTestingSuiteTLS(t, addrstr)
+}
+
 func TestMultiEnvelopeXfr(t *testing.T) {
 	HandleFunc("miek.nl.", MultipleEnvelopeXfrServer)
 	defer HandleRemove("miek.nl.")
@@ -131,6 +153,38 @@ func axfrTestingSuite(t *testing.T, addrstr string) {
 	}
 }
 
+func axfrTestingSuiteTLS(t *testing.T, addrstr string) {
+	tr := new(Transfer)
+	m := new(Msg)
+	m.SetAxfr("miek.nl.")
+
+	tr.TLS = &tls.Config{
+		InsecureSkipVerify: true,
+	}
+	c, err := tr.In(m, addrstr)
+	if err != nil {
+		t.Fatal("failed to zone transfer in", err)
+	}
+
+	var records []RR
+	for msg := range c {
+		if msg.Error != nil {
+			t.Fatal(msg.Error)
+		}
+		records = append(records, msg.RR...)
+	}
+
+	if len(records) != len(xfrTestData) {
+		t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
+	}
+
+	for i, rr := range records {
+		if !IsDuplicate(rr, xfrTestData[i]) {
+			t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData)
+		}
+	}
+}
+
 func axfrTestingSuiteWithCustomTsig(t *testing.T, addrstr string, provider TsigProvider) {
 	tr := new(Transfer)
 	m := new(Msg)