Skip to content

Commit d7d4c73

Browse files
committed
Merge branch main into fix-issue-8540
2 parents 8dbcd99 + f876e77 commit d7d4c73

192 files changed

Lines changed: 6304 additions & 2903 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/boulder-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ jobs:
121121
persist-credentials: false
122122

123123
- name: Setup Go
124-
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
124+
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
125125
with:
126126
# When Go produces a security release, we want govulncheck to run
127127
# against the most recently released Go version.
@@ -147,7 +147,7 @@ jobs:
147147
persist-credentials: false
148148

149149
- name: Setup Go ${{ matrix.go-version }}
150-
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
150+
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
151151
with:
152152
go-version: ${{ matrix.go-version }}
153153
cache: false

.github/workflows/cps-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
pull-requests: write
1414
steps:
1515
- name: Setup Go
16-
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
16+
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
1717
with:
1818
go-version: "stable"
1919

bdns/dns.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"errors"
77
"fmt"
88
"io"
9-
"log/slog"
109
"net"
1110
"net/http"
1211
"strconv"
@@ -18,7 +17,7 @@ import (
1817
"github.com/prometheus/client_golang/prometheus"
1918
"github.com/prometheus/client_golang/prometheus/promauto"
2019

21-
"github.com/letsencrypt/boulder/blog"
20+
blog "github.com/letsencrypt/boulder/log"
2221
"github.com/letsencrypt/boulder/metrics"
2322
)
2423

@@ -227,12 +226,7 @@ func (c *impl) exchangeOne(ctx context.Context, hostname string, qtype uint16) (
227226
}).Observe(rtt.Seconds())
228227

