Merge pull request #413 from letsencrypt/dnssec-cleanup
Remove local DNSSEC checking
This commit is contained in:
commit
31f8a3083c
184
core/dns.go
184
core/dns.go
|
|
@ -6,7 +6,6 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
|
@ -15,15 +14,6 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DNSSECError indicates an error caused by DNSSEC failing.
|
||||
type DNSSECError struct {
|
||||
}
|
||||
|
||||
// Error gives the DNSSEC failure notice.
|
||||
func (err DNSSECError) Error() string {
|
||||
return "DNSSEC validation failure"
|
||||
}
|
||||
|
||||
// DNSResolverImpl represents a resolver system
|
||||
type DNSResolverImpl struct {
|
||||
DNSClient *dns.Client
|
||||
|
|
@ -42,8 +32,16 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve
|
|||
}
|
||||
|
||||
// ExchangeOne performs a single DNS exchange with a randomly chosen server
|
||||
// out of the server list, returning the response, time, and error (if any)
|
||||
func (dnsResolver *DNSResolverImpl) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt time.Duration, err error) {
|
||||
// out of the server list, returning the response, time, and error (if any).
|
||||
// This method sets the DNSSEC OK bit on the message to true before sending
|
||||
// it to the resolver in case validation isn't the resolvers default behaviour.
|
||||
func (dnsResolver *DNSResolverImpl) ExchangeOne(hostname string, qtype uint16) (rsp *dns.Msg, rtt time.Duration, err error) {
|
||||
m := new(dns.Msg)
|
||||
// Set question type
|
||||
m.SetQuestion(dns.Fqdn(hostname), qtype)
|
||||
// Set DNSSEC OK bit for resolver
|
||||
m.SetEdns0(4096, true)
|
||||
|
||||
if len(dnsResolver.Servers) < 1 {
|
||||
err = fmt.Errorf("Not configured with at least one DNS Server")
|
||||
return
|
||||
|
|
@ -55,60 +53,25 @@ func (dnsResolver *DNSResolverImpl) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt t
|
|||
return dnsResolver.DNSClient.Exchange(m, chosenServer)
|
||||
}
|
||||
|
||||
// LookupDNSSEC sends the provided DNS message to a randomly chosen server (see
|
||||
// ExchangeOne) with DNSSEC enabled. If the lookup fails, this method sends a
|
||||
// clarification query to determine if it's because DNSSEC was invalid or just
|
||||
// a run-of-the-mill error. If it's because of DNSSEC, it returns ErrorDNSSEC.
|
||||
func (dnsResolver *DNSResolverImpl) LookupDNSSEC(m *dns.Msg) (*dns.Msg, time.Duration, error) {
|
||||
// Set DNSSEC OK bit
|
||||
m.SetEdns0(4096, true)
|
||||
r, rtt, err := dnsResolver.ExchangeOne(m)
|
||||
if err != nil {
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
if r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError && r.Rcode != dns.RcodeNXRrset {
|
||||
if r.Rcode == dns.RcodeServerFailure {
|
||||
// Re-send query with +cd to see if SERVFAIL was caused by DNSSEC
|
||||
// validation failure at the resolver
|
||||
m.CheckingDisabled = true
|
||||
checkR, _, err := dnsResolver.ExchangeOne(m)
|
||||
if err != nil {
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
if checkR.Rcode != dns.RcodeServerFailure {
|
||||
// DNSSEC error, so we return the testable object.
|
||||
err = DNSSECError{}
|
||||
return r, rtt, err
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("Invalid response code: %d-%s", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
// LookupTXT uses a DNSSEC-enabled query to find all TXT records associated with
|
||||
// the provided hostname. If the query fails due to DNSSEC, error will be
|
||||
// set to ErrorDNSSEC.
|
||||
// LookupTXT sends a DNS query to find all TXT records associated with
|
||||
// the provided hostname.
|
||||
func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.Duration, error) {
|
||||
var txt []string
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(hostname), dns.TypeTXT)
|
||||
r, rtt, err := dnsResolver.LookupDNSSEC(m)
|
||||
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeTXT)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for TXT query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
for _, answer := range r.Answer {
|
||||
if answer.Header().Rrtype == dns.TypeTXT {
|
||||
txtRec := answer.(*dns.TXT)
|
||||
for _, field := range txtRec.Txt {
|
||||
txt = append(txt, field)
|
||||
if txtRec, ok := answer.(*dns.TXT); ok {
|
||||
for _, field := range txtRec.Txt {
|
||||
txt = append(txt, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,97 +79,92 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
|
|||
return txt, rtt, err
|
||||
}
|
||||
|
||||
// LookupHost uses a DNSSEC-enabled query to find all A/AAAA records associated with
|
||||
// the provided hostname. If the query fails due to DNSSEC, error will be
|
||||
// set to ErrorDNSSEC.
|
||||
func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
|
||||
// LookupHost sends a DNS query to find all A/AAAA records associated with
|
||||
// the provided hostname.
|
||||
func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, time.Duration, error) {
|
||||
var addrs []net.IP
|
||||
var answers []dns.RR
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
|
||||
r, aRtt, err := dnsResolver.LookupDNSSEC(m)
|
||||
r, aRtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeA)
|
||||
if err != nil {
|
||||
return addrs, aRtt, err
|
||||
return addrs, 0, 0, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for A query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return nil, aRtt, 0, err
|
||||
}
|
||||
|
||||
answers = append(answers, r.Answer...)
|
||||
|
||||
m.SetQuestion(dns.Fqdn(hostname), dns.TypeAAAA)
|
||||
r, aaaaRtt, err := dnsResolver.LookupDNSSEC(m)
|
||||
r, aaaaRtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeAAAA)
|
||||
if err != nil {
|
||||
return addrs, aRtt + aaaaRtt, err
|
||||
return addrs, aRtt, 0, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for AAAA query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return nil, aRtt, aaaaRtt, err
|
||||
}
|
||||
|
||||
answers = append(answers, r.Answer...)
|
||||
|
||||
for _, answer := range answers {
|
||||
if answer.Header().Rrtype == dns.TypeA {
|
||||
a := answer.(*dns.A)
|
||||
addrs = append(addrs, a.A)
|
||||
if a, ok := answer.(*dns.A); ok {
|
||||
addrs = append(addrs, a.A)
|
||||
}
|
||||
} else if answer.Header().Rrtype == dns.TypeAAAA {
|
||||
aaaa := answer.(*dns.AAAA)
|
||||
addrs = append(addrs, aaaa.AAAA)
|
||||
if aaaa, ok := answer.(*dns.AAAA); ok {
|
||||
addrs = append(addrs, aaaa.AAAA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addrs, aRtt + aaaaRtt, nil
|
||||
return addrs, aRtt, aaaaRtt, nil
|
||||
}
|
||||
|
||||
// LookupCNAME uses a DNSSEC-enabled query to records for domain and returns either
|
||||
// the target, "", or a if the query fails due to DNSSEC, error will be set to
|
||||
// ErrorDNSSEC.
|
||||
func (dnsResolver *DNSResolverImpl) LookupCNAME(domain string) (string, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(domain), dns.TypeCNAME)
|
||||
|
||||
r, _, err := dnsResolver.LookupDNSSEC(m)
|
||||
// LookupCNAME sends a DNS query to find a CNAME record associated hostname and returns the
|
||||
// record target.
|
||||
func (dnsResolver *DNSResolverImpl) LookupCNAME(hostname string) (string, time.Duration, error) {
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCNAME)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", 0, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for CNAME query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return "", rtt, err
|
||||
}
|
||||
|
||||
for _, answer := range r.Answer {
|
||||
if cname, ok := answer.(*dns.CNAME); ok {
|
||||
return cname.Target, nil
|
||||
return cname.Target, rtt, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
return "", rtt, nil
|
||||
}
|
||||
|
||||
// LookupCAA uses a DNSSEC-enabled query to find all CAA records associated with
|
||||
// the provided hostname. If the query fails due to DNSSEC, error will be
|
||||
// set to ErrorDNSSEC.
|
||||
func (dnsResolver *DNSResolverImpl) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) {
|
||||
if alias {
|
||||
// Check if there is a CNAME record for domain
|
||||
canonName, err := dnsResolver.LookupCNAME(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if canonName == "" || canonName == domain {
|
||||
return []*dns.CAA{}, nil
|
||||
}
|
||||
domain = canonName
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA)
|
||||
|
||||
r, _, err := dnsResolver.LookupDNSSEC(m)
|
||||
// LookupCAA sends a DNS query to find all CAA records associated with
|
||||
// the provided hostname. If the response code from the resolver is SERVFAIL
|
||||
// an empty slice of CAA records is returned.
|
||||
func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time.Duration, error) {
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCAA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// On resolver validation failure, or other server failures, return empty an
|
||||
// set and no error.
|
||||
var CAAs []*dns.CAA
|
||||
if r.Rcode == dns.RcodeServerFailure {
|
||||
return CAAs, rtt, nil
|
||||
}
|
||||
|
||||
for _, answer := range r.Answer {
|
||||
if answer.Header().Rrtype == dns.TypeCAA {
|
||||
caaR, ok := answer.(*dns.CAA)
|
||||
if !ok {
|
||||
err = errors.New("Badly formatted record")
|
||||
return nil, err
|
||||
if caaR, ok := answer.(*dns.CAA); ok {
|
||||
CAAs = append(CAAs, caaR)
|
||||
}
|
||||
CAAs = append(CAAs, caaR)
|
||||
}
|
||||
}
|
||||
|
||||
return CAAs, nil
|
||||
return CAAs, rtt, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
|
|||
m.Compress = false
|
||||
|
||||
for _, q := range r.Question {
|
||||
if q.Name == "servfail.com." {
|
||||
m.Rcode = dns.RcodeServerFailure
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
}
|
||||
switch q.Qtype {
|
||||
case dns.TypeSOA:
|
||||
record := new(dns.SOA)
|
||||
|
|
@ -42,8 +47,7 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
|
|||
w.WriteMsg(m)
|
||||
return
|
||||
case dns.TypeA:
|
||||
switch q.Name {
|
||||
case "cps.letsencrypt.org.":
|
||||
if q.Name == "cps.letsencrypt.org." {
|
||||
record := new(dns.A)
|
||||
record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
|
||||
record.A = net.ParseIP("127.0.0.1")
|
||||
|
|
@ -51,12 +55,6 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
|
|||
m.Answer = append(m.Answer, record)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
case "sigfail.verteiltesysteme.net.":
|
||||
if !r.CheckingDisabled {
|
||||
m.Rcode = dns.RcodeServerFailure
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
}
|
||||
case dns.TypeCAA:
|
||||
if q.Name == "bracewel.net." {
|
||||
|
|
@ -111,8 +109,7 @@ func TestMain(m *testing.M) {
|
|||
func TestDNSNoServers(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Hour, []string{})
|
||||
|
||||
m := new(dns.Msg)
|
||||
_, _, err := obj.ExchangeOne(m)
|
||||
_, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeA)
|
||||
|
||||
test.AssertError(t, err, "No servers")
|
||||
}
|
||||
|
|
@ -120,9 +117,7 @@ func TestDNSNoServers(t *testing.T) {
|
|||
func TestDNSOneServer(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("letsencrypt.org.", dns.TypeSOA)
|
||||
_, _, err := obj.ExchangeOne(m)
|
||||
_, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeSOA)
|
||||
|
||||
test.AssertNotError(t, err, "No message")
|
||||
}
|
||||
|
|
@ -130,9 +125,7 @@ func TestDNSOneServer(t *testing.T) {
|
|||
func TestDNSDuplicateServers(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr, dnsLoopbackAddr})
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("letsencrypt.org.", dns.TypeSOA)
|
||||
_, _, err := obj.ExchangeOne(m)
|
||||
_, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeSOA)
|
||||
|
||||
test.AssertNotError(t, err, "No message")
|
||||
}
|
||||
|
|
@ -143,39 +136,34 @@ func TestDNSLookupsNoServer(t *testing.T) {
|
|||
_, _, err := obj.LookupTXT("letsencrypt.org")
|
||||
test.AssertError(t, err, "No servers")
|
||||
|
||||
_, _, err = obj.LookupHost("letsencrypt.org")
|
||||
_, _, _, err = obj.LookupHost("letsencrypt.org")
|
||||
test.AssertError(t, err, "No servers")
|
||||
|
||||
_, err = obj.LookupCNAME("letsencrypt.org")
|
||||
_, _, err = obj.LookupCNAME("letsencrypt.org")
|
||||
test.AssertError(t, err, "No servers")
|
||||
|
||||
_, err = obj.LookupCAA("letsencrypt.org", false)
|
||||
_, _, err = obj.LookupCAA("letsencrypt.org")
|
||||
test.AssertError(t, err, "No servers")
|
||||
}
|
||||
|
||||
func TestDNSLookupDNSSEC(t *testing.T) {
|
||||
goodServer := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
func TestDNSServFail(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
bad := "servfail.com"
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn("sigfail.verteiltesysteme.net"), dns.TypeA)
|
||||
_, _, err := obj.LookupTXT(bad)
|
||||
test.AssertError(t, err, "LookupTXT didn't return an error")
|
||||
|
||||
_, _, err := goodServer.LookupDNSSEC(m)
|
||||
test.AssertError(t, err, "DNSSEC failure")
|
||||
_, ok := err.(DNSSECError)
|
||||
fmt.Println(err)
|
||||
test.Assert(t, ok, "Should have been a DNSSECError")
|
||||
_, _, err = obj.LookupCNAME(bad)
|
||||
test.AssertError(t, err, "LookupCNAME didn't return an error")
|
||||
|
||||
m.SetQuestion(dns.Fqdn("sigok.verteiltesysteme.net"), dns.TypeA)
|
||||
_, _, _, err = obj.LookupHost(bad)
|
||||
test.AssertError(t, err, "LookupCNAME didn't return an error")
|
||||
|
||||
_, _, err = goodServer.LookupDNSSEC(m)
|
||||
test.AssertNotError(t, err, "DNSSEC should have worked")
|
||||
|
||||
badServer := NewDNSResolverImpl(time.Second*10, []string{"127.0.0.1:99"})
|
||||
|
||||
_, _, err = badServer.LookupDNSSEC(m)
|
||||
test.AssertError(t, err, "Should have failed")
|
||||
_, ok = err.(DNSSECError)
|
||||
test.Assert(t, !ok, "Shouldn't have been a DNSSECError")
|
||||
// CAA lookup ignores validation failures from the resolver for now
|
||||
// and returns an empty list of CAA records.
|
||||
emptyCaa, _, err := obj.LookupCAA(bad)
|
||||
test.Assert(t, len(emptyCaa) == 0, "Query returned non-empty list of CAA records")
|
||||
test.AssertNotError(t, err, "LookupCAA returned an error")
|
||||
}
|
||||
|
||||
func TestDNSLookupTXT(t *testing.T) {
|
||||
|
|
@ -190,30 +178,30 @@ func TestDNSLookupTXT(t *testing.T) {
|
|||
func TestDNSLookupHost(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
|
||||
ip, _, err := obj.LookupHost("sigfail.verteiltesysteme.net")
|
||||
t.Logf("sigfail.verteiltesysteme.net - IP: %s, Err: %s", ip, err)
|
||||
test.AssertError(t, err, "DNSSEC failure")
|
||||
ip, _, _, err := obj.LookupHost("servfail.com")
|
||||
t.Logf("servfail.com - IP: %s, Err: %s", ip, err)
|
||||
test.AssertError(t, err, "Server failure")
|
||||
test.Assert(t, len(ip) == 0, "Should not have IPs")
|
||||
|
||||
ip, _, err = obj.LookupHost("nonexistent.letsencrypt.org")
|
||||
ip, _, _, err = obj.LookupHost("nonexistent.letsencrypt.org")
|
||||
t.Logf("nonexistent.letsencrypt.org - IP: %s, Err: %s", ip, err)
|
||||
test.AssertNotError(t, err, "Not an error to not exist")
|
||||
test.Assert(t, len(ip) == 0, "Should not have IPs")
|
||||
|
||||
ip, _, err = obj.LookupHost("cps.letsencrypt.org")
|
||||
ip, _, _, err = obj.LookupHost("cps.letsencrypt.org")
|
||||
t.Logf("cps.letsencrypt.org - IP: %s, Err: %s", ip, err)
|
||||
test.AssertNotError(t, err, "Not an error to be a CNAME")
|
||||
test.AssertNotError(t, err, "Not an error to exist")
|
||||
test.Assert(t, len(ip) > 0, "Should have IPs")
|
||||
}
|
||||
|
||||
func TestDNSLookupCAA(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
|
||||
caas, err := obj.LookupCAA("bracewel.net", false)
|
||||
caas, _, err := obj.LookupCAA("bracewel.net")
|
||||
test.AssertNotError(t, err, "CAA lookup failed")
|
||||
test.Assert(t, len(caas) > 0, "Should have CAA records")
|
||||
|
||||
caas, err = obj.LookupCAA("nonexistent.letsencrypt.org", false)
|
||||
caas, _, err = obj.LookupCAA("nonexistent.letsencrypt.org")
|
||||
test.AssertNotError(t, err, "CAA lookup failed")
|
||||
test.Assert(t, len(caas) == 0, "Shouldn't have CAA records")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,10 +140,9 @@ type CertificateAuthorityDatabase interface {
|
|||
|
||||
// DNSResolver defines methods used for DNS resolution
|
||||
type DNSResolver interface {
|
||||
ExchangeOne(*dns.Msg) (*dns.Msg, time.Duration, error)
|
||||
LookupDNSSEC(*dns.Msg) (*dns.Msg, time.Duration, error)
|
||||
ExchangeOne(string, uint16) (*dns.Msg, time.Duration, error)
|
||||
LookupTXT(string) ([]string, time.Duration, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, error)
|
||||
LookupCNAME(string) (string, error)
|
||||
LookupCAA(string, bool) ([]*dns.CAA, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, time.Duration, error)
|
||||
LookupCNAME(string) (string, time.Duration, error)
|
||||
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ const (
|
|||
// Error types that can be used in ACME payloads
|
||||
const (
|
||||
ConnectionProblem = ProblemType("urn:acme:error:connection")
|
||||
DNSSECProblem = ProblemType("urn:acme:error:dnssec")
|
||||
MalformedProblem = ProblemType("urn:acme:error:malformed")
|
||||
ServerInternalProblem = ProblemType("urn:acme:error:serverInternal")
|
||||
TLSProblem = ProblemType("urn:acme:error:tls")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package mocks
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
|
|
@ -14,7 +15,6 @@ import (
|
|||
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/mattn/go-sqlite3"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
|
||||
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
)
|
||||
|
||||
// MockCADatabase is a mock
|
||||
|
|
@ -52,35 +52,30 @@ type MockDNS struct {
|
|||
}
|
||||
|
||||
// ExchangeOne is a mock
|
||||
func (mock *MockDNS) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt time.Duration, err error) {
|
||||
return m, 0, nil
|
||||
func (mock *MockDNS) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// LookupTXT is a mock
|
||||
func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error) {
|
||||
if hostname == "_acme-challenge.dnssec-failed.org" {
|
||||
return nil, 0, core.DNSSECError{}
|
||||
if hostname == "_acme-challenge.servfail.com" {
|
||||
return nil, 0, fmt.Errorf("SERVFAIL")
|
||||
}
|
||||
return []string{"hostname"}, 0, nil
|
||||
}
|
||||
|
||||
// LookupDNSSEC is a mock
|
||||
func (mock *MockDNS) LookupDNSSEC(m *dns.Msg) (*dns.Msg, time.Duration, error) {
|
||||
return m, 0, nil
|
||||
}
|
||||
|
||||
// LookupHost is a mock
|
||||
func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
|
||||
return nil, 0, nil
|
||||
func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, time.Duration, error) {
|
||||
return nil, 0, 0, nil
|
||||
}
|
||||
|
||||
// LookupCNAME is a mock
|
||||
func (mock *MockDNS) LookupCNAME(domain string) (string, error) {
|
||||
return "hostname", nil
|
||||
func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) {
|
||||
return "hostname", 0, nil
|
||||
}
|
||||
|
||||
// LookupCAA is a mock
|
||||
func (mock *MockDNS) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) {
|
||||
func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) {
|
||||
var results []*dns.CAA
|
||||
var record dns.CAA
|
||||
switch domain {
|
||||
|
|
@ -97,8 +92,8 @@ func (mock *MockDNS) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) {
|
|||
record.Tag = "issue"
|
||||
record.Value = "letsencrypt.org"
|
||||
results = append(results, &record)
|
||||
case "dnssec-failed.org":
|
||||
return results, core.DNSSECError{}
|
||||
case "servfail.com":
|
||||
return results, 0, fmt.Errorf("SERVFAIL")
|
||||
}
|
||||
return results, nil
|
||||
return results, 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,23 @@ type verificationRequestEvent struct {
|
|||
|
||||
// Validation methods
|
||||
|
||||
// setChallengeErrorFromDNSError checks the error returned from Lookup...
|
||||
// methods and tests if the error was an underlying net.OpError or an error
|
||||
// caused by resolver returning SERVFAIL or other invalid Rcodes and sets
|
||||
// the challenge.Error field accordingly.
|
||||
func setChallengeErrorFromDNSError(err error, challenge *core.Challenge) {
|
||||
challenge.Error = &core.ProblemDetails{Type: core.ConnectionProblem}
|
||||
if netErr, ok := err.(*net.OpError); ok {
|
||||
if netErr.Timeout() {
|
||||
challenge.Error.Detail = "DNS query timed out"
|
||||
} else if netErr.Temporary() {
|
||||
challenge.Error.Detail = "Temporary network connectivity error"
|
||||
}
|
||||
} else {
|
||||
challenge.Error.Detail = "Server failure at resolver"
|
||||
}
|
||||
}
|
||||
|
||||
func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
|
|
@ -296,19 +313,9 @@ func (va ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, in
|
|||
txts, _, err := va.DNSResolver.LookupTXT(challengeSubdomain)
|
||||
|
||||
if err != nil {
|
||||
if dnssecErr, ok := err.(core.DNSSECError); ok {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.DNSSECProblem,
|
||||
Detail: dnssecErr.Error(),
|
||||
}
|
||||
} else {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.ServerInternalProblem,
|
||||
Detail: "Unable to communicate with DNS server",
|
||||
}
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
va.log.Debug(fmt.Sprintf("DNS [%s] DNS failure: %s", identifier, err))
|
||||
setChallengeErrorFromDNSError(err, &challenge)
|
||||
va.log.Debug(fmt.Sprintf("%s [%s] DNS failure: %s", challenge.Type, identifier, err))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
|
|
@ -424,9 +431,9 @@ func newCAASet(CAAs []*dns.CAA) *CAASet {
|
|||
return &filtered
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) getCAASet(domain string, dnsResolver core.DNSResolver) (*CAASet, error) {
|
||||
domain = strings.TrimRight(domain, ".")
|
||||
splitDomain := strings.Split(domain, ".")
|
||||
func (va *ValidationAuthorityImpl) getCAASet(hostname string) (*CAASet, error) {
|
||||
hostname = strings.TrimRight(hostname, ".")
|
||||
splitDomain := strings.Split(hostname, ".")
|
||||
// RFC 6844 CAA set query sequence, 'x.y.z.com' => ['x.y.z.com', 'y.z.com', 'z.com']
|
||||
for i := range splitDomain {
|
||||
queryDomain := strings.Join(splitDomain[i:], ".")
|
||||
|
|
@ -437,7 +444,14 @@ func (va *ValidationAuthorityImpl) getCAASet(domain string, dnsResolver core.DNS
|
|||
|
||||
// Query CAA records for domain and its alias if it has a CNAME
|
||||
for _, alias := range []bool{false, true} {
|
||||
CAAs, err := va.DNSResolver.LookupCAA(queryDomain, alias)
|
||||
if alias {
|
||||
target, _, err := va.DNSResolver.LookupCNAME(queryDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queryDomain = target
|
||||
}
|
||||
CAAs, _, err := va.DNSResolver.LookupCAA(queryDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -455,8 +469,8 @@ func (va *ValidationAuthorityImpl) getCAASet(domain string, dnsResolver core.DNS
|
|||
// CheckCAARecords verifies that, if the indicated subscriber domain has any CAA
|
||||
// records, they authorize the configured CA domain to issue a certificate
|
||||
func (va *ValidationAuthorityImpl) CheckCAARecords(identifier core.AcmeIdentifier) (present, valid bool, err error) {
|
||||
domain := strings.ToLower(identifier.Value)
|
||||
caaSet, err := va.getCAASet(domain, va.DNSResolver)
|
||||
hostname := strings.ToLower(identifier.Value)
|
||||
caaSet, err := va.getCAASet(hostname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -472,7 +486,7 @@ func (va *ValidationAuthorityImpl) CheckCAARecords(identifier core.AcmeIdentifie
|
|||
} else if len(caaSet.Issue) > 0 || len(caaSet.Issuewild) > 0 {
|
||||
present = true
|
||||
var checkSet []*dns.CAA
|
||||
if strings.SplitN(domain, ".", 2)[0] == "*" {
|
||||
if strings.SplitN(hostname, ".", 2)[0] == "*" {
|
||||
checkSet = caaSet.Issuewild
|
||||
} else {
|
||||
checkSet = caaSet.Issue
|
||||
|
|
|
|||
|
|
@ -320,8 +320,8 @@ func TestDvsni(t *testing.T) {
|
|||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Domain name is invalid.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
|
||||
va.TestMode = true
|
||||
|
||||
va.TestMode = true
|
||||
chall.R = ba[5:]
|
||||
invalidChall, err = va.validateDvsni(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
|
|
@ -536,8 +536,8 @@ func TestCAAChecking(t *testing.T) {
|
|||
test.AssertEquals(t, caaTest.Valid, valid)
|
||||
}
|
||||
|
||||
present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: "dnssec-failed.org"})
|
||||
test.AssertError(t, err, "dnssec-failed.org")
|
||||
present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: "servfail.com"})
|
||||
test.AssertError(t, err, "servfail.com")
|
||||
test.Assert(t, !present, "Present should be false")
|
||||
test.Assert(t, !valid, "Valid should be false")
|
||||
}
|
||||
|
|
@ -630,7 +630,7 @@ func TestDNSValidationNotSane(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDNSValidationBadDNSSEC(t *testing.T) {
|
||||
func TestDNSValidationServFail(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
|
|
@ -638,21 +638,21 @@ func TestDNSValidationBadDNSSEC(t *testing.T) {
|
|||
|
||||
chalDNS := core.DNSChallenge()
|
||||
|
||||
badDNSSEC := core.AcmeIdentifier{
|
||||
badIdent := core.AcmeIdentifier{
|
||||
Type: core.IdentifierDNS,
|
||||
Value: "dnssec-failed.org",
|
||||
Value: "servfail.com",
|
||||
}
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
RegistrationID: 1,
|
||||
Identifier: badDNSSEC,
|
||||
Identifier: badIdent,
|
||||
Challenges: []core.Challenge{chalDNS},
|
||||
}
|
||||
va.validate(authz, 0)
|
||||
|
||||
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
|
||||
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
|
||||
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.DNSSECProblem)
|
||||
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationNoServer(t *testing.T) {
|
||||
|
|
@ -672,7 +672,7 @@ func TestDNSValidationNoServer(t *testing.T) {
|
|||
|
||||
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
|
||||
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
|
||||
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ServerInternalProblem)
|
||||
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
// TestDNSValidationLive is an integration test, depending on
|
||||
|
|
|
|||
Loading…
Reference in New Issue