github.com/letsencrypt/boulder@v0.20251208.0/va/http_test.go (about) 1 package va 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 mrand "math/rand/v2" 9 "net" 10 "net/http" 11 "net/http/httptest" 12 "net/netip" 13 "net/url" 14 "strconv" 15 "strings" 16 "time" 17 "unicode/utf8" 18 19 "github.com/letsencrypt/boulder/bdns" 20 "github.com/letsencrypt/boulder/core" 21 berrors "github.com/letsencrypt/boulder/errors" 22 "github.com/letsencrypt/boulder/identifier" 23 "github.com/letsencrypt/boulder/must" 24 "github.com/letsencrypt/boulder/probs" 25 "github.com/letsencrypt/boulder/test" 26 27 "testing" 28 ) 29 30 // TestDialerMismatchError tests that using a preresolvedDialer for one host for 31 // a dial to another host produces the expected dialerMismatchError. 32 func TestDialerMismatchError(t *testing.T) { 33 d := preresolvedDialer{ 34 ip: netip.MustParseAddr("127.0.0.1"), 35 port: 1337, 36 hostname: "letsencrypt.org", 37 } 38 39 expectedErr := dialerMismatchError{ 40 dialerHost: d.hostname, 41 dialerIP: d.ip.String(), 42 dialerPort: d.port, 43 host: "lettuceencrypt.org", 44 } 45 46 _, err := d.DialContext( 47 context.Background(), 48 "tincan-and-string", 49 "lettuceencrypt.org:80") 50 test.AssertEquals(t, err.Error(), expectedErr.Error()) 51 } 52 53 // dnsMockReturnsUnroutable is a DNSClient mock that always returns an 54 // unroutable address for LookupHost. This is useful in testing connect 55 // timeouts. 56 type dnsMockReturnsUnroutable struct { 57 *bdns.MockClient 58 } 59 60 func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) { 61 return []netip.Addr{netip.MustParseAddr("64.112.117.254")}, bdns.ResolverAddrs{"dnsMockReturnsUnroutable"}, nil 62 } 63 64 // TestDialerTimeout tests that the preresolvedDialer's DialContext 65 // will timeout after the expected singleDialTimeout. This ensures timeouts at 66 // the TCP level are handled correctly. It also ensures that we show the client 67 // the appropriate "Timeout during connect" error message, which helps clients 68 // distinguish between firewall problems and server problems. 69 func TestDialerTimeout(t *testing.T) { 70 va, _ := setup(nil, "", nil, nil) 71 // Timeouts below 50ms tend to be flaky. 72 va.singleDialTimeout = 50 * time.Millisecond 73 74 // The context timeout needs to be larger than the singleDialTimeout 75 ctxTimeout := 500 * time.Millisecond 76 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) 77 defer cancel() 78 79 va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockClient{}} 80 // NOTE(@jsha): The only method I've found so far to trigger a connect timeout 81 // is to connect to an unrouteable IP address. This usually generates 82 // a connection timeout, but will rarely return "Network unreachable" instead. 83 // If we get that, just retry until we get something other than "Network unreachable". 84 var err error 85 var took time.Duration 86 for range 20 { 87 started := time.Now() 88 _, _, err = va.processHTTPValidation(ctx, identifier.NewDNS("unroutable.invalid"), "/.well-known/acme-challenge/whatever") 89 took = time.Since(started) 90 if err != nil && strings.Contains(err.Error(), "network is unreachable") { 91 continue 92 } else { 93 break 94 } 95 } 96 if err == nil { 97 t.Fatalf("Connection should've timed out") 98 } 99 100 // Check that the HTTP connection doesn't return too fast, and times 101 // out after the expected time 102 if took < va.singleDialTimeout { 103 t.Fatalf("fetch returned before %s (took: %s) with %q", va.singleDialTimeout, took, err.Error()) 104 } 105 if took > 2*va.singleDialTimeout { 106 t.Fatalf("fetch didn't timeout after %s (took: %s)", va.singleDialTimeout, took) 107 } 108 prob := detailedError(err) 109 test.AssertEquals(t, prob.Type, probs.ConnectionProblem) 110 test.AssertContains(t, prob.Detail, "Timeout during connect (likely firewall problem)") 111 } 112 113 func TestHTTPTransport(t *testing.T) { 114 dummyDialerFunc := func(_ context.Context, _, _ string) (net.Conn, error) { 115 return nil, nil 116 } 117 transport := httpTransport(dummyDialerFunc) 118 // The HTTP Transport should have a TLS config that skips verifying 119 // certificates. 120 test.AssertEquals(t, transport.TLSClientConfig.InsecureSkipVerify, true) 121 // Keep alives should be disabled 122 test.AssertEquals(t, transport.DisableKeepAlives, true) 123 test.AssertEquals(t, transport.MaxIdleConns, 1) 124 test.AssertEquals(t, transport.IdleConnTimeout.String(), "1s") 125 test.AssertEquals(t, transport.TLSHandshakeTimeout.String(), "10s") 126 } 127 128 func TestHTTPValidationTarget(t *testing.T) { 129 // NOTE(@cpu): See `bdns/mocks.go` and the mock `LookupHost` function for the 130 // hostnames used in this test. 131 testCases := []struct { 132 Name string 133 Ident identifier.ACMEIdentifier 134 ExpectedError error 135 ExpectedIPs []string 136 }{ 137 { 138 Name: "No IPs for DNS identifier", 139 Ident: identifier.NewDNS("always.invalid"), 140 ExpectedError: berrors.DNSError("No valid IP addresses found for always.invalid"), 141 }, 142 { 143 Name: "Only IPv4 addrs for DNS identifier", 144 Ident: identifier.NewDNS("some.example.com"), 145 ExpectedIPs: []string{"127.0.0.1"}, 146 }, 147 { 148 Name: "Only IPv6 addrs for DNS identifier", 149 Ident: identifier.NewDNS("ipv6.localhost"), 150 ExpectedIPs: []string{"::1"}, 151 }, 152 { 153 Name: "Both IPv6 and IPv4 addrs for DNS identifier", 154 Ident: identifier.NewDNS("ipv4.and.ipv6.localhost"), 155 // In this case we expect 1 IPv6 address first, and then 1 IPv4 address 156 ExpectedIPs: []string{"::1", "127.0.0.1"}, 157 }, 158 { 159 Name: "IPv4 IP address identifier", 160 Ident: identifier.NewIP(netip.MustParseAddr("127.0.0.1")), 161 ExpectedIPs: []string{"127.0.0.1"}, 162 }, 163 { 164 Name: "IPv6 IP address identifier", 165 Ident: identifier.NewIP(netip.MustParseAddr("::1")), 166 ExpectedIPs: []string{"::1"}, 167 }, 168 } 169 170 const ( 171 examplePort = 1234 172 examplePath = "/.well-known/path/i/took" 173 exampleQuery = "my-path=was&my=own" 174 ) 175 176 va, _ := setup(nil, "", nil, nil) 177 for _, tc := range testCases { 178 t.Run(tc.Name, func(t *testing.T) { 179 target, err := va.newHTTPValidationTarget( 180 context.Background(), 181 tc.Ident, 182 examplePort, 183 examplePath, 184 exampleQuery) 185 if err != nil && tc.ExpectedError == nil { 186 t.Fatalf("Unexpected error from NewHTTPValidationTarget: %v", err) 187 } else if err != nil && tc.ExpectedError != nil { 188 test.AssertMarshaledEquals(t, err, tc.ExpectedError) 189 } else if err == nil { 190 // The target should be populated. 191 test.AssertNotEquals(t, target.host, "") 192 test.AssertNotEquals(t, target.port, 0) 193 test.AssertNotEquals(t, target.path, "") 194 // Calling ip() on the target should give the expected IPs in the right 195 // order. 196 for i, expectedIP := range tc.ExpectedIPs { 197 gotIP := target.cur 198 if (gotIP == netip.Addr{}) { 199 t.Errorf("Expected IP %d to be %s got nil", i, expectedIP) 200 } else { 201 test.AssertEquals(t, gotIP.String(), expectedIP) 202 } 203 // Advance to the next IP 204 _ = target.nextIP() 205 } 206 } 207 }) 208 } 209 } 210 211 func TestExtractRequestTarget(t *testing.T) { 212 mustURL := func(rawURL string) *url.URL { 213 return must.Do(url.Parse(rawURL)) 214 } 215 216 testCases := []struct { 217 Name string 218 Req *http.Request 219 ExpectedError error 220 ExpectedIdent identifier.ACMEIdentifier 221 ExpectedPort int 222 }{ 223 { 224 Name: "nil input req", 225 ExpectedError: fmt.Errorf("redirect HTTP request was nil"), 226 }, 227 { 228 Name: "invalid protocol scheme", 229 Req: &http.Request{ 230 URL: mustURL("gopher://letsencrypt.org"), 231 }, 232 ExpectedError: fmt.Errorf("Invalid protocol scheme in redirect target. " + 233 `Only "http" and "https" protocol schemes are supported, ` + 234 `not "gopher"`), 235 }, 236 { 237 Name: "invalid explicit port", 238 Req: &http.Request{ 239 URL: mustURL("https://weird.port.letsencrypt.org:9999"), 240 }, 241 ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " + 242 "and 443 are supported, not 9999"), 243 }, 244 { 245 Name: "invalid empty host", 246 Req: &http.Request{ 247 URL: mustURL("https:///who/needs/a/hostname?not=me"), 248 }, 249 ExpectedError: errors.New("Invalid empty host in redirect target"), 250 }, 251 { 252 Name: "invalid .well-known hostname", 253 Req: &http.Request{ 254 URL: mustURL("https://my.webserver.is.misconfigured.well-known/acme-challenge/xxx"), 255 }, 256 ExpectedError: errors.New(`Invalid host in redirect target "my.webserver.is.misconfigured.well-known". Check webserver config for missing '/' in redirect target.`), 257 }, 258 { 259 Name: "invalid non-iana hostname", 260 Req: &http.Request{ 261 URL: mustURL("https://my.tld.is.cpu/pretty/cool/right?yeah=Ithoughtsotoo"), 262 }, 263 ExpectedError: errors.New("Invalid host in redirect target, must end in IANA registered TLD"), 264 }, 265 { 266 Name: "malformed wildcard-ish IPv4 address", 267 Req: &http.Request{ 268 URL: mustURL("https://10.10.10.*"), 269 }, 270 ExpectedError: errors.New("Invalid host in redirect target, must end in IANA registered TLD"), 271 }, 272 { 273 Name: "bare IPv4, implicit port", 274 Req: &http.Request{ 275 URL: mustURL("http://127.0.0.1"), 276 }, 277 ExpectedIdent: identifier.NewIP(netip.MustParseAddr("127.0.0.1")), 278 ExpectedPort: 80, 279 }, 280 { 281 Name: "bare IPv4, explicit valid port", 282 Req: &http.Request{ 283 URL: mustURL("http://127.0.0.1:80"), 284 }, 285 ExpectedIdent: identifier.NewIP(netip.MustParseAddr("127.0.0.1")), 286 ExpectedPort: 80, 287 }, 288 { 289 Name: "bare IPv4, explicit invalid port", 290 Req: &http.Request{ 291 URL: mustURL("http://127.0.0.1:9999"), 292 }, 293 ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " + 294 "and 443 are supported, not 9999"), 295 }, 296 { 297 Name: "bare IPv4, HTTPS", 298 Req: &http.Request{ 299 URL: mustURL("https://127.0.0.1"), 300 }, 301 ExpectedIdent: identifier.NewIP(netip.MustParseAddr("127.0.0.1")), 302 ExpectedPort: 443, 303 }, 304 { 305 Name: "bare IPv4, reserved IP address", 306 Req: &http.Request{ 307 URL: mustURL("http://10.10.10.10"), 308 }, 309 ExpectedError: fmt.Errorf("Invalid host in redirect target: " + 310 "IP address is in a reserved address block: [RFC1918]: Private-Use"), 311 }, 312 { 313 Name: "bare IPv6, implicit port", 314 Req: &http.Request{ 315 URL: mustURL("http://[::1]"), 316 }, 317 ExpectedIdent: identifier.NewIP(netip.MustParseAddr("::1")), 318 ExpectedPort: 80, 319 }, 320 { 321 Name: "bare IPv6, explicit valid port", 322 Req: &http.Request{ 323 URL: mustURL("http://[::1]:80"), 324 }, 325 ExpectedIdent: identifier.NewIP(netip.MustParseAddr("::1")), 326 ExpectedPort: 80, 327 }, 328 { 329 Name: "bare IPv6, explicit invalid port", 330 Req: &http.Request{ 331 URL: mustURL("http://[::1]:9999"), 332 }, 333 ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " + 334 "and 443 are supported, not 9999"), 335 }, 336 { 337 Name: "bare IPv6, HTTPS", 338 Req: &http.Request{ 339 URL: mustURL("https://[::1]"), 340 }, 341 ExpectedIdent: identifier.NewIP(netip.MustParseAddr("::1")), 342 ExpectedPort: 443, 343 }, 344 { 345 Name: "bare IPv6, reserved IP address", 346 Req: &http.Request{ 347 URL: mustURL("http://[3fff:aaa:aaaa:aaaa:abad:0ff1:cec0:ffee]"), 348 }, 349 ExpectedError: fmt.Errorf("Invalid host in redirect target: " + 350 "IP address is in a reserved address block: [RFC9637]: Documentation"), 351 }, 352 { 353 Name: "bare IPv6, scope zone", 354 Req: &http.Request{ 355 URL: mustURL("http://[::1%25lo]"), 356 }, 357 ExpectedError: fmt.Errorf("Invalid host in redirect target: " + 358 "contains scope zone"), 359 }, 360 { 361 Name: "valid HTTP redirect, explicit port", 362 Req: &http.Request{ 363 URL: mustURL("http://cpu.letsencrypt.org:80"), 364 }, 365 ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"), 366 ExpectedPort: 80, 367 }, 368 { 369 Name: "valid HTTP redirect, implicit port", 370 Req: &http.Request{ 371 URL: mustURL("http://cpu.letsencrypt.org"), 372 }, 373 ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"), 374 ExpectedPort: 80, 375 }, 376 { 377 Name: "valid HTTPS redirect, explicit port", 378 Req: &http.Request{ 379 URL: mustURL("https://cpu.letsencrypt.org:443/hello.world"), 380 }, 381 ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"), 382 ExpectedPort: 443, 383 }, 384 { 385 Name: "valid HTTPS redirect, implicit port", 386 Req: &http.Request{ 387 URL: mustURL("https://cpu.letsencrypt.org/hello.world"), 388 }, 389 ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"), 390 ExpectedPort: 443, 391 }, 392 } 393 394 va, _ := setup(nil, "", nil, nil) 395 for _, tc := range testCases { 396 t.Run(tc.Name, func(t *testing.T) { 397 host, port, err := va.extractRequestTarget(tc.Req) 398 if err != nil && tc.ExpectedError == nil { 399 t.Errorf("Expected nil err got %v", err) 400 } else if err != nil && tc.ExpectedError != nil { 401 test.AssertEquals(t, err.Error(), tc.ExpectedError.Error()) 402 } else if err == nil && tc.ExpectedError != nil { 403 t.Errorf("Expected err %v, got nil", tc.ExpectedError) 404 } else { 405 test.AssertEquals(t, host, tc.ExpectedIdent) 406 test.AssertEquals(t, port, tc.ExpectedPort) 407 } 408 }) 409 } 410 } 411 412 func TestSetupHTTPValidation(t *testing.T) { 413 va, _ := setup(nil, "", nil, nil) 414 415 mustTarget := func(t *testing.T, host string, port int, path string) *httpValidationTarget { 416 target, err := va.newHTTPValidationTarget( 417 context.Background(), 418 identifier.NewDNS(host), 419 port, 420 path, 421 "") 422 if err != nil { 423 t.Fatalf("Failed to construct httpValidationTarget for %q", host) 424 return nil 425 } 426 return target 427 } 428 429 httpInputURL := "http://ipv4.and.ipv6.localhost/yellow/brick/road" 430 httpsInputURL := "https://ipv4.and.ipv6.localhost/yellow/brick/road" 431 432 testCases := []struct { 433 Name string 434 InputURL string 435 InputTarget *httpValidationTarget 436 ExpectedRecord core.ValidationRecord 437 ExpectedDialer *preresolvedDialer 438 ExpectedError error 439 }{ 440 { 441 Name: "nil target", 442 InputURL: httpInputURL, 443 ExpectedError: fmt.Errorf("httpValidationTarget can not be nil"), 444 }, 445 { 446 Name: "empty input URL", 447 InputTarget: &httpValidationTarget{}, 448 ExpectedError: fmt.Errorf("reqURL can not be nil"), 449 }, 450 { 451 Name: "target with no IPs", 452 InputURL: httpInputURL, 453 InputTarget: &httpValidationTarget{ 454 host: "ipv4.and.ipv6.localhost", 455 port: va.httpPort, 456 path: "idk", 457 }, 458 ExpectedRecord: core.ValidationRecord{ 459 URL: "http://ipv4.and.ipv6.localhost/yellow/brick/road", 460 Hostname: "ipv4.and.ipv6.localhost", 461 Port: strconv.Itoa(va.httpPort), 462 }, 463 ExpectedError: fmt.Errorf(`host "ipv4.and.ipv6.localhost" has no IP addresses remaining to use`), 464 }, 465 { 466 Name: "HTTP input req", 467 InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpPort, "/yellow/brick/road"), 468 InputURL: httpInputURL, 469 ExpectedRecord: core.ValidationRecord{ 470 Hostname: "ipv4.and.ipv6.localhost", 471 Port: strconv.Itoa(va.httpPort), 472 URL: "http://ipv4.and.ipv6.localhost/yellow/brick/road", 473 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")}, 474 AddressUsed: netip.MustParseAddr("::1"), 475 ResolverAddrs: []string{"MockClient"}, 476 }, 477 ExpectedDialer: &preresolvedDialer{ 478 ip: netip.MustParseAddr("::1"), 479 port: va.httpPort, 480 timeout: va.singleDialTimeout, 481 }, 482 }, 483 { 484 Name: "HTTPS input req", 485 InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpsPort, "/yellow/brick/road"), 486 InputURL: httpsInputURL, 487 ExpectedRecord: core.ValidationRecord{ 488 Hostname: "ipv4.and.ipv6.localhost", 489 Port: strconv.Itoa(va.httpsPort), 490 URL: "https://ipv4.and.ipv6.localhost/yellow/brick/road", 491 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")}, 492 AddressUsed: netip.MustParseAddr("::1"), 493 ResolverAddrs: []string{"MockClient"}, 494 }, 495 ExpectedDialer: &preresolvedDialer{ 496 ip: netip.MustParseAddr("::1"), 497 port: va.httpsPort, 498 timeout: va.singleDialTimeout, 499 }, 500 }, 501 } 502 503 for _, tc := range testCases { 504 t.Run(tc.Name, func(t *testing.T) { 505 outDialer, outRecord, err := va.setupHTTPValidation(tc.InputURL, tc.InputTarget) 506 if err != nil && tc.ExpectedError == nil { 507 t.Errorf("Expected nil error, got %v", err) 508 } else if err == nil && tc.ExpectedError != nil { 509 t.Errorf("Expected %v error, got nil", tc.ExpectedError) 510 } else if err != nil && tc.ExpectedError != nil { 511 test.AssertEquals(t, err.Error(), tc.ExpectedError.Error()) 512 } 513 if tc.ExpectedDialer == nil && outDialer != nil { 514 t.Errorf("Expected nil dialer, got %v", outDialer) 515 } else if tc.ExpectedDialer != nil { 516 test.AssertMarshaledEquals(t, outDialer, tc.ExpectedDialer) 517 } 518 // In all cases we expect there to have been a validation record 519 test.AssertMarshaledEquals(t, outRecord, tc.ExpectedRecord) 520 }) 521 } 522 } 523 524 // A more concise version of httpSrv() that supports http.go tests 525 func httpTestSrv(t *testing.T, ipv6 bool) *httptest.Server { 526 t.Helper() 527 mux := http.NewServeMux() 528 server := httptest.NewUnstartedServer(mux) 529 530 if ipv6 { 531 l, err := net.Listen("tcp", "[::1]:0") 532 if err != nil { 533 panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err)) 534 } 535 server.Listener = l 536 } 537 538 server.Start() 539 httpPort := getPort(server) 540 541 // A path that always returns an OK response 542 mux.HandleFunc("/ok", func(resp http.ResponseWriter, req *http.Request) { 543 resp.WriteHeader(http.StatusOK) 544 fmt.Fprint(resp, "ok") 545 }) 546 547 // A path that always times out by sleeping longer than the validation context 548 // allows 549 mux.HandleFunc("/timeout", func(resp http.ResponseWriter, req *http.Request) { 550 time.Sleep(time.Second) 551 resp.WriteHeader(http.StatusOK) 552 fmt.Fprint(resp, "sorry, I'm a slow server") 553 }) 554 555 // A path that always redirects to itself, creating a loop that will terminate 556 // when detected. 557 mux.HandleFunc("/loop", func(resp http.ResponseWriter, req *http.Request) { 558 http.Redirect( 559 resp, 560 req, 561 fmt.Sprintf("http://example.com:%d/loop", httpPort), 562 http.StatusMovedPermanently) 563 }) 564 565 // A path that sequentially redirects, creating an incrementing redirect 566 // that will terminate when the redirect limit is reached and ensures each 567 // URL is different than the last. 568 for i := range maxRedirect + 2 { 569 mux.HandleFunc(fmt.Sprintf("/max-redirect/%d", i), 570 func(resp http.ResponseWriter, req *http.Request) { 571 http.Redirect( 572 resp, 573 req, 574 fmt.Sprintf("http://example.com:%d/max-redirect/%d", httpPort, i+1), 575 http.StatusMovedPermanently, 576 ) 577 }) 578 } 579 580 // A path that always redirects to a URL with a non-HTTP/HTTPs protocol scheme 581 mux.HandleFunc("/redir-bad-proto", func(resp http.ResponseWriter, req *http.Request) { 582 http.Redirect( 583 resp, 584 req, 585 "gopher://example.com", 586 http.StatusMovedPermanently, 587 ) 588 }) 589 590 // A path that always redirects to a URL with a port other than the configured 591 // HTTP/HTTPS port 592 mux.HandleFunc("/redir-bad-port", func(resp http.ResponseWriter, req *http.Request) { 593 http.Redirect( 594 resp, 595 req, 596 "https://example.com:1987", 597 http.StatusMovedPermanently, 598 ) 599 }) 600 601 // A path that always redirects to a URL with a bare IP address 602 mux.HandleFunc("/redir-bare-ipv4", func(resp http.ResponseWriter, req *http.Request) { 603 http.Redirect( 604 resp, 605 req, 606 "http://127.0.0.1/ok", 607 http.StatusMovedPermanently, 608 ) 609 }) 610 611 mux.HandleFunc("/redir-bare-ipv6", func(resp http.ResponseWriter, req *http.Request) { 612 http.Redirect( 613 resp, 614 req, 615 "http://[::1]/ok", 616 http.StatusMovedPermanently, 617 ) 618 }) 619 620 mux.HandleFunc("/bad-status-code", func(resp http.ResponseWriter, req *http.Request) { 621 resp.WriteHeader(http.StatusGone) 622 fmt.Fprint(resp, "sorry, I'm gone") 623 }) 624 625 // A path that always responds with a 303 redirect 626 mux.HandleFunc("/303-see-other", func(resp http.ResponseWriter, req *http.Request) { 627 http.Redirect( 628 resp, 629 req, 630 "http://example.org/303-see-other", 631 http.StatusSeeOther, 632 ) 633 }) 634 635 tooLargeBuf := bytes.NewBuffer([]byte{}) 636 for range maxResponseSize + 10 { 637 tooLargeBuf.WriteByte(byte(97)) 638 } 639 mux.HandleFunc("/resp-too-big", func(resp http.ResponseWriter, req *http.Request) { 640 resp.WriteHeader(http.StatusOK) 641 fmt.Fprint(resp, tooLargeBuf) 642 }) 643 644 // Create a buffer that starts with invalid UTF8 and is bigger than 645 // maxResponseSize 646 tooLargeInvalidUTF8 := bytes.NewBuffer([]byte{}) 647 tooLargeInvalidUTF8.WriteString("f\xffoo") 648 tooLargeInvalidUTF8.Write(tooLargeBuf.Bytes()) 649 // invalid-utf8-body Responds with body that is larger than 650 // maxResponseSize and starts with an invalid UTF8 string. This is to 651 // test the codepath where invalid UTF8 is converted to valid UTF8 652 // that can be passed as an error message via grpc. 653 mux.HandleFunc("/invalid-utf8-body", func(resp http.ResponseWriter, req *http.Request) { 654 resp.WriteHeader(http.StatusOK) 655 fmt.Fprint(resp, tooLargeInvalidUTF8) 656 }) 657 658 mux.HandleFunc("/redir-path-too-long", func(resp http.ResponseWriter, req *http.Request) { 659 http.Redirect( 660 resp, 661 req, 662 "https://example.com/this-is-too-long-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 663 http.StatusMovedPermanently) 664 }) 665 666 // A path that redirects to an uppercase public suffix (#4215) 667 mux.HandleFunc("/redir-uppercase-publicsuffix", func(resp http.ResponseWriter, req *http.Request) { 668 http.Redirect( 669 resp, 670 req, 671 "http://example.COM/ok", 672 http.StatusMovedPermanently) 673 }) 674 675 // A path that returns a body containing printf formatting verbs 676 mux.HandleFunc("/printf-verbs", func(resp http.ResponseWriter, req *http.Request) { 677 resp.WriteHeader(http.StatusOK) 678 fmt.Fprint(resp, "%"+"2F.well-known%"+"2F"+tooLargeBuf.String()) 679 }) 680 681 return server 682 } 683 684 type testNetErr struct{} 685 686 func (e *testNetErr) Error() string { 687 return "testNetErr" 688 } 689 690 func (e *testNetErr) Temporary() bool { 691 return false 692 } 693 694 func (e *testNetErr) Timeout() bool { 695 return false 696 } 697 698 func TestFallbackErr(t *testing.T) { 699 untypedErr := errors.New("the least interesting kind of error") 700 berr := berrors.InternalServerError("code violet: class neptune") 701 netOpErr := &net.OpError{ 702 Op: "siphon", 703 Err: fmt.Errorf("port was clogged. please empty packets"), 704 } 705 netDialOpErr := &net.OpError{ 706 Op: "dial", 707 Err: fmt.Errorf("your call is important to us - please stay on the line"), 708 } 709 netErr := &testNetErr{} 710 711 testCases := []struct { 712 Name string 713 Err error 714 ExpectFallback bool 715 }{ 716 { 717 Name: "Nil error", 718 Err: nil, 719 }, 720 { 721 Name: "Standard untyped error", 722 Err: untypedErr, 723 }, 724 { 725 Name: "A Boulder error instance", 726 Err: berr, 727 }, 728 { 729 Name: "A non-dial net.OpError instance", 730 Err: netOpErr, 731 }, 732 { 733 Name: "A dial net.OpError instance", 734 Err: netDialOpErr, 735 ExpectFallback: true, 736 }, 737 { 738 Name: "A generic net.Error instance", 739 Err: netErr, 740 }, 741 { 742 Name: "A URL error wrapping a standard error", 743 Err: &url.Error{ 744 Op: "ivy", 745 URL: "https://en.wikipedia.org/wiki/Operation_Ivy_(band)", 746 Err: errors.New("take warning"), 747 }, 748 }, 749 { 750 Name: "A URL error wrapping a nil error", 751 Err: &url.Error{ 752 Err: nil, 753 }, 754 }, 755 { 756 Name: "A URL error wrapping a Boulder error instance", 757 Err: &url.Error{ 758 Err: berr, 759 }, 760 }, 761 { 762 Name: "A URL error wrapping a non-dial net OpError", 763 Err: &url.Error{ 764 Err: netOpErr, 765 }, 766 }, 767 { 768 Name: "A URL error wrapping a dial net.OpError", 769 Err: &url.Error{ 770 Err: netDialOpErr, 771 }, 772 ExpectFallback: true, 773 }, 774 { 775 Name: "A URL error wrapping a generic net Error", 776 Err: &url.Error{ 777 Err: netErr, 778 }, 779 }, 780 } 781 782 for _, tc := range testCases { 783 t.Run(tc.Name, func(t *testing.T) { 784 if isFallback := fallbackErr(tc.Err); isFallback != tc.ExpectFallback { 785 t.Errorf( 786 "Expected fallbackErr for %t to be %v was %v\n", 787 tc.Err, tc.ExpectFallback, isFallback) 788 } 789 }) 790 } 791 } 792 793 func TestFetchHTTP(t *testing.T) { 794 // Create test servers 795 testSrvIPv4 := httpTestSrv(t, false) 796 defer testSrvIPv4.Close() 797 testSrvIPv6 := httpTestSrv(t, true) 798 defer testSrvIPv6.Close() 799 800 // Setup VAs. By providing the testSrv to setup the VA will use the testSrv's 801 // randomly assigned port as its HTTP port. 802 vaIPv4, _ := setup(testSrvIPv4, "", nil, nil) 803 vaIPv6, _ := setup(testSrvIPv6, "", nil, nil) 804 805 // We need to know the randomly assigned HTTP port for testcases as well 806 httpPortIPv4 := getPort(testSrvIPv4) 807 httpPortIPv6 := getPort(testSrvIPv6) 808 809 // For the looped test case we expect one validation record per redirect 810 // until boulder detects that a url has been used twice indicating a 811 // redirect loop. Because it is hitting the /loop endpoint it will encounter 812 // this scenario after the base url and fail on the second time hitting the 813 // redirect with a port definition. On i=0 it will encounter the first 814 // redirect to the url with a port definition and on i=1 it will encounter 815 // the second redirect to the url with the port and get an expected error. 816 expectedLoopRecords := []core.ValidationRecord{} 817 for i := range 2 { 818 // The first request will not have a port # in the URL. 819 url := "http://example.com/loop" 820 if i != 0 { 821 url = fmt.Sprintf("http://example.com:%d/loop", httpPortIPv4) 822 } 823 expectedLoopRecords = append(expectedLoopRecords, 824 core.ValidationRecord{ 825 Hostname: "example.com", 826 Port: strconv.Itoa(httpPortIPv4), 827 URL: url, 828 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 829 AddressUsed: netip.MustParseAddr("127.0.0.1"), 830 ResolverAddrs: []string{"MockClient"}, 831 }) 832 } 833 834 // For the too many redirect test case we expect one validation record per 835 // redirect up to maxRedirect (inclusive). There is also +1 record for the 836 // base lookup, giving a termination criteria of > maxRedirect+1 837 expectedTooManyRedirRecords := []core.ValidationRecord{} 838 for i := range maxRedirect + 2 { 839 // The first request will not have a port # in the URL. 840 url := "http://example.com/max-redirect/0" 841 if i != 0 { 842 url = fmt.Sprintf("http://example.com:%d/max-redirect/%d", httpPortIPv4, i) 843 } 844 expectedTooManyRedirRecords = append(expectedTooManyRedirRecords, 845 core.ValidationRecord{ 846 Hostname: "example.com", 847 Port: strconv.Itoa(httpPortIPv4), 848 URL: url, 849 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 850 AddressUsed: netip.MustParseAddr("127.0.0.1"), 851 ResolverAddrs: []string{"MockClient"}, 852 }) 853 } 854 855 expectedTruncatedResp := bytes.NewBuffer([]byte{}) 856 for range maxResponseSize { 857 expectedTruncatedResp.WriteByte(byte(97)) 858 } 859 860 testCases := []struct { 861 Name string 862 IPv6 bool 863 Ident identifier.ACMEIdentifier 864 Path string 865 ExpectedBody string 866 ExpectedRecords []core.ValidationRecord 867 ExpectedProblem *probs.ProblemDetails 868 }{ 869 { 870 Name: "No IPs for host", 871 Ident: identifier.NewDNS("always.invalid"), 872 Path: "/.well-known/whatever", 873 ExpectedProblem: probs.DNS( 874 "No valid IP addresses found for always.invalid"), 875 // There are no validation records in this case because the base record 876 // is only constructed once a URL is made. 877 ExpectedRecords: nil, 878 }, 879 { 880 Name: "Timeout for host with standard ACME allowed port", 881 Ident: identifier.NewDNS("example.com"), 882 Path: "/timeout", 883 ExpectedProblem: probs.Connection( 884 "127.0.0.1: Fetching http://example.com/timeout: " + 885 "Timeout after connect (your server may be slow or overloaded)"), 886 ExpectedRecords: []core.ValidationRecord{ 887 { 888 Hostname: "example.com", 889 Port: strconv.Itoa(httpPortIPv4), 890 URL: "http://example.com/timeout", 891 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 892 AddressUsed: netip.MustParseAddr("127.0.0.1"), 893 ResolverAddrs: []string{"MockClient"}, 894 }, 895 }, 896 }, 897 { 898 Name: "Redirect loop", 899 Ident: identifier.NewDNS("example.com"), 900 Path: "/loop", 901 ExpectedProblem: probs.Connection(fmt.Sprintf( 902 "127.0.0.1: Fetching http://example.com:%d/loop: Redirect loop detected", httpPortIPv4)), 903 ExpectedRecords: expectedLoopRecords, 904 }, 905 { 906 Name: "Too many redirects", 907 Ident: identifier.NewDNS("example.com"), 908 Path: "/max-redirect/0", 909 ExpectedProblem: probs.Connection(fmt.Sprintf( 910 "127.0.0.1: Fetching http://example.com:%d/max-redirect/12: Too many redirects", httpPortIPv4)), 911 ExpectedRecords: expectedTooManyRedirRecords, 912 }, 913 { 914 Name: "Redirect to bad protocol", 915 Ident: identifier.NewDNS("example.com"), 916 Path: "/redir-bad-proto", 917 ExpectedProblem: probs.Connection( 918 "127.0.0.1: Fetching gopher://example.com: Invalid protocol scheme in " + 919 `redirect target. Only "http" and "https" protocol schemes ` + 920 `are supported, not "gopher"`), 921 ExpectedRecords: []core.ValidationRecord{ 922 { 923 Hostname: "example.com", 924 Port: strconv.Itoa(httpPortIPv4), 925 URL: "http://example.com/redir-bad-proto", 926 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 927 AddressUsed: netip.MustParseAddr("127.0.0.1"), 928 ResolverAddrs: []string{"MockClient"}, 929 }, 930 }, 931 }, 932 { 933 Name: "Redirect to bad port", 934 Ident: identifier.NewDNS("example.com"), 935 Path: "/redir-bad-port", 936 ExpectedProblem: probs.Connection(fmt.Sprintf( 937 "127.0.0.1: Fetching https://example.com:1987: Invalid port in redirect target. "+ 938 "Only ports %d and 443 are supported, not 1987", httpPortIPv4)), 939 ExpectedRecords: []core.ValidationRecord{ 940 { 941 Hostname: "example.com", 942 Port: strconv.Itoa(httpPortIPv4), 943 URL: "http://example.com/redir-bad-port", 944 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 945 AddressUsed: netip.MustParseAddr("127.0.0.1"), 946 ResolverAddrs: []string{"MockClient"}, 947 }, 948 }, 949 }, 950 { 951 Name: "Redirect to bare IPv4 address", 952 Ident: identifier.NewDNS("example.com"), 953 Path: "/redir-bare-ipv4", 954 ExpectedBody: "ok", 955 ExpectedRecords: []core.ValidationRecord{ 956 { 957 Hostname: "example.com", 958 Port: strconv.Itoa(httpPortIPv4), 959 URL: "http://example.com/redir-bare-ipv4", 960 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 961 AddressUsed: netip.MustParseAddr("127.0.0.1"), 962 ResolverAddrs: []string{"MockClient"}, 963 }, 964 { 965 Hostname: "127.0.0.1", 966 Port: strconv.Itoa(httpPortIPv4), 967 URL: "http://127.0.0.1/ok", 968 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 969 AddressUsed: netip.MustParseAddr("127.0.0.1"), 970 }, 971 }, 972 }, { 973 Name: "Redirect to bare IPv6 address", 974 IPv6: true, 975 Ident: identifier.NewDNS("ipv6.localhost"), 976 Path: "/redir-bare-ipv6", 977 ExpectedBody: "ok", 978 ExpectedRecords: []core.ValidationRecord{ 979 { 980 Hostname: "ipv6.localhost", 981 Port: strconv.Itoa(httpPortIPv6), 982 URL: "http://ipv6.localhost/redir-bare-ipv6", 983 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")}, 984 AddressUsed: netip.MustParseAddr("::1"), 985 ResolverAddrs: []string{"MockClient"}, 986 }, 987 { 988 Hostname: "::1", 989 Port: strconv.Itoa(httpPortIPv6), 990 URL: "http://[::1]/ok", 991 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")}, 992 AddressUsed: netip.MustParseAddr("::1"), 993 }, 994 }, 995 }, 996 { 997 Name: "Redirect to long path", 998 Ident: identifier.NewDNS("example.com"), 999 Path: "/redir-path-too-long", 1000 ExpectedProblem: probs.Connection( 1001 "127.0.0.1: Fetching https://example.com/this-is-too-long-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789: Redirect target too long"), 1002 ExpectedRecords: []core.ValidationRecord{ 1003 { 1004 Hostname: "example.com", 1005 Port: strconv.Itoa(httpPortIPv4), 1006 URL: "http://example.com/redir-path-too-long", 1007 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1008 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1009 ResolverAddrs: []string{"MockClient"}, 1010 }, 1011 }, 1012 }, 1013 { 1014 Name: "Wrong HTTP status code", 1015 Ident: identifier.NewDNS("example.com"), 1016 Path: "/bad-status-code", 1017 ExpectedProblem: probs.Unauthorized( 1018 "127.0.0.1: Invalid response from http://example.com/bad-status-code: 410"), 1019 ExpectedRecords: []core.ValidationRecord{ 1020 { 1021 Hostname: "example.com", 1022 Port: strconv.Itoa(httpPortIPv4), 1023 URL: "http://example.com/bad-status-code", 1024 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1025 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1026 ResolverAddrs: []string{"MockClient"}, 1027 }, 1028 }, 1029 }, 1030 { 1031 Name: "HTTP status code 303 redirect", 1032 Ident: identifier.NewDNS("example.com"), 1033 Path: "/303-see-other", 1034 ExpectedProblem: probs.Connection( 1035 "127.0.0.1: Fetching http://example.org/303-see-other: received disallowed redirect status code"), 1036 ExpectedRecords: []core.ValidationRecord{ 1037 { 1038 Hostname: "example.com", 1039 Port: strconv.Itoa(httpPortIPv4), 1040 URL: "http://example.com/303-see-other", 1041 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1042 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1043 ResolverAddrs: []string{"MockClient"}, 1044 }, 1045 }, 1046 }, 1047 { 1048 Name: "Response too large", 1049 Ident: identifier.NewDNS("example.com"), 1050 Path: "/resp-too-big", 1051 ExpectedProblem: probs.Unauthorized(fmt.Sprintf( 1052 "127.0.0.1: Invalid response from http://example.com/resp-too-big: %q", expectedTruncatedResp.String(), 1053 )), 1054 ExpectedRecords: []core.ValidationRecord{ 1055 { 1056 Hostname: "example.com", 1057 Port: strconv.Itoa(httpPortIPv4), 1058 URL: "http://example.com/resp-too-big", 1059 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1060 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1061 ResolverAddrs: []string{"MockClient"}, 1062 }, 1063 }, 1064 }, 1065 { 1066 Name: "Broken IPv6 only", 1067 Ident: identifier.NewDNS("ipv6.localhost"), 1068 Path: "/ok", 1069 ExpectedProblem: probs.Connection( 1070 "::1: Fetching http://ipv6.localhost/ok: Connection refused"), 1071 ExpectedRecords: []core.ValidationRecord{ 1072 { 1073 Hostname: "ipv6.localhost", 1074 Port: strconv.Itoa(httpPortIPv4), 1075 URL: "http://ipv6.localhost/ok", 1076 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")}, 1077 AddressUsed: netip.MustParseAddr("::1"), 1078 ResolverAddrs: []string{"MockClient"}, 1079 }, 1080 }, 1081 }, 1082 { 1083 Name: "Dual homed w/ broken IPv6, working IPv4", 1084 Ident: identifier.NewDNS("ipv4.and.ipv6.localhost"), 1085 Path: "/ok", 1086 ExpectedBody: "ok", 1087 ExpectedRecords: []core.ValidationRecord{ 1088 { 1089 Hostname: "ipv4.and.ipv6.localhost", 1090 Port: strconv.Itoa(httpPortIPv4), 1091 URL: "http://ipv4.and.ipv6.localhost/ok", 1092 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")}, 1093 // The first validation record should have used the IPv6 addr 1094 AddressUsed: netip.MustParseAddr("::1"), 1095 ResolverAddrs: []string{"MockClient"}, 1096 }, 1097 { 1098 Hostname: "ipv4.and.ipv6.localhost", 1099 Port: strconv.Itoa(httpPortIPv4), 1100 URL: "http://ipv4.and.ipv6.localhost/ok", 1101 AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")}, 1102 // The second validation record should have used the IPv4 addr as a fallback 1103 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1104 ResolverAddrs: []string{"MockClient"}, 1105 }, 1106 }, 1107 }, 1108 { 1109 Name: "Working IPv4 only", 1110 Ident: identifier.NewDNS("example.com"), 1111 Path: "/ok", 1112 ExpectedBody: "ok", 1113 ExpectedRecords: []core.ValidationRecord{ 1114 { 1115 Hostname: "example.com", 1116 Port: strconv.Itoa(httpPortIPv4), 1117 URL: "http://example.com/ok", 1118 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1119 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1120 ResolverAddrs: []string{"MockClient"}, 1121 }, 1122 }, 1123 }, 1124 { 1125 Name: "Redirect to uppercase Public Suffix", 1126 Ident: identifier.NewDNS("example.com"), 1127 Path: "/redir-uppercase-publicsuffix", 1128 ExpectedBody: "ok", 1129 ExpectedRecords: []core.ValidationRecord{ 1130 { 1131 Hostname: "example.com", 1132 Port: strconv.Itoa(httpPortIPv4), 1133 URL: "http://example.com/redir-uppercase-publicsuffix", 1134 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1135 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1136 ResolverAddrs: []string{"MockClient"}, 1137 }, 1138 { 1139 Hostname: "example.com", 1140 Port: strconv.Itoa(httpPortIPv4), 1141 URL: "http://example.com/ok", 1142 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1143 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1144 ResolverAddrs: []string{"MockClient"}, 1145 }, 1146 }, 1147 }, 1148 { 1149 Name: "Reflected response body containing printf verbs", 1150 Ident: identifier.NewDNS("example.com"), 1151 Path: "/printf-verbs", 1152 ExpectedProblem: &probs.ProblemDetails{ 1153 Type: probs.UnauthorizedProblem, 1154 Detail: fmt.Sprintf("127.0.0.1: Invalid response from http://example.com/printf-verbs: %q", 1155 ("%2F.well-known%2F" + expectedTruncatedResp.String())[:maxResponseSize]), 1156 HTTPStatus: http.StatusForbidden, 1157 }, 1158 ExpectedRecords: []core.ValidationRecord{ 1159 { 1160 Hostname: "example.com", 1161 Port: strconv.Itoa(httpPortIPv4), 1162 URL: "http://example.com/printf-verbs", 1163 AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")}, 1164 AddressUsed: netip.MustParseAddr("127.0.0.1"), 1165 ResolverAddrs: []string{"MockClient"}, 1166 }, 1167 }, 1168 }, 1169 } 1170 1171 for _, tc := range testCases { 1172 t.Run(tc.Name, func(t *testing.T) { 1173 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) 1174 defer cancel() 1175 var body []byte 1176 var records []core.ValidationRecord 1177 var err error 1178 if tc.IPv6 { 1179 body, records, err = vaIPv6.processHTTPValidation(ctx, tc.Ident, tc.Path) 1180 } else { 1181 body, records, err = vaIPv4.processHTTPValidation(ctx, tc.Ident, tc.Path) 1182 } 1183 if tc.ExpectedProblem == nil { 1184 test.AssertNotError(t, err, "expected nil prob") 1185 } else { 1186 test.AssertError(t, err, "expected non-nil prob") 1187 prob := detailedError(err) 1188 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem) 1189 } 1190 if tc.ExpectedBody != "" { 1191 test.AssertEquals(t, string(body), tc.ExpectedBody) 1192 } 1193 // in all cases we expect validation records to be present and matching expected 1194 test.AssertMarshaledEquals(t, records, tc.ExpectedRecords) 1195 }) 1196 } 1197 } 1198 1199 // All paths that get assigned to tokens MUST be valid tokens 1200 const pathWrongToken = "i6lNAC4lOOLYCl-A08VJt9z_tKYvVk63Dumo8icsBjQ" 1201 const path404 = "404" 1202 const path500 = "500" 1203 const pathFound = "GBq8SwWq3JsbREFdCamk5IX3KLsxW5ULeGs98Ajl_UM" 1204 const pathMoved = "5J4FIMrWNfmvHZo-QpKZngmuhqZGwRm21-oEgUDstJM" 1205 const pathRedirectInvalidPort = "port-redirect" 1206 const pathWait = "wait" 1207 const pathWaitLong = "wait-long" 1208 const pathReLookup = "7e-P57coLM7D3woNTp_xbJrtlkDYy6PWf3mSSbLwCr4" 1209 const pathReLookupInvalid = "re-lookup-invalid" 1210 const pathRedirectToFailingURL = "re-to-failing-url" 1211 const pathLooper = "looper" 1212 const pathValid = "valid" 1213 const rejectUserAgent = "rejectMe" 1214 1215 func httpSrv(t *testing.T, token string, ipv6 bool) *httptest.Server { 1216 m := http.NewServeMux() 1217 1218 server := httptest.NewUnstartedServer(m) 1219 1220 defaultToken := token 1221 currentToken := defaultToken 1222 1223 m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 1224 if strings.HasSuffix(r.URL.Path, path404) { 1225 t.Logf("HTTPSRV: Got a 404 req\n") 1226 http.NotFound(w, r) 1227 } else if strings.HasSuffix(r.URL.Path, path500) { 1228 t.Logf("HTTPSRV: Got a 500 req\n") 1229 http.Error(w, "Internal Server Error", http.StatusInternalServerError) 1230 } else if strings.HasSuffix(r.URL.Path, pathMoved) { 1231 t.Logf("HTTPSRV: Got a http.StatusMovedPermanently redirect req\n") 1232 if currentToken == defaultToken { 1233 currentToken = pathMoved 1234 } 1235 http.Redirect(w, r, pathValid, http.StatusMovedPermanently) 1236 } else if strings.HasSuffix(r.URL.Path, pathFound) { 1237 t.Logf("HTTPSRV: Got a http.StatusFound redirect req\n") 1238 if currentToken == defaultToken { 1239 currentToken = pathFound 1240 } 1241 http.Redirect(w, r, pathMoved, http.StatusFound) 1242 } else if strings.HasSuffix(r.URL.Path, pathWait) { 1243 t.Logf("HTTPSRV: Got a wait req\n") 1244 time.Sleep(time.Second * 3) 1245 } else if strings.HasSuffix(r.URL.Path, pathWaitLong) { 1246 t.Logf("HTTPSRV: Got a wait-long req\n") 1247 time.Sleep(time.Second * 10) 1248 } else if strings.HasSuffix(r.URL.Path, pathReLookup) { 1249 t.Logf("HTTPSRV: Got a redirect req to a valid hostname\n") 1250 if currentToken == defaultToken { 1251 currentToken = pathReLookup 1252 } 1253 port := getPort(server) 1254 http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/path", port), http.StatusFound) 1255 } else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) { 1256 t.Logf("HTTPSRV: Got a redirect req to an invalid host\n") 1257 http.Redirect(w, r, "http://invalid.invalid/path", http.StatusFound) 1258 } else if strings.HasSuffix(r.URL.Path, pathRedirectToFailingURL) { 1259 t.Logf("HTTPSRV: Redirecting to a URL that will fail\n") 1260 port := getPort(server) 1261 http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/%s", port, path500), http.StatusMovedPermanently) 1262 } else if strings.HasSuffix(r.URL.Path, pathLooper) { 1263 t.Logf("HTTPSRV: Got a loop req\n") 1264 http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently) 1265 } else if strings.HasSuffix(r.URL.Path, pathRedirectInvalidPort) { 1266 t.Logf("HTTPSRV: Got a port redirect req\n") 1267 // Port 8080 is not the VA's httpPort or httpsPort and should be rejected 1268 http.Redirect(w, r, "http://other.valid.com:8080/path", http.StatusFound) 1269 } else if r.Header.Get("User-Agent") == rejectUserAgent { 1270 w.WriteHeader(http.StatusBadRequest) 1271 w.Write([]byte("found trap User-Agent")) 1272 } else { 1273 t.Logf("HTTPSRV: Got a valid req\n") 1274 t.Logf("HTTPSRV: Path = %s\n", r.URL.Path) 1275 1276 ch := core.Challenge{Token: currentToken} 1277 keyAuthz, _ := ch.ExpectedKeyAuthorization(accountKey) 1278 t.Logf("HTTPSRV: Key Authz = '%s%s'\n", keyAuthz, "\\n\\r \\t") 1279 1280 fmt.Fprint(w, keyAuthz, "\n\r \t") 1281 currentToken = defaultToken 1282 } 1283 }) 1284 1285 if ipv6 { 1286 l, err := net.Listen("tcp", "[::1]:0") 1287 if err != nil { 1288 panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err)) 1289 } 1290 server.Listener = l 1291 } 1292 1293 server.Start() 1294 return server 1295 } 1296 1297 func TestHTTPBadPort(t *testing.T) { 1298 hs := httpSrv(t, expectedToken, false) 1299 defer hs.Close() 1300 1301 va, _ := setup(hs, "", nil, nil) 1302 1303 // Pick a random port between 40000 and 65000 - with great certainty we won't 1304 // have an HTTP server listening on this port and the test will fail as 1305 // intended 1306 badPort := 40000 + mrand.IntN(25000) 1307 va.httpPort = badPort 1308 1309 _, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), expectedToken, expectedKeyAuthorization) 1310 if err == nil { 1311 t.Fatalf("Server's down; expected refusal. Where did we connect?") 1312 } 1313 prob := detailedError(err) 1314 test.AssertEquals(t, prob.Type, probs.ConnectionProblem) 1315 if !strings.Contains(prob.Detail, "Connection refused") { 1316 t.Errorf("Expected a connection refused error, got %q", prob.Detail) 1317 } 1318 } 1319 1320 func TestHTTPBadIdentifier(t *testing.T) { 1321 hs := httpSrv(t, expectedToken, false) 1322 defer hs.Close() 1323 1324 va, _ := setup(hs, "", nil, nil) 1325 1326 _, err := va.validateHTTP01(ctx, identifier.ACMEIdentifier{Type: "smime", Value: "dobber@bad.horse"}, expectedToken, expectedKeyAuthorization) 1327 if err == nil { 1328 t.Fatalf("Server accepted a hypothetical S/MIME identifier") 1329 } 1330 prob := detailedError(err) 1331 test.AssertEquals(t, prob.Type, probs.MalformedProblem) 1332 if !strings.Contains(prob.Detail, "Identifier type for HTTP-01 challenge was not DNS or IP") { 1333 t.Errorf("Expected an identifier type error, got %q", prob.Detail) 1334 } 1335 } 1336 1337 func TestHTTPKeyAuthorizationFileMismatch(t *testing.T) { 1338 m := http.NewServeMux() 1339 hs := httptest.NewUnstartedServer(m) 1340 m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 1341 w.Write([]byte("\xef\xffAABBCC")) 1342 }) 1343 hs.Start() 1344 1345 va, _ := setup(hs, "", nil, nil) 1346 _, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), expectedToken, expectedKeyAuthorization) 1347 1348 if err == nil { 1349 t.Fatalf("Expected validation to fail when file mismatched.") 1350 } 1351 expected := fmt.Sprintf(`The key authorization file from the server did not match this challenge. Expected "%s" (got "\xef\xffAABBCC")`, expectedKeyAuthorization) 1352 if err.Error() != expected { 1353 t.Errorf("validation failed with %s, expected %s", err, expected) 1354 } 1355 } 1356 1357 func TestHTTP(t *testing.T) { 1358 hs := httpSrv(t, expectedToken, false) 1359 defer hs.Close() 1360 1361 va, log := setup(hs, "", nil, nil) 1362 1363 _, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), expectedToken, expectedKeyAuthorization) 1364 if err != nil { 1365 t.Errorf("Unexpected failure in HTTP validation for DNS: %s", err) 1366 } 1367 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1) 1368 1369 log.Clear() 1370 _, err = va.validateHTTP01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedToken, expectedKeyAuthorization) 1371 if err != nil { 1372 t.Errorf("Unexpected failure in HTTP validation for IPv4: %s", err) 1373 } 1374 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1) 1375 1376 log.Clear() 1377 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), path404, ka(path404)) 1378 if err == nil { 1379 t.Fatalf("Should have found a 404 for the challenge.") 1380 } 1381 test.AssertErrorIs(t, err, berrors.Unauthorized) 1382 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1) 1383 1384 log.Clear() 1385 // The "wrong token" will actually be the expectedToken. It's wrong 1386 // because it doesn't match pathWrongToken. 1387 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathWrongToken, ka(pathWrongToken)) 1388 if err == nil { 1389 t.Fatalf("Should have found the wrong token value.") 1390 } 1391 prob := detailedError(err) 1392 test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem) 1393 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1) 1394 1395 log.Clear() 1396 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathMoved, ka(pathMoved)) 1397 if err != nil { 1398 t.Fatalf("Failed to follow http.StatusMovedPermanently redirect") 1399 } 1400 redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"` 1401 matchedValidRedirect := log.GetAllMatching(redirectValid) 1402 test.AssertEquals(t, len(matchedValidRedirect), 1) 1403 1404 log.Clear() 1405 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathFound, ka(pathFound)) 1406 if err != nil { 1407 t.Fatalf("Failed to follow http.StatusFound redirect") 1408 } 1409 redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"` 1410 matchedMovedRedirect := log.GetAllMatching(redirectMoved) 1411 test.AssertEquals(t, len(matchedValidRedirect), 1) 1412 test.AssertEquals(t, len(matchedMovedRedirect), 1) 1413 1414 _, err = va.validateHTTP01(ctx, identifier.NewDNS("always.invalid"), pathFound, ka(pathFound)) 1415 if err == nil { 1416 t.Fatalf("Domain name is invalid.") 1417 } 1418 prob = detailedError(err) 1419 test.AssertEquals(t, prob.Type, probs.DNSProblem) 1420 } 1421 1422 func TestHTTPIPv6(t *testing.T) { 1423 hs := httpSrv(t, expectedToken, true) 1424 defer hs.Close() 1425 1426 va, log := setup(hs, "", nil, nil) 1427 1428 _, err := va.validateHTTP01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedToken, expectedKeyAuthorization) 1429 if err != nil { 1430 t.Errorf("Unexpected failure in HTTP validation for IPv6: %s", err) 1431 } 1432 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1) 1433 } 1434 1435 func TestHTTPTimeout(t *testing.T) { 1436 hs := httpSrv(t, expectedToken, false) 1437 defer hs.Close() 1438 1439 va, _ := setup(hs, "", nil, nil) 1440 1441 started := time.Now() 1442 timeout := 250 * time.Millisecond 1443 ctx, cancel := context.WithTimeout(context.Background(), timeout) 1444 defer cancel() 1445 _, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), pathWaitLong, ka(pathWaitLong)) 1446 if err == nil { 1447 t.Fatalf("Connection should've timed out") 1448 } 1449 1450 took := time.Since(started) 1451 // Check that the HTTP connection doesn't return before a timeout, and times 1452 // out after the expected time 1453 if took < timeout-200*time.Millisecond { 1454 t.Fatalf("HTTP timed out before %s: %s with %s", timeout, took, err) 1455 } 1456 if took > 2*timeout { 1457 t.Fatalf("HTTP connection didn't timeout after %s", timeout) 1458 } 1459 prob := detailedError(err) 1460 test.AssertEquals(t, prob.Type, probs.ConnectionProblem) 1461 test.AssertEquals(t, prob.Detail, "127.0.0.1: Fetching http://localhost/.well-known/acme-challenge/wait-long: Timeout after connect (your server may be slow or overloaded)") 1462 } 1463 1464 func TestHTTPRedirectLookup(t *testing.T) { 1465 hs := httpSrv(t, expectedToken, false) 1466 defer hs.Close() 1467 va, log := setup(hs, "", nil, nil) 1468 1469 _, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathMoved, ka(pathMoved)) 1470 if err != nil { 1471 t.Fatalf("Unexpected failure in redirect (%s): %s", pathMoved, err) 1472 } 1473 redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"` 1474 matchedValidRedirect := log.GetAllMatching(redirectValid) 1475 test.AssertEquals(t, len(matchedValidRedirect), 1) 1476 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 2) 1477 1478 log.Clear() 1479 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathFound, ka(pathFound)) 1480 if err != nil { 1481 t.Fatalf("Unexpected failure in redirect (%s): %s", pathFound, err) 1482 } 1483 redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"` 1484 matchedMovedRedirect := log.GetAllMatching(redirectMoved) 1485 test.AssertEquals(t, len(matchedMovedRedirect), 1) 1486 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 3) 1487 1488 log.Clear() 1489 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathReLookupInvalid, ka(pathReLookupInvalid)) 1490 test.AssertError(t, err, "error for pathReLookupInvalid should not be nil") 1491 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1) 1492 prob := detailedError(err) 1493 test.AssertDeepEquals(t, prob, probs.Connection(`127.0.0.1: Fetching http://invalid.invalid/path: Invalid host in redirect target, must end in IANA registered TLD`)) 1494 1495 log.Clear() 1496 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathReLookup, ka(pathReLookup)) 1497 if err != nil { 1498 t.Fatalf("Unexpected error in redirect (%s): %s", pathReLookup, err) 1499 } 1500 redirectPattern := `following redirect to host "" url "http://other.valid.com:\d+/path"` 1501 test.AssertEquals(t, len(log.GetAllMatching(redirectPattern)), 1) 1502 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1) 1503 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid.com: \[127.0.0.1\]`)), 1) 1504 1505 log.Clear() 1506 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathRedirectInvalidPort, ka(pathRedirectInvalidPort)) 1507 test.AssertNotNil(t, err, "error for pathRedirectInvalidPort should not be nil") 1508 prob = detailedError(err) 1509 test.AssertEquals(t, prob.Detail, fmt.Sprintf( 1510 "127.0.0.1: Fetching http://other.valid.com:8080/path: Invalid port in redirect target. "+ 1511 "Only ports %d and %d are supported, not 8080", va.httpPort, va.httpsPort)) 1512 1513 // This case will redirect from a valid host to a host that is throwing 1514 // HTTP 500 errors. The test case is ensuring that the connection error 1515 // is referencing the redirected to host, instead of the original host. 1516 log.Clear() 1517 _, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathRedirectToFailingURL, ka(pathRedirectToFailingURL)) 1518 test.AssertNotNil(t, err, "err should not be nil") 1519 prob = detailedError(err) 1520 test.AssertDeepEquals(t, prob, 1521 probs.Unauthorized( 1522 fmt.Sprintf("127.0.0.1: Invalid response from http://other.valid.com:%d/500: 500", 1523 va.httpPort))) 1524 } 1525 1526 func TestHTTPRedirectLoop(t *testing.T) { 1527 hs := httpSrv(t, expectedToken, false) 1528 defer hs.Close() 1529 va, _ := setup(hs, "", nil, nil) 1530 1531 _, prob := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), "looper", ka("looper")) 1532 if prob == nil { 1533 t.Fatalf("Challenge should have failed for looper") 1534 } 1535 } 1536 1537 func TestHTTPRedirectUserAgent(t *testing.T) { 1538 hs := httpSrv(t, expectedToken, false) 1539 defer hs.Close() 1540 va, _ := setup(hs, "", nil, nil) 1541 va.userAgent = rejectUserAgent 1542 1543 _, prob := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), pathMoved, ka(pathMoved)) 1544 if prob == nil { 1545 t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathMoved) 1546 } 1547 1548 _, prob = va.validateHTTP01(ctx, identifier.NewDNS("localhost"), pathFound, ka(pathFound)) 1549 if prob == nil { 1550 t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathFound) 1551 } 1552 } 1553 1554 func getPort(hs *httptest.Server) int { 1555 url, err := url.Parse(hs.URL) 1556 if err != nil { 1557 panic(fmt.Sprintf("Failed to parse hs URL: %q - %s", hs.URL, err.Error())) 1558 } 1559 _, portString, err := net.SplitHostPort(url.Host) 1560 if err != nil { 1561 panic(fmt.Sprintf("Failed to split hs URL host: %q - %s", url.Host, err.Error())) 1562 } 1563 port, err := strconv.ParseInt(portString, 10, 64) 1564 if err != nil { 1565 panic(fmt.Sprintf("Failed to parse hs URL port: %q - %s", portString, err.Error())) 1566 } 1567 return int(port) 1568 } 1569 1570 func TestValidateHTTP(t *testing.T) { 1571 token := core.NewToken() 1572 1573 hs := httpSrv(t, token, false) 1574 defer hs.Close() 1575 1576 va, _ := setup(hs, "", nil, nil) 1577 1578 _, prob := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), token, ka(token)) 1579 test.Assert(t, prob == nil, "validation failed") 1580 } 1581 1582 func TestLimitedReader(t *testing.T) { 1583 token := core.NewToken() 1584 1585 hs := httpSrv(t, "012345\xff67890123456789012345678901234567890123456789012345678901234567890123456789", false) 1586 va, _ := setup(hs, "", nil, nil) 1587 defer hs.Close() 1588 1589 _, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), token, ka(token)) 1590 1591 prob := detailedError(err) 1592 test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem) 1593 test.Assert(t, strings.HasPrefix(prob.Detail, "127.0.0.1: Invalid response from "), 1594 "Expected failure due to truncation") 1595 1596 if !utf8.ValidString(err.Error()) { 1597 t.Errorf("Problem Detail contained an invalid UTF-8 string") 1598 } 1599 } 1600 1601 type hostHeaderHandler struct { 1602 host string 1603 } 1604 1605 func (handler *hostHeaderHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 1606 handler.host = req.Host 1607 } 1608 1609 // TestHTTPHostHeader tests compliance with RFC 8555, Sec. 8.3 & RFC 8738, Sec. 1610 // 5. 1611 func TestHTTPHostHeader(t *testing.T) { 1612 testCases := []struct { 1613 Name string 1614 Ident identifier.ACMEIdentifier 1615 IPv6 bool 1616 want string 1617 }{ 1618 { 1619 Name: "DNS name", 1620 Ident: identifier.NewDNS("example.com"), 1621 want: "example.com", 1622 }, 1623 { 1624 Name: "IPv4 address", 1625 Ident: identifier.NewIP(netip.MustParseAddr("127.0.0.1")), 1626 want: "127.0.0.1", 1627 }, 1628 { 1629 Name: "IPv6 address", 1630 Ident: identifier.NewIP(netip.MustParseAddr("::1")), 1631 IPv6: true, 1632 want: "[::1]", 1633 }, 1634 } 1635 1636 for _, tc := range testCases { 1637 t.Run(tc.Name, func(t *testing.T) { 1638 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) 1639 defer cancel() 1640 1641 handler := hostHeaderHandler{} 1642 testSrv := httptest.NewUnstartedServer(&handler) 1643 1644 if tc.IPv6 { 1645 l, err := net.Listen("tcp", "[::1]:0") 1646 if err != nil { 1647 panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err)) 1648 } 1649 testSrv.Listener = l 1650 } 1651 1652 testSrv.Start() 1653 defer testSrv.Close() 1654 1655 // Setup VA. By providing the testSrv to setup the VA will use the 1656 // testSrv's randomly assigned port as its HTTP port. 1657 va, _ := setup(testSrv, "", nil, nil) 1658 1659 var got string 1660 _, _, _ = va.processHTTPValidation(ctx, tc.Ident, "/ok") 1661 got = handler.host 1662 if got != tc.want { 1663 t.Errorf("Got host %#v, but want %#v", got, tc.want) 1664 } 1665 }) 1666 } 1667 }