229228
if err != nil {
230-
c.log.Info(ctx, "logDNSError",
231-
slog.String("chosenServer", chosenServer),
232-
slog.String("hostname", hostname),
233-
slog.String("qtype", qtypeStr),
234-
blog.Error(err),
235-
)
229+
c.log.Infof("logDNSError chosenServer=[%s] hostname=[%s] queryType=[%s] err=[%s]", chosenServer, hostname, qtypeStr, err)
236230

237231
// Check if the error is a network timeout, rather than a local context
238232
// timeout. If it is, retry instead of giving up.
@@ -310,7 +304,9 @@ func (c *impl) LookupCAA(ctx context.Context, hostname string) (*Result[*dns.CAA
310304
// for DNS-01 challenge) and then removed after validation but before CAA
311305
// rechecking. But allow NXDOMAIN for TLDs to fall through to the error code
312306
// below, so we don't issue for gTLDs that have been removed by ICANN.
313-
if err == nil && resp.Rcode == dns.RcodeNameError && strings.Contains(hostname, ".") {
307+
// Truncated responses also fall through, since we can't definitively trust
308+
// an incomplete response to accurately reflect an NXDOMAIN.
309+
if err == nil && !resp.Truncated && resp.Rcode == dns.RcodeNameError && strings.Contains(hostname, ".") {
314310
return resultFromMsg[*dns.CAA](resp), resolver, nil
315311
}
316312

bdns/dns_test.go

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"github.com/miekg/dns"
2323
"github.com/prometheus/client_golang/prometheus"
2424

25-
"github.com/letsencrypt/boulder/blog"
25+
blog "github.com/letsencrypt/boulder/log"
2626
"github.com/letsencrypt/boulder/metrics"
2727
"github.com/letsencrypt/boulder/test"
2828
)
@@ -283,7 +283,7 @@ func TestDNSNoServers(t *testing.T) {
283283
staticProvider, err := NewStaticProvider([]string{})
284284
test.AssertNotError(t, err, "Got error creating StaticProvider")
285285

286-
obj := New(time.Hour, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
286+
obj := New(time.Hour, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
287287

288288
_, resolver, err := obj.LookupA(context.Background(), "letsencrypt.org")
289289
test.AssertEquals(t, resolver, "")
@@ -306,7 +306,7 @@ func TestDNSOneServer(t *testing.T) {
306306
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
307307
test.AssertNotError(t, err, "Got error creating StaticProvider")
308308

309-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
309+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
310310

311311
_, resolver, err := obj.LookupA(context.Background(), "letsencrypt.org")
312312
test.AssertNotError(t, err, "No message")
@@ -317,7 +317,7 @@ func TestDNSDuplicateServers(t *testing.T) {
317317
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr, dnsLoopbackAddr})
318318
test.AssertNotError(t, err, "Got error creating StaticProvider")
319319

320-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
320+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
321321

322322
_, resolver, err := obj.LookupA(context.Background(), "letsencrypt.org")
323323
test.AssertNotError(t, err, "No message")
@@ -328,7 +328,7 @@ func TestDNSServFail(t *testing.T) {
328328
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
329329
test.AssertNotError(t, err, "Got error creating StaticProvider")
330330

331-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
331+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
332332
bad := "servfail.com"
333333

334334
_, _, err = obj.LookupTXT(context.Background(), "servfail.com")
@@ -348,7 +348,7 @@ func TestDNSLookupTXT(t *testing.T) {
348348
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
349349
test.AssertNotError(t, err, "Got error creating StaticProvider")
350350

351-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
351+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
352352

353353
_, _, err = obj.LookupTXT(context.Background(), "letsencrypt.org")
354354
test.AssertNotError(t, err, "No message")
@@ -363,7 +363,7 @@ func TestDNSLookupA(t *testing.T) {
363363
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
364364
test.AssertNotError(t, err, "Got error creating StaticProvider")
365365

366-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
366+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
367367

368368
for _, tc := range []struct {
369369
name string
@@ -448,7 +448,7 @@ func TestDNSLookupAAAA(t *testing.T) {
448448
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
449449
test.AssertNotError(t, err, "Got error creating StaticProvider")
450450

451-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
451+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
452452

453453
for _, tc := range []struct {
454454
name string
@@ -533,7 +533,7 @@ func TestDNSNXDOMAIN(t *testing.T) {
533533
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
534534
test.AssertNotError(t, err, "Got error creating StaticProvider")
535535

536-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
536+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
537537
hostname := "nxdomain.letsencrypt.org"
538538

539539
_, _, err = obj.LookupA(context.Background(), hostname)
@@ -543,15 +543,15 @@ func TestDNSNXDOMAIN(t *testing.T) {
543543
test.AssertContains(t, err.Error(), "NXDOMAIN looking up AAAA for")
544544

545545
_, _, err = obj.LookupTXT(context.Background(), hostname)
546-
expected := Error{dns.TypeTXT, hostname, nil, dns.RcodeNameError, nil}
546+
expected := Error{dns.TypeTXT, hostname, nil, dns.RcodeNameError, nil, false}
547547
test.AssertDeepEquals(t, err, expected)
548548
}
549549

550550
func TestDNSLookupCAA(t *testing.T) {
551551
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
552552
test.AssertNotError(t, err, "Got error creating StaticProvider")
553553

554-
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
554+
obj := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.UseMock(), tlsConfig)
555555
removeIDExp := regexp.MustCompile(" id: [[:digit:]]+")
556556

557557
caas, resolver, err := obj.LookupCAA(context.Background(), "bracewel.net")
@@ -759,7 +759,7 @@ func TestRetry(t *testing.T) {
759759
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
760760
test.AssertNotError(t, err, "Got error creating StaticProvider")
761761

762-
testClient := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), tc.maxTries, "", blog.NewMock(), tlsConfig)
762+
testClient := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), tc.maxTries, "", blog.UseMock(), tlsConfig)
763763
dr := testClient.(*impl)
764764
dr.exchanger = tc.te
765765
_, _, err = dr.LookupTXT(context.Background(), "example.com")
@@ -796,7 +796,7 @@ func TestRetryMetrics(t *testing.T) {
796796
// context itself being cancelled. It should never see the error in the
797797
// testExchanger, because the fake exchanger (like the real http package)
798798
// checks for cancellation before doing any work.
799-
testClient := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 3, "", blog.NewMock(), tlsConfig)
799+
testClient := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 3, "", blog.UseMock(), tlsConfig)
800800
dr := testClient.(*impl)
801801
dr.exchanger = &testExchanger{errs: []error{errors.New("oops")}}
802802
ctx, cancel := context.WithCancel(t.Context())
@@ -815,7 +815,7 @@ func TestRetryMetrics(t *testing.T) {
815815

816816
// Same as above, except rather than cancelling the context ourselves, we
817817
// let the go runtime cancel it as a result of a deadline in the past.
818-
testClient = New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 3, "", blog.NewMock(), tlsConfig)
818+
testClient = New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 3, "", blog.UseMock(), tlsConfig)
819819
dr = testClient.(*impl)
820820
dr.exchanger = &testExchanger{errs: []error{errors.New("oops")}}
821821
ctx, cancel = context.WithTimeout(t.Context(), -10*time.Hour)
@@ -883,7 +883,7 @@ func TestRotateServerOnErr(t *testing.T) {
883883
test.AssertNotError(t, err, "Got error creating StaticProvider")
884884

885885
maxTries := 5
886-
client := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), maxTries, "", blog.NewMock(), tlsConfig)
886+
client := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), maxTries, "", blog.UseMock(), tlsConfig)
887887

888888
// Configure a mock exchanger that will always return a retryable error for
889889
// servers A and B. This will force server "[2606:4700:4700::1111]:53" to do
@@ -948,7 +948,7 @@ func TestDOHMetric(t *testing.T) {
948948
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
949949
test.AssertNotError(t, err, "Got error creating StaticProvider")
950950

951-
testClient := New(time.Second*11, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 0, "", blog.NewMock(), tlsConfig)
951+
testClient := New(time.Second*11, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 0, "", blog.UseMock(), tlsConfig)
952952
resolver := testClient.(*impl)
953953
resolver.exchanger = &dohAlwaysRetryExchanger{err: &url.Error{Op: "read", Err: testTimeoutError(true)}}
954954

@@ -961,3 +961,39 @@ func TestDOHMetric(t *testing.T) {
961961
// Now, we should count 1 "out of retries" errors.
962962
test.AssertMetricWithLabelsEquals(t, resolver.timeoutCounter, prometheus.Labels{"qtype": "None", "type": "out of retries", "resolver": "127.0.0.1", "isTLD": "false"}, 1)
963963
}
964+
965+
// truncatedExchanger returns a truncated (TC bit set) response with the given
966+
// Rcode. If a caller failed to check for truncation on a CAA query, it would
967+
// otherwise be fooled into trusting an incomplete set of records, potentially
968+
// missing an issue record that would forbid issuance.
969+
type truncatedExchanger struct {
970+
rcode int
971+
}
972+
973+
func (te truncatedExchanger) ExchangeContext(_ context.Context, m *dns.Msg, _ string) (*dns.Msg, time.Duration, error) {
974+
resp := new(dns.Msg)
975+
resp.SetReply(m)
976+
resp.Rcode = te.rcode
977+
resp.Truncated = true
978+
return resp, time.Millisecond, nil
979+
}
980+
981+
func TestDNSCAATruncatedResponse(t *testing.T) {
982+
staticProvider, err := NewStaticProvider([]string{dnsLoopbackAddr})
983+
test.AssertNotError(t, err, "Got error creating StaticProvider")
984+
985+
client := New(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, "", blog.NewMock(), tlsConfig)
986+
client.(*impl).exchanger = truncatedExchanger{rcode: dns.RcodeSuccess}
987+
988+
_, _, err = client.LookupCAA(context.Background(), "example.com")
989+
test.AssertError(t, err, "expected error for truncated CAA response")
990+
test.AssertContains(t, err.Error(), "response was truncated")
991+
992+
// A truncated NXDOMAIN response must not be treated as the usual
993+
// NXDOMAIN-as-empty-CAA-set special case for non-TLD names: we can't
994+
// trust an incomplete response to accurately reflect an NXDOMAIN.
995+
client.(*impl).exchanger = truncatedExchanger{rcode: dns.RcodeNameError}
996+
_, _, err = client.LookupCAA(context.Background(), "nonexistent.letsencrypt.org")
997+
test.AssertError(t, err, "expected error for truncated CAA response, even when NXDOMAIN-shaped")
998+
test.AssertContains(t, err.Error(), "response was truncated")
999+
}

bdns/problem.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ import (
1414
type Error struct {
1515
recordType uint16
1616
hostname string
17-
// Exactly one of rCode or underlying should be set.
17+
// Exactly one of underlying, rCode, or truncated should be set.
1818
underlying error
1919
rCode int
2020

2121
// Optional: If the resolver returned extended error information, it will be stored here.
2222
// https://www.rfc-editor.org/rfc/rfc8914
2323
extended *dns.EDNS0_EDE
24+
25+
// truncated is set when the response to a CAA query had the TC bit set. We
26+
// don't implement fallback to TCP, so we treat a truncated response as an
27+
// error rather than risk silently acting on an incomplete set of records.
28+
truncated bool
2429
}
2530

2631
// extendedDNSError returns non-nil if the input message contained an OPT RR
@@ -39,8 +44,10 @@ func extendedDNSError(msg *dns.Msg) *dns.EDNS0_EDE {
3944
return nil
4045
}
4146

42-
// wrapErr returns a non-nil error if err is non-nil or if resp.Rcode is not dns.RcodeSuccess.
43-
// The error includes appropriate details about the DNS query that failed.
47+
// wrapErr returns a non-nil error if err is non-nil, if resp.Rcode is not
48+
// dns.RcodeSuccess, or if resp was truncated (the TC bit was set) for a CAA
49+
// query. The error includes appropriate details about the DNS query that
50+
// failed.
4451
func wrapErr(queryType uint16, hostname string, resp *dns.Msg, err error) error {
4552
if err != nil {
4653
return Error{
@@ -50,6 +57,13 @@ func wrapErr(queryType uint16, hostname string, resp *dns.Msg, err error) error
5057
extended: nil,
5158
}
5259
}
60+
if queryType == dns.TypeCAA && resp.Truncated {
61+
return Error{
62+
recordType: queryType,
63+
hostname: hostname,
64+
truncated: true,
65+
}
66+
}
5367
if resp.Rcode != dns.RcodeSuccess {
5468
return Error{
5569
recordType: queryType,
@@ -120,6 +134,8 @@ func (d Error) Error() string {
120134
} else {
121135
detail = detailServerFailure
122136
}
137+
} else if d.truncated {
138+
detail = detailDNSTruncated
123139
} else if d.rCode != dns.RcodeSuccess {
124140
detail = dns.RcodeToString[d.rCode]
125141
if explanation, ok := rcodeExplanations[d.rCode]; ok {
@@ -150,6 +166,7 @@ const detailDNSTimeout = "query timed out"
150166
const detailCanceled = "query timed out (and was canceled)"
151167
const detailDNSNetFailure = "networking error"
152168
const detailServerFailure = "server failure at resolver"
169+
const detailDNSTruncated = "response was truncated"
153170

154171
// rcodeExplanations provide additional friendly explanatory text to be included in DNS
155172
// error messages, for select inscrutable RCODEs.

bdns/problem_test.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,41 +18,44 @@ func TestError(t *testing.T) {
1818
expected string
1919
}{
2020
{
21-
&Error{dns.TypeMX, "hostname", &net.OpError{Err: errors.New("some net error")}, -1, nil},
21+
&Error{dns.TypeMX, "hostname", &net.OpError{Err: errors.New("some net error")}, -1, nil, false},
2222
"DNS problem: networking error looking up MX for hostname",
2323
}, {
24-
&Error{dns.TypeTXT, "hostname", nil, dns.RcodeNameError, nil},
24+
&Error{dns.TypeTXT, "hostname", nil, dns.RcodeNameError, nil, false},
2525
"DNS problem: NXDOMAIN looking up TXT for hostname - check that a DNS record exists for this domain",
2626
}, {
27-
&Error{dns.TypeTXT, "hostname", context.DeadlineExceeded, -1, nil},
27+
&Error{dns.TypeTXT, "hostname", context.DeadlineExceeded, -1, nil, false},
2828
"DNS problem: query timed out looking up TXT for hostname",
2929
}, {
30-
&Error{dns.TypeTXT, "hostname", context.Canceled, -1, nil},
30+
&Error{dns.TypeTXT, "hostname", context.Canceled, -1, nil, false},
3131
"DNS problem: query timed out (and was canceled) looking up TXT for hostname",
3232
}, {
33-
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeServerFailure, nil},
33+
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeServerFailure, nil, false},
3434
"DNS problem: SERVFAIL looking up CAA for hostname - the domain's nameservers may be malfunctioning",
3535
}, {
36-
&Error{dns.TypeA, "hostname", nil, dns.RcodeServerFailure, &dns.EDNS0_EDE{InfoCode: 1, ExtraText: "oh no"}},
36+
&Error{dns.TypeA, "hostname", nil, dns.RcodeServerFailure, &dns.EDNS0_EDE{InfoCode: 1, ExtraText: "oh no"}, false},
3737
"DNS problem: looking up A for hostname: DNSSEC: Unsupported DNSKEY Algorithm: oh no",
3838
}, {
39-
&Error{dns.TypeA, "hostname", nil, dns.RcodeServerFailure, &dns.EDNS0_EDE{InfoCode: 6, ExtraText: ""}},
39+
&Error{dns.TypeA, "hostname", nil, dns.RcodeServerFailure, &dns.EDNS0_EDE{InfoCode: 6, ExtraText: ""}, false},
4040
"DNS problem: looking up A for hostname: DNSSEC: Bogus",
4141
}, {
42-
&Error{dns.TypeA, "hostname", nil, dns.RcodeServerFailure, &dns.EDNS0_EDE{InfoCode: 1337, ExtraText: "mysterious"}},
42+
&Error{dns.TypeA, "hostname", nil, dns.RcodeServerFailure, &dns.EDNS0_EDE{InfoCode: 1337, ExtraText: "mysterious"}, false},
4343
"DNS problem: looking up A for hostname: Unknown Extended DNS Error code 1337: mysterious",
4444
}, {
45-
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeServerFailure, nil},
45+
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeServerFailure, nil, false},
4646
"DNS problem: SERVFAIL looking up CAA for hostname - the domain's nameservers may be malfunctioning",
4747
}, {
48-
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeServerFailure, nil},
48+
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeServerFailure, nil, false},
4949
"DNS problem: SERVFAIL looking up CAA for hostname - the domain's nameservers may be malfunctioning",
5050
}, {
51-
&Error{dns.TypeA, "hostname", nil, dns.RcodeFormatError, nil},
51+
&Error{dns.TypeA, "hostname", nil, dns.RcodeFormatError, nil, false},
5252
"DNS problem: FORMERR looking up A for hostname",
5353
}, {
54-
&Error{dns.TypeA, "hostname", &url.Error{Op: "GET", URL: "https://example.com/", Err: dohTimeoutError{}}, -1, nil},
54+
&Error{dns.TypeA, "hostname", &url.Error{Op: "GET", URL: "https://example.com/", Err: dohTimeoutError{}}, -1, nil, false},
5555
"DNS problem: query timed out looking up A for hostname",
56+
}, {
57+
&Error{dns.TypeCAA, "hostname", nil, dns.RcodeSuccess, nil, true},
58+
"DNS problem: response was truncated looking up CAA for hostname",
5659
},
5760
}
5861
for _, tc := range testCases {
@@ -87,4 +90,14 @@ func TestWrapErr(t *testing.T) {
8790
MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess},
8891
}, errors.New("oh no"))
8992
test.AssertError(t, err, "expected error")
93+
94+
// A truncated response should be treated as an error, even though its
95+
// Rcode is RcodeSuccess: a truncated CAA response could be silently
96+
// missing the issue/issuewild records that would otherwise forbid
97+
// issuance.
98+
err = wrapErr(dns.TypeCAA, "hostname", &dns.Msg{
99+
MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess, Truncated: true},
100+
}, nil)
101+
test.AssertError(t, err, "expected error for truncated response")
102+
test.AssertContains(t, err.Error(), "response was truncated")
90103
}

0 commit comments

Comments
 (0)