lego/providers/dns/rfc2136/rfc2136_test.go
Brett Vickers 47219adc00 Make DNS provider credential-handling more consistent.
Different DNS providers were handling credentials in different ways.
Some were reading credential environment variables in cli_handlers.go
and then passing them into the NewDNSProvider function, while others
were reading the environment variables within their NewDNSProvider
functions.

This change replaces each DNS challenge's NewDNSProvider function with
two new functions: (1) a NewDNSProvider function that takes no
parameters and uses the environment to read credentials, and (2) a
NewDNSProviderCredentials that takes credentials as parameters.
2016-03-20 11:40:30 -07:00

244 lines
7.3 KiB
Go

package rfc2136
import (
"bytes"
"fmt"
"net"
"strings"
"sync"
"testing"
"time"
"github.com/miekg/dns"
"github.com/xenolf/lego/acme"
)
var (
rfc2136TestDomain = "123456789.www.example.com"
rfc2136TestKeyAuth = "123d=="
rfc2136TestValue = "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo"
rfc2136TestFqdn = "_acme-challenge.123456789.www.example.com."
rfc2136TestZone = "example.com."
rfc2136TestTTL = 120
rfc2136TestTsigKey = "example.com."
rfc2136TestTsigSecret = "IwBTJx9wrDp4Y1RyC3H0gA=="
)
var reqChan = make(chan *dns.Msg, 10)
func TestRFC2136CanaryLocalTestServer(t *testing.T) {
acme.ClearFqdnCache()
dns.HandleFunc("example.com.", serverHandlerHello)
defer dns.HandleRemove("example.com.")
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
c := new(dns.Client)
m := new(dns.Msg)
m.SetQuestion("example.com.", dns.TypeTXT)
r, _, err := c.Exchange(m, addrstr)
if err != nil || len(r.Extra) == 0 {
t.Fatalf("Failed to communicate with test server: %v", err)
}
txt := r.Extra[0].(*dns.TXT).Txt[0]
if txt != "Hello world" {
t.Error("Expected test server to return 'Hello world' but got: ", txt)
}
}
func TestRFC2136ServerSuccess(t *testing.T) {
acme.ClearFqdnCache()
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err)
}
}
func TestRFC2136ServerError(t *testing.T) {
acme.ClearFqdnCache()
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnErr)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err == nil {
t.Errorf("Expected Present() to return an error but it did not.")
} else if !strings.Contains(err.Error(), "NOTZONE") {
t.Errorf("Expected Present() to return an error with the 'NOTZONE' rcode string but it did not.")
}
}
func TestRFC2136TsigClient(t *testing.T) {
acme.ClearFqdnCache()
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", true)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
provider, err := NewDNSProviderCredentials(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret)
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err)
}
}
func TestRFC2136ValidUpdatePacket(t *testing.T) {
acme.ClearFqdnCache()
dns.HandleFunc(rfc2136TestZone, serverHandlerPassBackRequest)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", rfc2136TestFqdn, rfc2136TestTTL, rfc2136TestValue))
rrs := []dns.RR{txtRR}
m := new(dns.Msg)
m.SetUpdate(rfc2136TestZone)
m.RemoveRRset(rrs)
m.Insert(rrs)
expectstr := m.String()
expect, err := m.Pack()
if err != nil {
t.Fatalf("Error packing expect msg: %v", err)
}
provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", "1234d=="); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err)
}
rcvMsg := <-reqChan
rcvMsg.Id = m.Id
actual, err := rcvMsg.Pack()
if err != nil {
t.Fatalf("Error packing actual msg: %v", err)
}
if !bytes.Equal(actual, expect) {
tmp := new(dns.Msg)
if err := tmp.Unpack(actual); err != nil {
t.Fatalf("Error unpacking actual msg: %v", err)
}
t.Errorf("Expected msg:\n%s", expectstr)
t.Errorf("Actual msg:\n%v", tmp)
}
}
func runLocalDNSTestServer(listenAddr string, tsig bool) (*dns.Server, string, error) {
pc, err := net.ListenPacket("udp", listenAddr)
if err != nil {
return nil, "", err
}
server := &dns.Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
if tsig {
server.TsigSecret = map[string]string{rfc2136TestTsigKey: rfc2136TestTsigSecret}
}
waitLock := sync.Mutex{}
waitLock.Lock()
server.NotifyStartedFunc = waitLock.Unlock
go func() {
server.ActivateAndServe()
pc.Close()
}()
waitLock.Lock()
return server, pc.LocalAddr().String(), nil
}
func serverHandlerHello(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
m.Extra = make([]dns.RR, 1)
m.Extra[0] = &dns.TXT{
Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
Txt: []string{"Hello world"},
}
w.WriteMsg(m)
}
func serverHandlerReturnSuccess(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET {
// Return SOA to appease findZoneByFqdn()
soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", rfc2136TestZone, rfc2136TestTTL, rfc2136TestZone, rfc2136TestZone))
m.Answer = []dns.RR{soaRR}
}
if t := req.IsTsig(); t != nil {
if w.TsigStatus() == nil {
// Validated
m.SetTsig(rfc2136TestZone, dns.HmacMD5, 300, time.Now().Unix())
}
}
w.WriteMsg(m)
}
func serverHandlerReturnErr(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetRcode(req, dns.RcodeNotZone)
w.WriteMsg(m)
}
func serverHandlerPassBackRequest(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET {
// Return SOA to appease findZoneByFqdn()
soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", rfc2136TestZone, rfc2136TestTTL, rfc2136TestZone, rfc2136TestZone))
m.Answer = []dns.RR{soaRR}
}
if t := req.IsTsig(); t != nil {
if w.TsigStatus() == nil {
// Validated
m.SetTsig(rfc2136TestZone, dns.HmacMD5, 300, time.Now().Unix())
}
}
w.WriteMsg(m)
if req.Opcode != dns.OpcodeQuery || req.Question[0].Qtype != dns.TypeSOA || req.Question[0].Qclass != dns.ClassINET {
// Only talk back when it is not the SOA RR.
reqChan <- req
}
}