Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
493 changes: 187 additions & 306 deletions bdns/dns.go

Large diffs are not rendered by default.

416 changes: 244 additions & 172 deletions bdns/dns_test.go

Large diffs are not rendered by default.

154 changes: 12 additions & 142 deletions bdns/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,159 +3,29 @@ package bdns
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"os"

"github.com/miekg/dns"

blog "github.com/letsencrypt/boulder/log"
)

// MockClient is a mock
type MockClient struct {
Log blog.Logger
}
type MockClient struct{}

// LookupTXT is a mock
func (mock *MockClient) LookupTXT(_ context.Context, hostname string) ([]string, ResolverAddrs, error) {
// Use the example account-specific label prefix derived from
// "https://example.com/acme/acct/ExampleAccount"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So stoked to move this action-at-a-distance to up-close-and-personal in the va package!

const accountLabelPrefix = "_ujmmovf2vn55tgye._acme-challenge"

if hostname == accountLabelPrefix+".servfail.com" {
// Mirror dns-01 servfail behaviour
return nil, ResolverAddrs{"MockClient"}, fmt.Errorf("SERVFAIL")
}
if hostname == accountLabelPrefix+".good-dns01.com" {
// Mirror dns-01 good record
// base64(sha256("LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
// + "." + "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"))
return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == accountLabelPrefix+".wrong-dns01.com" {
// Mirror dns-01 wrong record
return []string{"a"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == accountLabelPrefix+".wrong-many-dns01.com" {
// Mirror dns-01 wrong-many record
return []string{"a", "b", "c", "d", "e"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == accountLabelPrefix+".long-dns01.com" {
// Mirror dns-01 long record
return []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == accountLabelPrefix+".no-authority-dns01.com" {
// Mirror dns-01 no-authority good record
// base64(sha256("LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
// + "." + "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"))
return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == accountLabelPrefix+".empty-txts.com" {
// Mirror dns-01 zero TXT records
return []string{}, ResolverAddrs{"MockClient"}, nil
}

if hostname == "_acme-challenge.servfail.com" {
return nil, ResolverAddrs{"MockClient"}, fmt.Errorf("SERVFAIL")
}
if hostname == "_acme-challenge.good-dns01.com" {
// base64(sha256("LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
// + "." + "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"))
// expected token + test account jwk thumbprint
return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "_acme-challenge.wrong-dns01.com" {
return []string{"a"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "_acme-challenge.wrong-many-dns01.com" {
return []string{"a", "b", "c", "d", "e"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "_acme-challenge.long-dns01.com" {
return []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "_acme-challenge.no-authority-dns01.com" {
// base64(sha256("LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
// + "." + "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"))
// expected token + test account jwk thumbprint
return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, ResolverAddrs{"MockClient"}, nil
}
// empty-txts.com always returns zero TXT records
if hostname == "_acme-challenge.empty-txts.com" {
return []string{}, ResolverAddrs{"MockClient"}, nil
}

// Default fallback
return []string{"hostname"}, ResolverAddrs{"MockClient"}, nil
func (mock *MockClient) LookupTXT(_ context.Context, hostname string) (*Result[*dns.TXT], string, error) {
return nil, "MockClient", errors.New("unexpected LookupTXT call on test fake")
}

// makeTimeoutError returns a a net.OpError for which Timeout() returns true.
func makeTimeoutError() *net.OpError {
return &net.OpError{
Err: os.NewSyscallError("ugh timeout", timeoutError{}),
}
}

type timeoutError struct{}

func (t timeoutError) Error() string {
return "so sloooow"
}
func (t timeoutError) Timeout() bool {
return true
// LookupA is a fake
func (mock *MockClient) LookupA(_ context.Context, hostname string) (*Result[*dns.A], string, error) {
return nil, "MockClient", errors.New("unexpected LookupA call on test fake")
}

// LookupHost is a mock
func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]netip.Addr, ResolverAddrs, error) {
if hostname == "always.invalid" ||
hostname == "invalid.invalid" {
return []netip.Addr{}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "always.timeout" {
return []netip.Addr{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, "always.timeout", makeTimeoutError(), -1, nil}
}
if hostname == "always.error" {
err := &net.OpError{
Op: "read",
Net: "udp",
Err: errors.New("some net error"),
}
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
m.AuthenticatedData = true
m.SetEdns0(4096, false)
return []netip.Addr{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
}
if hostname == "id.mismatch" {
err := dns.ErrId
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
m.AuthenticatedData = true
m.SetEdns0(4096, false)
r := new(dns.Msg)
record := new(dns.A)
record.Hdr = dns.RR_Header{Name: dns.Fqdn(hostname), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
record.A = net.ParseIP("127.0.0.1")
r.Answer = append(r.Answer, record)
return []netip.Addr{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
}
// dual-homed host with an IPv6 and an IPv4 address
if hostname == "ipv4.and.ipv6.localhost" {
return []netip.Addr{
netip.MustParseAddr("::1"),
netip.MustParseAddr("127.0.0.1"),
}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "ipv6.localhost" {
return []netip.Addr{
netip.MustParseAddr("::1"),
}, ResolverAddrs{"MockClient"}, nil
}
return []netip.Addr{netip.MustParseAddr("127.0.0.1")}, ResolverAddrs{"MockClient"}, nil
// LookupAAAA is a fake
func (mock *MockClient) LookupAAAA(_ context.Context, hostname string) (*Result[*dns.AAAA], string, error) {
return nil, "MockClient", errors.New("unexpected LookupAAAA call on test fake")
}

// LookupCAA returns mock records for use in tests.
func (mock *MockClient) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, ResolverAddrs, error) {
return nil, "", ResolverAddrs{"MockClient"}, nil
// LookupCAA is a fake
func (mock *MockClient) LookupCAA(_ context.Context, domain string) (*Result[*dns.CAA], string, error) {
return nil, "MockClient", errors.New("unexpected LookupCAA call on test fake")
}
6 changes: 2 additions & 4 deletions bdns/problem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
"net/url"
"testing"

"github.com/letsencrypt/boulder/test"
"github.com/miekg/dns"

"github.com/letsencrypt/boulder/test"
)

func TestError(t *testing.T) {
Expand All @@ -17,9 +18,6 @@ func TestError(t *testing.T) {
expected string
}{
{
&Error{dns.TypeA, "hostname", makeTimeoutError(), -1, nil},
"DNS problem: query timed out looking up A for hostname",
}, {
&Error{dns.TypeMX, "hostname", &net.OpError{Err: errors.New("some net error")}, -1, nil},
"DNS problem: networking error looking up MX for hostname",
}, {
Expand Down
33 changes: 11 additions & 22 deletions cmd/boulder-va/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,28 +100,16 @@ func main() {
tlsConfig, err := c.VA.TLS.Load(scope)
cmd.FailOnError(err, "tlsConfig config")

var resolver bdns.Client
if !c.VA.DNSAllowLoopbackAddresses {
resolver = bdns.New(
c.VA.DNSTimeout.Duration,
servers,
scope,
clk,
c.VA.DNSTries,
c.VA.UserAgent,
logger,
tlsConfig)
} else {
resolver = bdns.NewTest(
c.VA.DNSTimeout.Duration,
servers,
scope,
clk,
c.VA.DNSTries,
c.VA.UserAgent,
logger,
tlsConfig)
}
resolver := bdns.New(
c.VA.DNSTimeout.Duration,
servers,
scope,
clk,
c.VA.DNSTries,
c.VA.UserAgent,
logger,
tlsConfig)

var remotes []va.RemoteVA
if len(c.VA.RemoteVAs) > 0 {
for _, rva := range c.VA.RemoteVAs {
Expand Down Expand Up @@ -155,6 +143,7 @@ func main() {
"",
iana.IsReservedAddr,
c.VA.SlowRemoteTimeout.Duration,
c.VA.DNSAllowLoopbackAddresses,
)
cmd.FailOnError(err, "Unable to create VA server")

Expand Down
32 changes: 10 additions & 22 deletions cmd/remoteva/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,28 +106,15 @@ func main() {
tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
}

var resolver bdns.Client
if !c.RVA.DNSAllowLoopbackAddresses {
resolver = bdns.New(
c.RVA.DNSTimeout.Duration,
servers,
scope,
clk,
c.RVA.DNSTries,
c.RVA.UserAgent,
logger,
tlsConfig)
} else {
resolver = bdns.NewTest(
c.RVA.DNSTimeout.Duration,
servers,
scope,
clk,
c.RVA.DNSTries,
c.RVA.UserAgent,
logger,
tlsConfig)
}
resolver := bdns.New(
c.RVA.DNSTimeout.Duration,
servers,
scope,
clk,
c.RVA.DNSTries,
c.RVA.UserAgent,
logger,
tlsConfig)

vai, err := va.NewValidationAuthorityImpl(
resolver,
Expand All @@ -142,6 +129,7 @@ func main() {
c.RVA.RIR,
iana.IsReservedAddr,
0,
c.RVA.DNSAllowLoopbackAddresses,
)
cmd.FailOnError(err, "Unable to create Remote-VA server")

Expand Down
16 changes: 10 additions & 6 deletions va/caa.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ type caaResult struct {
issuewild []*dns.CAA
criticalUnknown bool
dig string
resolvers bdns.ResolverAddrs
resolver string
err error
}

Expand Down Expand Up @@ -213,14 +213,18 @@ func (va *ValidationAuthorityImpl) parallelCAALookup(ctx context.Context, name s
// Start the concurrent DNS lookup.
wg.Add(1)
go func(name string, r *caaResult) {
defer wg.Done()
r.name = name
var records []*dns.CAA
records, r.dig, r.resolvers, r.err = va.dnsClient.LookupCAA(ctx, name)
if len(records) > 0 {
var records *bdns.Result[*dns.CAA]
records, r.resolver, r.err = va.dnsClient.LookupCAA(ctx, name)
if r.err != nil {
return
}
r.dig = records.String()
if len(records.Final) > 0 {
r.present = true
}
r.issue, r.issuewild, r.criticalUnknown = filterCAA(records)
wg.Done()
r.issue, r.issuewild, r.criticalUnknown = filterCAA(records.Final)
}(strings.Join(labels[i:], "."), &results[i])
}

Expand Down
Loading