1 package va
2
3 import (
4 "context"
5 "crypto/rsa"
6 "encoding/base64"
7 "errors"
8 "fmt"
9 "math/big"
10 "net"
11 "net/http"
12 "net/http/httptest"
13 "os"
14 "strings"
15 "sync"
16 "syscall"
17 "testing"
18 "time"
19
20 "github.com/jmhodges/clock"
21 "github.com/letsencrypt/boulder/bdns"
22 "github.com/letsencrypt/boulder/core"
23 corepb "github.com/letsencrypt/boulder/core/proto"
24 "github.com/letsencrypt/boulder/features"
25 "github.com/letsencrypt/boulder/identifier"
26 blog "github.com/letsencrypt/boulder/log"
27 "github.com/letsencrypt/boulder/metrics"
28 "github.com/letsencrypt/boulder/probs"
29 "github.com/letsencrypt/boulder/test"
30 vapb "github.com/letsencrypt/boulder/va/proto"
31 "github.com/prometheus/client_golang/prometheus"
32 "google.golang.org/grpc"
33 "gopkg.in/go-jose/go-jose.v2"
34 )
35
36 func TestImplementation(t *testing.T) {
37 test.AssertImplementsGRPCServer(t, &ValidationAuthorityImpl{}, vapb.UnimplementedVAServer{})
38 test.AssertImplementsGRPCServer(t, &ValidationAuthorityImpl{}, vapb.UnimplementedCAAServer{})
39 }
40
41 var expectedToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
42 var expectedKeyAuthorization = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0.9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"
43
44 func bigIntFromB64(b64 string) *big.Int {
45 bytes, _ := base64.URLEncoding.DecodeString(b64)
46 x := big.NewInt(0)
47 x.SetBytes(bytes)
48 return x
49 }
50
51 func intFromB64(b64 string) int {
52 return int(bigIntFromB64(b64).Int64())
53 }
54
55 var n = bigIntFromB64("n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw==")
56 var e = intFromB64("AQAB")
57 var d = bigIntFromB64("bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ==")
58 var p = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=")
59 var q = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=")
60
61 var TheKey = rsa.PrivateKey{
62 PublicKey: rsa.PublicKey{N: n, E: e},
63 D: d,
64 Primes: []*big.Int{p, q},
65 }
66
67 var accountKey = &jose.JSONWebKey{Key: TheKey.Public()}
68
69
70 func dnsi(hostname string) identifier.ACMEIdentifier {
71 return identifier.DNSIdentifier(hostname)
72 }
73
74 var ctx context.Context
75
76 func TestMain(m *testing.M) {
77 var cancel context.CancelFunc
78 ctx, cancel = context.WithTimeout(context.Background(), 10*time.Minute)
79 ret := m.Run()
80 cancel()
81 os.Exit(ret)
82 }
83
84 var accountURIPrefixes = []string{"http://boulder.service.consul:4000/acme/reg/"}
85
86 func createValidationRequest(domain string, challengeType core.AcmeChallenge) *vapb.PerformValidationRequest {
87 return &vapb.PerformValidationRequest{
88 Domain: domain,
89 Challenge: &corepb.Challenge{
90 Type: string(challengeType),
91 Status: string(core.StatusPending),
92 Token: expectedToken,
93 Validationrecords: nil,
94 KeyAuthorization: expectedKeyAuthorization,
95 },
96 Authz: &vapb.AuthzMeta{
97 Id: "",
98 RegID: 1,
99 },
100 }
101 }
102
103 func createChallenge(challengeType core.AcmeChallenge) core.Challenge {
104 return core.Challenge{
105 Type: challengeType,
106 Status: core.StatusPending,
107 Token: expectedToken,
108 ValidationRecord: []core.ValidationRecord{},
109 ProvidedKeyAuthorization: expectedKeyAuthorization,
110 }
111 }
112
113
114
115 func setChallengeToken(ch *core.Challenge, token string) {
116 ch.Token = token
117 ch.ProvidedKeyAuthorization = token + ".9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"
118 }
119
120 func setup(srv *httptest.Server, maxRemoteFailures int, userAgent string, remoteVAs []RemoteVA) (*ValidationAuthorityImpl, *blog.Mock) {
121 features.Reset()
122 fc := clock.NewFake()
123
124 logger := blog.NewMock()
125
126 if userAgent == "" {
127 userAgent = "user agent 1.0"
128 }
129
130 va, err := NewValidationAuthorityImpl(
131 &bdns.MockClient{Log: logger},
132 nil,
133 maxRemoteFailures,
134 userAgent,
135 "letsencrypt.org",
136 metrics.NoopRegisterer,
137 fc,
138 logger,
139 accountURIPrefixes,
140 )
141
142
143
144 if srv != nil {
145 port := getPort(srv)
146 va.httpPort = port
147 va.tlsPort = port
148 }
149
150 if err != nil {
151 panic(fmt.Sprintf("Failed to create validation authority: %v", err))
152 }
153 if remoteVAs != nil {
154 va.remoteVAs = remoteVAs
155 }
156 return va, logger
157 }
158
159 func setupRemote(srv *httptest.Server, userAgent string) vapb.VAClient {
160 innerVA, _ := setup(srv, 0, userAgent, nil)
161 return &localRemoteVA{remote: *innerVA}
162 }
163
164 type multiSrv struct {
165 *httptest.Server
166
167 mu sync.Mutex
168 allowedUAs map[string]bool
169 }
170
171 func (s *multiSrv) setAllowedUAs(allowedUAs map[string]bool) {
172 s.mu.Lock()
173 defer s.mu.Unlock()
174 s.allowedUAs = allowedUAs
175 }
176
177 const slowRemoteSleepMillis = 1000
178
179 func httpMultiSrv(t *testing.T, token string, allowedUAs map[string]bool) *multiSrv {
180 t.Helper()
181 m := http.NewServeMux()
182
183 server := httptest.NewUnstartedServer(m)
184 ms := &multiSrv{server, sync.Mutex{}, allowedUAs}
185
186 m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
187 if r.UserAgent() == "slow remote" {
188 time.Sleep(slowRemoteSleepMillis)
189 }
190 ms.mu.Lock()
191 defer ms.mu.Unlock()
192 if ms.allowedUAs[r.UserAgent()] {
193 ch := core.Challenge{Token: token}
194 keyAuthz, _ := ch.ExpectedKeyAuthorization(accountKey)
195 fmt.Fprint(w, keyAuthz, "\n\r \t")
196 } else {
197 fmt.Fprint(w, "???")
198 }
199 })
200
201 ms.Start()
202 return ms
203 }
204
205
206
207 type cancelledVA struct{}
208
209 func (v cancelledVA) PerformValidation(_ context.Context, _ *vapb.PerformValidationRequest, _ ...grpc.CallOption) (*vapb.ValidationResult, error) {
210 return nil, context.Canceled
211 }
212
213
214
215 type brokenRemoteVA struct{}
216
217
218
219 var errBrokenRemoteVA = errors.New("brokenRemoteVA is broken")
220
221
222 func (b brokenRemoteVA) PerformValidation(_ context.Context, _ *vapb.PerformValidationRequest, _ ...grpc.CallOption) (*vapb.ValidationResult, error) {
223 return nil, errBrokenRemoteVA
224 }
225
226
227
228
229 type localRemoteVA struct {
230 remote ValidationAuthorityImpl
231 }
232
233 func (lrva localRemoteVA) PerformValidation(ctx context.Context, req *vapb.PerformValidationRequest, _ ...grpc.CallOption) (*vapb.ValidationResult, error) {
234 return lrva.remote.PerformValidation(ctx, req)
235 }
236
237 func TestValidateMalformedChallenge(t *testing.T) {
238 va, _ := setup(nil, 0, "", nil)
239
240 _, prob := va.validateChallenge(ctx, dnsi("example.com"), createChallenge("fake-type-01"))
241
242 test.AssertEquals(t, prob.Type, probs.MalformedProblem)
243 }
244
245 func TestPerformValidationInvalid(t *testing.T) {
246 va, _ := setup(nil, 0, "", nil)
247
248 req := createValidationRequest("foo.com", core.ChallengeTypeDNS01)
249 res, _ := va.PerformValidation(context.Background(), req)
250 test.Assert(t, res.Problems != nil, "validation succeeded")
251
252 test.AssertMetricWithLabelsEquals(t, va.metrics.validationTime, prometheus.Labels{
253 "type": "dns-01",
254 "result": "invalid",
255 "problem_type": "unauthorized",
256 }, 1)
257 }
258
259 func TestPerformValidationValid(t *testing.T) {
260 va, mockLog := setup(nil, 0, "", nil)
261
262
263 req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
264 res, _ := va.PerformValidation(context.Background(), req)
265 test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
266
267 test.AssertMetricWithLabelsEquals(t, va.metrics.validationTime, prometheus.Labels{
268 "type": "dns-01",
269 "result": "valid",
270 "problem_type": "",
271 }, 1)
272 resultLog := mockLog.GetAllMatching(`Validation result`)
273 if len(resultLog) != 1 {
274 t.Fatalf("Wrong number of matching lines for 'Validation result'")
275 }
276 if !strings.Contains(resultLog[0], `"Hostname":"good-dns01.com"`) {
277 t.Error("PerformValidation didn't log validation hostname.")
278 }
279 }
280
281
282
283 func TestPerformValidationWildcard(t *testing.T) {
284 va, mockLog := setup(nil, 0, "", nil)
285
286
287 req := createValidationRequest("*.good-dns01.com", core.ChallengeTypeDNS01)
288
289 res, _ := va.PerformValidation(context.Background(), req)
290 test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
291
292 test.AssertMetricWithLabelsEquals(t, va.metrics.validationTime, prometheus.Labels{
293 "type": "dns-01",
294 "result": "valid",
295 "problem_type": "",
296 }, 1)
297 resultLog := mockLog.GetAllMatching(`Validation result`)
298 if len(resultLog) != 1 {
299 t.Fatalf("Wrong number of matching lines for 'Validation result'")
300 }
301
302
303 if !strings.Contains(resultLog[0], `"Hostname":"*.good-dns01.com"`) {
304 t.Errorf("PerformValidation didn't log correct validation hostname.")
305 }
306
307
308 if !strings.Contains(resultLog[0], `"hostname":"good-dns01.com"`) {
309 t.Errorf("PerformValidation didn't log correct validation record hostname.")
310 }
311 }
312
313 func TestDCVAndCAASequencing(t *testing.T) {
314 va, mockLog := setup(nil, 0, "", nil)
315
316
317
318 req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
319 res, err := va.PerformValidation(context.Background(), req)
320 test.AssertNotError(t, err, "performing validation")
321 test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
322 caaLog := mockLog.GetAllMatching(`Checked CAA records for`)
323 test.AssertEquals(t, len(caaLog), 1)
324
325 _ = features.Set(map[string]bool{features.CAAAfterValidation.String(): true})
326 defer features.Reset()
327
328
329
330 mockLog.Clear()
331 req = createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
332 res, err = va.PerformValidation(context.Background(), req)
333 test.AssertNotError(t, err, "performing validation")
334 test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
335 caaLog = mockLog.GetAllMatching(`Checked CAA records for`)
336 test.AssertEquals(t, len(caaLog), 1)
337
338
339
340 mockLog.Clear()
341 req = createValidationRequest("bad-dns01.com", core.ChallengeTypeDNS01)
342 res, err = va.PerformValidation(context.Background(), req)
343 test.AssertNotError(t, err, "performing validation")
344 test.Assert(t, res.Problems != nil, "validation succeeded")
345 caaLog = mockLog.GetAllMatching(`Checked CAA records for`)
346 test.AssertEquals(t, len(caaLog), 0)
347 }
348
349 func TestMultiVA(t *testing.T) {
350
351 req := createValidationRequest("localhost", core.ChallengeTypeHTTP01)
352
353 const (
354 remoteUA1 = "remote 1"
355 remoteUA2 = "remote 2"
356 localUA = "local 1"
357 )
358 allowedUAs := map[string]bool{
359 localUA: true,
360 remoteUA1: true,
361 remoteUA2: true,
362 }
363
364
365 ms := httpMultiSrv(t, expectedToken, allowedUAs)
366 defer ms.Close()
367
368 remoteVA1 := setupRemote(ms.Server, remoteUA1)
369 remoteVA2 := setupRemote(ms.Server, remoteUA2)
370
371 remoteVAs := []RemoteVA{
372 {remoteVA1, remoteUA1},
373 {remoteVA2, remoteUA2},
374 }
375
376 enforceMultiVA := map[string]bool{
377 "EnforceMultiVA": true,
378 }
379 enforceMultiVAFullResults := map[string]bool{
380 "EnforceMultiVA": true,
381 "MultiVAFullResults": true,
382 }
383 noEnforceMultiVA := map[string]bool{
384 "EnforceMultiVA": false,
385 }
386 noEnforceMultiVAFullResults := map[string]bool{
387 "EnforceMultiVA": false,
388 "MultiVAFullResults": true,
389 }
390
391 unauthorized := probs.Unauthorized(fmt.Sprintf(
392 `The key authorization file from the server did not match this challenge. Expected %q (got "???")`,
393 expectedKeyAuthorization))
394
395 expectedInternalErrLine := fmt.Sprintf(
396 `ERR: \[AUDIT\] Remote VA "broken".PerformValidation failed: %s`,
397 errBrokenRemoteVA.Error())
398
399 testCases := []struct {
400 Name string
401 RemoteVAs []RemoteVA
402 AllowedUAs map[string]bool
403 Features map[string]bool
404 ExpectedProb *probs.ProblemDetails
405 ExpectedLog string
406 }{
407 {
408
409 Name: "Local and remote VAs OK, enforce multi VA",
410 RemoteVAs: remoteVAs,
411 AllowedUAs: allowedUAs,
412 Features: enforceMultiVA,
413 },
414 {
415
416 Name: "Local and remote VAs OK, no enforce multi VA",
417 RemoteVAs: remoteVAs,
418 AllowedUAs: allowedUAs,
419 Features: noEnforceMultiVA,
420 },
421 {
422
423 Name: "Local VA bad, remote VAs OK, no enforce multi VA",
424 RemoteVAs: remoteVAs,
425 AllowedUAs: map[string]bool{remoteUA1: true, remoteUA2: true},
426 Features: noEnforceMultiVA,
427 ExpectedProb: unauthorized,
428 },
429 {
430
431 Name: "Local VA bad, remote VAs OK, enforce multi VA",
432 RemoteVAs: remoteVAs,
433 AllowedUAs: map[string]bool{remoteUA1: true, remoteUA2: true},
434 Features: enforceMultiVA,
435 ExpectedProb: unauthorized,
436 },
437 {
438
439 Name: "Local VA ok, remote VA internal err, enforce multi VA",
440 RemoteVAs: []RemoteVA{
441 {remoteVA1, remoteUA1},
442 {&brokenRemoteVA{}, "broken"},
443 },
444 AllowedUAs: allowedUAs,
445 Features: enforceMultiVA,
446 ExpectedProb: probs.ServerInternal("During secondary validation: Remote PerformValidation RPC failed"),
447
448 ExpectedLog: expectedInternalErrLine,
449 },
450 {
451
452
453 Name: "Local VA ok, remote VA internal err, no enforce multi VA",
454 RemoteVAs: []RemoteVA{
455 {remoteVA1, remoteUA1},
456 {&brokenRemoteVA{}, "broken"},
457 },
458 AllowedUAs: allowedUAs,
459 Features: noEnforceMultiVA,
460
461
462
463 },
464 {
465
466
467 Name: "Local VA and one remote VA OK, no enforce multi VA",
468 RemoteVAs: remoteVAs,
469 AllowedUAs: map[string]bool{localUA: true, remoteUA2: true},
470 Features: noEnforceMultiVA,
471 },
472 {
473
474
475 Name: "Local VA and one remote VA OK, enforce multi VA",
476 RemoteVAs: remoteVAs,
477 AllowedUAs: map[string]bool{localUA: true, remoteUA2: true},
478 Features: enforceMultiVA,
479 ExpectedProb: probs.Unauthorized(fmt.Sprintf(
480 `During secondary validation: The key authorization file from the server did not match this challenge. Expected %q (got "???")`,
481 expectedKeyAuthorization)),
482 },
483 {
484
485 Name: "Local VA and one remote VA OK, one cancelled VA, enforce multi VA",
486 RemoteVAs: []RemoteVA{
487 {remoteVA1, remoteUA1},
488 {cancelledVA{}, remoteUA2},
489 },
490 AllowedUAs: allowedUAs,
491 Features: enforceMultiVA,
492 ExpectedProb: probs.ServerInternal("During secondary validation: Remote PerformValidation RPC canceled"),
493 },
494 {
495
496 Name: "Local VA OK, two cancelled remote VAs, enforce multi VA",
497 RemoteVAs: []RemoteVA{
498 {cancelledVA{}, remoteUA1},
499 {cancelledVA{}, remoteUA2},
500 },
501 AllowedUAs: allowedUAs,
502 Features: enforceMultiVA,
503 ExpectedProb: probs.ServerInternal("During secondary validation: Remote PerformValidation RPC canceled"),
504 },
505 {
506
507
508
509 Name: "Local and remote VA differential, full results, no enforce multi VA",
510 RemoteVAs: remoteVAs,
511 AllowedUAs: map[string]bool{localUA: true},
512 Features: noEnforceMultiVAFullResults,
513 },
514 {
515
516
517 Name: "Local and remote VA differential, full results, enforce multi VA",
518 RemoteVAs: remoteVAs,
519 AllowedUAs: map[string]bool{localUA: true},
520 Features: enforceMultiVAFullResults,
521 ExpectedProb: probs.Unauthorized(fmt.Sprintf(
522 `During secondary validation: The key authorization file from the server did not match this challenge. Expected %q (got "???")`,
523 expectedKeyAuthorization)),
524 },
525 }
526
527 for _, tc := range testCases {
528 t.Run(tc.Name, func(t *testing.T) {
529
530 ms.setAllowedUAs(tc.AllowedUAs)
531
532
533 localVA, mockLog := setup(ms.Server, 0, localUA, tc.RemoteVAs)
534
535 if tc.Features != nil {
536 err := features.Set(tc.Features)
537 test.AssertNotError(t, err, "Failed to set feature flags")
538 defer features.Reset()
539 }
540
541
542 res, _ := localVA.PerformValidation(ctx, req)
543 if res.Problems == nil && tc.ExpectedProb != nil {
544 t.Errorf("expected prob %v, got nil", tc.ExpectedProb)
545 } else if res.Problems != nil && tc.ExpectedProb == nil {
546 t.Errorf("expected no prob, got %v", res.Problems)
547 } else if res.Problems != nil && tc.ExpectedProb != nil {
548
549 test.AssertEquals(t, res.Problems.ProblemType, string(tc.ExpectedProb.Type))
550 test.AssertEquals(t, res.Problems.Detail, tc.ExpectedProb.Detail)
551 }
552
553 if tc.ExpectedLog != "" {
554 lines := mockLog.GetAllMatching(tc.ExpectedLog)
555 if len(lines) != 1 {
556 t.Fatalf("Got log %v; expected %q", mockLog.GetAll(), tc.ExpectedLog)
557 }
558 }
559 })
560 }
561 }
562
563 func TestMultiVAEarlyReturn(t *testing.T) {
564 const (
565 remoteUA1 = "remote 1"
566 remoteUA2 = "slow remote"
567 localUA = "local 1"
568 )
569 allowedUAs := map[string]bool{
570 localUA: true,
571 remoteUA1: false,
572 remoteUA2: true,
573 }
574
575 ms := httpMultiSrv(t, expectedToken, allowedUAs)
576 defer ms.Close()
577
578 remoteVA1 := setupRemote(ms.Server, remoteUA1)
579 remoteVA2 := setupRemote(ms.Server, remoteUA2)
580
581 remoteVAs := []RemoteVA{
582 {remoteVA1, remoteUA1},
583 {remoteVA2, remoteUA2},
584 }
585
586
587 localVA, mockLog := setup(ms.Server, 0, localUA, remoteVAs)
588
589 testCases := []struct {
590 Name string
591 EarlyReturn bool
592 }{
593 {
594 Name: "One slow remote VA, no early return",
595 },
596 {
597 Name: "One slow remote VA, early return",
598 EarlyReturn: true,
599 },
600 }
601
602 earlyReturnFeatures := map[string]bool{
603 "EnforceMultiVA": true,
604 "MultiVAFullResults": false,
605 }
606 noEarlyReturnFeatures := map[string]bool{
607 "EnforceMultiVA": true,
608 "MultiVAFullResults": true,
609 }
610
611 req := createValidationRequest("localhost", core.ChallengeTypeHTTP01)
612 for _, tc := range testCases {
613 t.Run(tc.Name, func(t *testing.T) {
614 mockLog.Clear()
615
616 var err error
617 if tc.EarlyReturn {
618 err = features.Set(earlyReturnFeatures)
619 } else {
620 err = features.Set(noEarlyReturnFeatures)
621 }
622 test.AssertNotError(t, err, "Failed to set MultiVAFullResults feature flag")
623 defer features.Reset()
624
625 start := time.Now()
626
627
628 res, _ := localVA.PerformValidation(ctx, req)
629
630 if res.Problems == nil {
631 t.Error("expected prob from PerformValidation, got nil")
632 }
633
634 elapsed := time.Since(start).Round(time.Millisecond).Milliseconds()
635
636
637
638
639
640
641 if tc.EarlyReturn && elapsed > slowRemoteSleepMillis/2 {
642 t.Errorf(
643 "Expected an early return from PerformValidation in < %d ms, took %d ms",
644 slowRemoteSleepMillis/2, elapsed)
645 }
646 })
647 }
648 }
649
650 func TestMultiVAPolicy(t *testing.T) {
651 const (
652 remoteUA1 = "remote 1"
653 remoteUA2 = "remote 2"
654 localUA = "local 1"
655 )
656
657 allowedUAs := map[string]bool{
658 localUA: true,
659 remoteUA1: false,
660 remoteUA2: false,
661 }
662
663 ms := httpMultiSrv(t, expectedToken, allowedUAs)
664 defer ms.Close()
665
666 remoteVA1 := setupRemote(ms.Server, remoteUA1)
667 remoteVA2 := setupRemote(ms.Server, remoteUA2)
668
669 remoteVAs := []RemoteVA{
670 {remoteVA1, remoteUA1},
671 {remoteVA2, remoteUA2},
672 }
673
674
675 localVA, _ := setup(ms.Server, 0, localUA, remoteVAs)
676
677
678
679 err := features.Set(map[string]bool{
680 "EnforceMultiVA": true,
681 "MultiVAFullResults": false,
682 })
683 test.AssertNotError(t, err, "setting feature flags")
684 defer features.Reset()
685
686
687 req := createValidationRequest("letsencrypt.org", core.ChallengeTypeHTTP01)
688 res, _ := localVA.PerformValidation(ctx, req)
689
690 if res.Problems == nil {
691 t.Error("expected prob from PerformValidation, got nil")
692 }
693 }
694
695 func TestDetailedError(t *testing.T) {
696 cases := []struct {
697 err error
698 ip net.IP
699 expected string
700 }{
701 {
702 err: ipError{
703 ip: net.ParseIP("192.168.1.1"),
704 err: &net.OpError{
705 Op: "dial",
706 Net: "tcp",
707 Err: &os.SyscallError{
708 Syscall: "getsockopt",
709 Err: syscall.ECONNREFUSED,
710 },
711 },
712 },
713 expected: "192.168.1.1: Connection refused",
714 },
715 {
716 err: &net.OpError{
717 Op: "dial",
718 Net: "tcp",
719 Err: &os.SyscallError{
720 Syscall: "getsockopt",
721 Err: syscall.ECONNREFUSED,
722 },
723 },
724 expected: "Connection refused",
725 },
726 {
727 err: &net.OpError{
728 Op: "dial",
729 Net: "tcp",
730 Err: &os.SyscallError{
731 Syscall: "getsockopt",
732 Err: syscall.ECONNRESET,
733 },
734 },
735 ip: nil,
736 expected: "Connection reset by peer",
737 },
738 }
739 for _, tc := range cases {
740 actual := detailedError(tc.err).Detail
741 if actual != tc.expected {
742 t.Errorf("Wrong detail for %v. Got %q, expected %q", tc.err, actual, tc.expected)
743 }
744 }
745 }
746
747 func TestLogRemoteValidationDifferentials(t *testing.T) {
748
749 remoteVA1 := setupRemote(nil, "remote 1")
750 remoteVA2 := setupRemote(nil, "remote 2")
751 remoteVA3 := setupRemote(nil, "remote 3")
752 remoteVAs := []RemoteVA{
753 {remoteVA1, "remote 1"},
754 {remoteVA2, "remote 2"},
755 {remoteVA3, "remote 3"},
756 }
757
758
759 localVA, mockLog := setup(nil, 2, "local 1", remoteVAs)
760
761 egProbA := probs.DNS("root DNS servers closed at 4:30pm")
762 egProbB := probs.OrderNotReady("please take a number")
763
764 testCases := []struct {
765 name string
766 primaryResult *probs.ProblemDetails
767 remoteProbs []*remoteValidationResult
768 expectedLog string
769 }{
770 {
771 name: "remote and primary results equal (all nil)",
772 primaryResult: nil,
773 remoteProbs: []*remoteValidationResult{
774 {Problem: nil, VAHostname: "remoteA"},
775 {Problem: nil, VAHostname: "remoteB"},
776 {Problem: nil, VAHostname: "remoteC"},
777 },
778 },
779 {
780 name: "remote and primary results equal (not nil)",
781 primaryResult: egProbA,
782 remoteProbs: []*remoteValidationResult{
783 {Problem: egProbA, VAHostname: "remoteA"},
784 {Problem: egProbA, VAHostname: "remoteB"},
785 {Problem: egProbA, VAHostname: "remoteC"},
786 },
787 },
788 {
789 name: "remote and primary differ (primary nil)",
790 primaryResult: nil,
791 remoteProbs: []*remoteValidationResult{
792 {Problem: egProbA, VAHostname: "remoteA"},
793 {Problem: nil, VAHostname: "remoteB"},
794 {Problem: egProbB, VAHostname: "remoteC"},
795 },
796 expectedLog: `INFO: remoteVADifferentials JSON={"Domain":"example.com","AccountID":1999,"ChallengeType":"blorpus-01","PrimaryResult":null,"RemoteSuccesses":1,"RemoteFailures":[{"VAHostname":"remoteA","Problem":{"type":"dns","detail":"root DNS servers closed at 4:30pm","status":400}},{"VAHostname":"remoteC","Problem":{"type":"orderNotReady","detail":"please take a number","status":403}}]}`,
797 },
798 {
799 name: "remote and primary differ (primary not nil)",
800 primaryResult: egProbA,
801 remoteProbs: []*remoteValidationResult{
802 {Problem: nil, VAHostname: "remoteA"},
803 {Problem: egProbB, VAHostname: "remoteB"},
804 {Problem: nil, VAHostname: "remoteC"},
805 },
806 expectedLog: `INFO: remoteVADifferentials JSON={"Domain":"example.com","AccountID":1999,"ChallengeType":"blorpus-01","PrimaryResult":{"type":"dns","detail":"root DNS servers closed at 4:30pm","status":400},"RemoteSuccesses":2,"RemoteFailures":[{"VAHostname":"remoteB","Problem":{"type":"orderNotReady","detail":"please take a number","status":403}}]}`,
807 },
808 }
809
810 for _, tc := range testCases {
811 t.Run(tc.name, func(t *testing.T) {
812 mockLog.Clear()
813
814 localVA.logRemoteValidationDifferentials(
815 "example.com", 1999, "blorpus-01", tc.primaryResult, tc.remoteProbs)
816
817 lines := mockLog.GetAllMatching("remoteVADifferentials JSON=.*")
818 if tc.expectedLog != "" {
819 test.AssertEquals(t, len(lines), 1)
820 test.AssertEquals(t, lines[0], tc.expectedLog)
821 } else {
822 test.AssertEquals(t, len(lines), 0)
823 }
824 })
825 }
826 }
827
View as plain text