github.com/letsencrypt/boulder@v0.20251208.0/va/http.go (about)

     1  package va
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"net/netip"
    12  	"net/url"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  	"unicode"
    17  
    18  	"github.com/letsencrypt/boulder/bdns"
    19  	"github.com/letsencrypt/boulder/core"
    20  	berrors "github.com/letsencrypt/boulder/errors"
    21  	"github.com/letsencrypt/boulder/iana"
    22  	"github.com/letsencrypt/boulder/identifier"
    23  )
    24  
    25  const (
    26  	// maxRedirect is the maximum number of redirects the VA will follow
    27  	// processing an HTTP-01 challenge.
    28  	maxRedirect = 10
    29  	// maxResponseSize holds the maximum number of bytes that will be read from an
    30  	// HTTP-01 challenge response. The expected payload should be ~87 bytes. Since
    31  	// it may be padded by whitespace which we previously allowed accept up to 128
    32  	// bytes before rejecting a response (32 byte b64 encoded token + . + 32 byte
    33  	// b64 encoded key fingerprint).
    34  	maxResponseSize = 128
    35  	// maxPathSize is the maximum number of bytes we will accept in the path of a
    36  	// redirect URL.
    37  	maxPathSize = 2000
    38  )
    39  
    40  // preresolvedDialer is a struct type that provides a DialContext function which
    41  // will connect to the provided IP and port instead of letting DNS resolve
    42  // The hostname of the preresolvedDialer is used to ensure the dial only completes
    43  // using the pre-resolved IP/port when used for the correct host.
    44  type preresolvedDialer struct {
    45  	ip       netip.Addr
    46  	port     int
    47  	hostname string
    48  	timeout  time.Duration
    49  }
    50  
    51  // a dialerMismatchError is produced when a preresolvedDialer is used to dial
    52  // a host other than the dialer's specified hostname.
    53  type dialerMismatchError struct {
    54  	// The original dialer information
    55  	dialerHost string
    56  	dialerIP   string
    57  	dialerPort int
    58  	// The host that the dialer was incorrectly used with
    59  	host string
    60  }
    61  
    62  func (e *dialerMismatchError) Error() string {
    63  	return fmt.Sprintf(
    64  		"preresolvedDialer mismatch: dialer is for %q (ip: %q port: %d) not %q",
    65  		e.dialerHost, e.dialerIP, e.dialerPort, e.host)
    66  }
    67  
    68  // DialContext for a preresolvedDialer shaves 10ms off of the context it was
    69  // given before calling the default transport DialContext using the pre-resolved
    70  // IP and port as the host. If the original host being dialed by DialContext
    71  // does not match the expected hostname in the preresolvedDialer an error will
    72  // be returned instead. This helps prevents a bug that might use
    73  // a preresolvedDialer for the wrong host.
    74  //
    75  // Shaving the context helps us be able to differentiate between timeouts during
    76  // connect and timeouts after connect.
    77  //
    78  // Using preresolved information for the host argument given to the real
    79  // transport dial lets us have fine grained control over IP address resolution for
    80  // domain names.
    81  func (d *preresolvedDialer) DialContext(
    82  	ctx context.Context,
    83  	network,
    84  	origAddr string) (net.Conn, error) {
    85  	deadline, ok := ctx.Deadline()
    86  	if !ok {
    87  		// Shouldn't happen: All requests should have a deadline by this point.
    88  		deadline = time.Now().Add(100 * time.Second)
    89  	} else {
    90  		// Set the context deadline slightly shorter than the HTTP deadline, so we
    91  		// get a useful error rather than a generic "deadline exceeded" error. This
    92  		// lets us give a more specific error to the subscriber.
    93  		deadline = deadline.Add(-10 * time.Millisecond)
    94  	}
    95  	ctx, cancel := context.WithDeadline(ctx, deadline)
    96  	defer cancel()
    97  
    98  	// NOTE(@cpu): I don't capture and check the origPort here because using
    99  	// `net.SplitHostPort` and also supporting the va's custom httpPort and
   100  	// httpsPort is cumbersome. The initial origAddr may be "example.com:80"
   101  	// if the URL used for the dial input was "http://example.com" without an
   102  	// explicit port. Checking for equality here will fail unless we add
   103  	// special case logic for converting 80/443 -> httpPort/httpsPort when
   104  	// configured. This seems more likely to cause bugs than catch them so I'm
   105  	// ignoring this for now. In the future if we remove the httpPort/httpsPort
   106  	// (we should!) we can also easily enforce that the preresolved dialer port
   107  	// matches expected here.
   108  	origHost, _, err := net.SplitHostPort(origAddr)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	// If the hostname we're dialing isn't equal to the hostname the dialer was
   113  	// constructed for then a bug has occurred where we've mismatched the
   114  	// preresolved dialer.
   115  	if origHost != d.hostname {
   116  		return nil, &dialerMismatchError{
   117  			dialerHost: d.hostname,
   118  			dialerIP:   d.ip.String(),
   119  			dialerPort: d.port,
   120  			host:       origHost,
   121  		}
   122  	}
   123  
   124  	// Make a new dial address using the pre-resolved IP and port.
   125  	targetAddr := net.JoinHostPort(d.ip.String(), strconv.Itoa(d.port))
   126  
   127  	// Create a throw-away dialer using default values and the dialer timeout
   128  	// (populated from the VA singleDialTimeout).
   129  	throwAwayDialer := &net.Dialer{
   130  		Timeout: d.timeout,
   131  		// Default KeepAlive - see Golang src/net/http/transport.go DefaultTransport
   132  		KeepAlive: 30 * time.Second,
   133  	}
   134  	return throwAwayDialer.DialContext(ctx, network, targetAddr)
   135  }
   136  
   137  // a dialerFunc meets the function signature requirements of
   138  // a http.Transport.DialContext handler.
   139  type dialerFunc func(ctx context.Context, network, addr string) (net.Conn, error)
   140  
   141  // httpTransport constructs a HTTP Transport with settings appropriate for
   142  // HTTP-01 validation. The provided dialerFunc is used as the Transport's
   143  // DialContext handler.
   144  func httpTransport(df dialerFunc) *http.Transport {
   145  	return &http.Transport{
   146  		DialContext: df,
   147  		// We are talking to a client that does not yet have a certificate,
   148  		// so we accept a temporary, invalid one.
   149  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   150  		// We don't expect to make multiple requests to a client, so close
   151  		// connection immediately.
   152  		DisableKeepAlives: true,
   153  		// We don't want idle connections, but 0 means "unlimited," so we pick 1.
   154  		MaxIdleConns:        1,
   155  		IdleConnTimeout:     time.Second,
   156  		TLSHandshakeTimeout: 10 * time.Second,
   157  	}
   158  }
   159  
   160  // httpValidationTarget bundles all of the information needed to make an HTTP-01
   161  // validation request against a target.
   162  type httpValidationTarget struct {
   163  	// the host being validated
   164  	host string
   165  	// the port for the validation request
   166  	port int
   167  	// the path for the validation request
   168  	path string
   169  	// query data for validation request (potentially populated when
   170  	// following redirects)
   171  	query string
   172  	// all of the IP addresses available for the host
   173  	available []netip.Addr
   174  	// the IP addresses that were tried for validation previously that were cycled
   175  	// out of cur by calls to nextIP()
   176  	tried []netip.Addr
   177  	// the IP addresses that will be drawn from by calls to nextIP() to set curIP
   178  	next []netip.Addr
   179  	// the current IP address being used for validation (if any)
   180  	cur netip.Addr
   181  	// the DNS resolver(s) that will attempt to fulfill the validation request
   182  	resolvers bdns.ResolverAddrs
   183  }
   184  
   185  // nextIP changes the cur IP by removing the first entry from the next slice and
   186  // setting it to cur. If cur was previously set the value will be added to the
   187  // tried slice to keep track of IPs that were previously used. If nextIP() is
   188  // called but vt.next is empty an error is returned.
   189  func (vt *httpValidationTarget) nextIP() error {
   190  	if len(vt.next) == 0 {
   191  		return fmt.Errorf(
   192  			"host %q has no IP addresses remaining to use",
   193  			vt.host)
   194  	}
   195  	vt.tried = append(vt.tried, vt.cur)
   196  	vt.cur = vt.next[0]
   197  	vt.next = vt.next[1:]
   198  	return nil
   199  }
   200  
   201  // newHTTPValidationTarget creates a httpValidationTarget for the given host,
   202  // port, and path. This involves querying DNS for the IP addresses for the host.
   203  // An error is returned if there are no usable IP addresses or if the DNS
   204  // lookups fail.
   205  func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
   206  	ctx context.Context,
   207  	ident identifier.ACMEIdentifier,
   208  	port int,
   209  	path string,
   210  	query string) (*httpValidationTarget, error) {
   211  	var addrs []netip.Addr
   212  	var resolvers bdns.ResolverAddrs
   213  	switch ident.Type {
   214  	case identifier.TypeDNS:
   215  		// Resolve IP addresses for the identifier
   216  		dnsAddrs, dnsResolvers, err := va.getAddrs(ctx, ident.Value)
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  		addrs, resolvers = dnsAddrs, dnsResolvers
   221  	case identifier.TypeIP:
   222  		netIP, err := netip.ParseAddr(ident.Value)
   223  		if err != nil {
   224  			return nil, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err)
   225  		}
   226  		addrs = []netip.Addr{netIP}
   227  	default:
   228  		return nil, fmt.Errorf("unknown identifier type: %s", ident.Type)
   229  	}
   230  
   231  	target := &httpValidationTarget{
   232  		host:      ident.Value,
   233  		port:      port,
   234  		path:      path,
   235  		query:     query,
   236  		available: addrs,
   237  		resolvers: resolvers,
   238  	}
   239  
   240  	// Separate the addresses into the available v4 and v6 addresses
   241  	v4Addrs, v6Addrs := availableAddresses(addrs)
   242  	hasV6Addrs := len(v6Addrs) > 0
   243  	hasV4Addrs := len(v4Addrs) > 0
   244  
   245  	if !hasV6Addrs && !hasV4Addrs {
   246  		// If there are no v6 addrs and no v4addrs there was a bug with getAddrs or
   247  		// availableAddresses and we need to return an error.
   248  		return nil, fmt.Errorf("host %q has no IPv4 or IPv6 addresses", ident.Value)
   249  	} else if !hasV6Addrs && hasV4Addrs {
   250  		// If there are no v6 addrs and there are v4 addrs then use the first v4
   251  		// address. There's no fallback address.
   252  		target.next = []netip.Addr{v4Addrs[0]}
   253  	} else if hasV6Addrs && hasV4Addrs {
   254  		// If there are both v6 addrs and v4 addrs then use the first v6 address and
   255  		// fallback with the first v4 address.
   256  		target.next = []netip.Addr{v6Addrs[0], v4Addrs[0]}
   257  	} else if hasV6Addrs && !hasV4Addrs {
   258  		// If there are just v6 addrs then use the first v6 address. There's no
   259  		// fallback address.
   260  		target.next = []netip.Addr{v6Addrs[0]}
   261  	}
   262  
   263  	// Advance the target using nextIP to populate the cur IP before returning
   264  	_ = target.nextIP()
   265  	return target, nil
   266  }
   267  
   268  // extractRequestTarget extracts the host and port specified in the provided
   269  // HTTP redirect request. If the request's URL's protocol schema is not HTTP or
   270  // HTTPS an error is returned. If an explicit port is specified in the request's
   271  // URL and it isn't the VA's HTTP or HTTPS port, an error is returned.
   272  func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (identifier.ACMEIdentifier, int, error) {
   273  	// A nil request is certainly not a valid redirect and has no port to extract.
   274  	if req == nil {
   275  		return identifier.ACMEIdentifier{}, 0, fmt.Errorf("redirect HTTP request was nil")
   276  	}
   277  
   278  	reqScheme := req.URL.Scheme
   279  
   280  	// The redirect request must use HTTP or HTTPs protocol schemes regardless of the port..
   281  	if reqScheme != "http" && reqScheme != "https" {
   282  		return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError(
   283  			"Invalid protocol scheme in redirect target. "+
   284  				`Only "http" and "https" protocol schemes are supported, not %q`, reqScheme)
   285  	}
   286  
   287  	// Try to parse an explicit port number from the request URL host. If there
   288  	// is one, we need to make sure its a valid port. If there isn't one we need
   289  	// to pick the port based on the reqScheme default port.
   290  	reqHost := req.URL.Hostname()
   291  	var reqPort int
   292  	// URL.Port() will return "" for an invalid port, not just an empty port. To
   293  	// reject invalid ports, we rely on the calling function having used
   294  	// URL.Parse(), which does enforce validity.
   295  	if req.URL.Port() != "" {
   296  		parsedPort, err := strconv.Atoi(req.URL.Port())
   297  		if err != nil {
   298  			return identifier.ACMEIdentifier{}, 0, err
   299  		}
   300  
   301  		// The explicit port must match the VA's configured HTTP or HTTPS port.
   302  		if parsedPort != va.httpPort && parsedPort != va.httpsPort {
   303  			return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError(
   304  				"Invalid port in redirect target. Only ports %d and %d are supported, not %d",
   305  				va.httpPort, va.httpsPort, parsedPort)
   306  		}
   307  
   308  		reqPort = parsedPort
   309  	} else if reqScheme == "http" {
   310  		reqPort = va.httpPort
   311  	} else if reqScheme == "https" {
   312  		reqPort = va.httpsPort
   313  	} else {
   314  		// This shouldn't happen but defensively return an internal server error in
   315  		// case it does.
   316  		return identifier.ACMEIdentifier{}, 0, fmt.Errorf("unable to determine redirect HTTP request port")
   317  	}
   318  
   319  	if reqHost == "" {
   320  		return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid empty host in redirect target")
   321  	}
   322  
   323  	// Often folks will misconfigure their webserver to send an HTTP redirect
   324  	// missing a `/' between the FQDN and the path. E.g. in Apache using:
   325  	//   Redirect / https://bad-redirect.org
   326  	// Instead of
   327  	//   Redirect / https://bad-redirect.org/
   328  	// Will produce an invalid HTTP-01 redirect target like:
   329  	//   https://bad-redirect.org.well-known/acme-challenge/xxxx
   330  	// This happens frequently enough we want to return a distinct error message
   331  	// for this case by detecting the reqHost ending in ".well-known".
   332  	if strings.HasSuffix(reqHost, ".well-known") {
   333  		return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError(
   334  			"Invalid host in redirect target %q. Check webserver config for missing '/' in redirect target.",
   335  			reqHost,
   336  		)
   337  	}
   338  
   339  	reqIP, err := netip.ParseAddr(reqHost)
   340  	if err == nil {
   341  		// Reject IPv6 addresses with a scope zone (RFCs 4007 & 6874)
   342  		if reqIP.Zone() != "" {
   343  			return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: contains scope zone")
   344  		}
   345  		err := va.isReservedIPFunc(reqIP)
   346  		if err != nil {
   347  			return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: %s", err)
   348  		}
   349  		return identifier.NewIP(reqIP), reqPort, nil
   350  	}
   351  
   352  	if _, err := iana.ExtractSuffix(reqHost); err != nil {
   353  		return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target, must end in IANA registered TLD")
   354  	}
   355  
   356  	return identifier.NewDNS(reqHost), reqPort, nil
   357  }
   358  
   359  // setupHTTPValidation sets up a preresolvedDialer and a validation record for
   360  // the given request URL and httpValidationTarget. If the req URL is empty, or
   361  // the validation target is nil or has no available IP addresses, an error will
   362  // be returned.
   363  func (va *ValidationAuthorityImpl) setupHTTPValidation(
   364  	reqURL string,
   365  	target *httpValidationTarget) (*preresolvedDialer, core.ValidationRecord, error) {
   366  	if reqURL == "" {
   367  		return nil,
   368  			core.ValidationRecord{},
   369  			fmt.Errorf("reqURL can not be nil")
   370  	}
   371  	if target == nil {
   372  		// This is the only case where returning an empty validation record makes
   373  		// sense - we can't construct a better one, something has gone quite wrong.
   374  		return nil,
   375  			core.ValidationRecord{},
   376  			fmt.Errorf("httpValidationTarget can not be nil")
   377  	}
   378  
   379  	// Construct a base validation record with the validation target's
   380  	// information.
   381  	record := core.ValidationRecord{
   382  		Hostname:          target.host,
   383  		Port:              strconv.Itoa(target.port),
   384  		AddressesResolved: target.available,
   385  		URL:               reqURL,
   386  		ResolverAddrs:     target.resolvers,
   387  	}
   388  
   389  	// Get the target IP to build a preresolved dialer with
   390  	targetIP := target.cur
   391  	if (targetIP == netip.Addr{}) {
   392  		return nil,
   393  			record,
   394  			fmt.Errorf(
   395  				"host %q has no IP addresses remaining to use",
   396  				target.host)
   397  	}
   398  
   399  	// This is a backstop check to avoid connecting to reserved IP addresses.
   400  	// They should have been caught and excluded by `bdns.LookupHost`.
   401  	err := va.isReservedIPFunc(targetIP)
   402  	if err != nil {
   403  		return nil, record, err
   404  	}
   405  
   406  	record.AddressUsed = targetIP
   407  
   408  	dialer := &preresolvedDialer{
   409  		ip:       targetIP,
   410  		port:     target.port,
   411  		hostname: target.host,
   412  		timeout:  va.singleDialTimeout,
   413  	}
   414  	return dialer, record, nil
   415  }
   416  
   417  // fallbackErr returns true only for net.OpError instances where the op is equal
   418  // to "dial", or url.Error instances wrapping such an error. fallbackErr returns
   419  // false for all other errors. By policy, only dial errors (not read or write
   420  // errors) are eligible for fallback from an IPv6 to an IPv4 address.
   421  func fallbackErr(err error) bool {
   422  	// Err shouldn't ever be nil if we're considering it for fallback
   423  	if err == nil {
   424  		return false
   425  	}
   426  	// Net OpErrors are fallback errs only if the operation was a "dial"
   427  	// All other errs are not fallback errs
   428  	var netOpError *net.OpError
   429  	return errors.As(err, &netOpError) && netOpError.Op == "dial"
   430  }
   431  
   432  // processHTTPValidation performs an HTTP validation for the given host, port
   433  // and path. If successful the body of the HTTP response is returned along with
   434  // the validation records created during the validation. If not successful
   435  // a non-nil error and potentially some ValidationRecords are returned.
   436  func (va *ValidationAuthorityImpl) processHTTPValidation(
   437  	ctx context.Context,
   438  	ident identifier.ACMEIdentifier,
   439  	path string) ([]byte, []core.ValidationRecord, error) {
   440  	// Create a target for the host, port and path with no query parameters
   441  	target, err := va.newHTTPValidationTarget(ctx, ident, va.httpPort, path, "")
   442  	if err != nil {
   443  		return nil, nil, err
   444  	}
   445  
   446  	// When constructing a URL, bare IPv6 addresses must be enclosed in square
   447  	// brackets. Otherwise, a colon may be interpreted as a port separator.
   448  	host := ident.Value
   449  	if ident.Type == identifier.TypeIP {
   450  		netipHost, err := netip.ParseAddr(host)
   451  		if err != nil {
   452  			return nil, nil, fmt.Errorf("couldn't parse IP address from identifier")
   453  		}
   454  		if !netipHost.Is4() {
   455  			host = "[" + host + "]"
   456  		}
   457  	}
   458  
   459  	// Create an initial GET Request
   460  	initialURL := url.URL{
   461  		Scheme: "http",
   462  		Host:   host,
   463  		Path:   path,
   464  	}
   465  	initialReq, err := http.NewRequest("GET", initialURL.String(), nil)
   466  	if err != nil {
   467  		return nil, nil, newIPError(target.cur, err)
   468  	}
   469  
   470  	// Add a context to the request. Shave some time from the
   471  	// overall context deadline so that we are not racing with gRPC when the
   472  	// HTTP server is timing out. This avoids returning ServerInternal
   473  	// errors when we should be returning Connection errors. This may fix a flaky
   474  	// integration test: https://github.com/letsencrypt/boulder/issues/4087
   475  	// Note: The gRPC interceptor in grpc/interceptors.go already shaves some time
   476  	// off RPCs, but this takes off additional time because HTTP-related timeouts
   477  	// are so common (and because it might fix a flaky build).
   478  	deadline, ok := ctx.Deadline()
   479  	if !ok {
   480  		return nil, nil, fmt.Errorf("processHTTPValidation had no deadline")
   481  	} else {
   482  		deadline = deadline.Add(-200 * time.Millisecond)
   483  	}
   484  	ctx, cancel := context.WithDeadline(ctx, deadline)
   485  	defer cancel()
   486  	initialReq = initialReq.WithContext(ctx)
   487  	if va.userAgent != "" {
   488  		initialReq.Header.Set("User-Agent", va.userAgent)
   489  	}
   490  	// Some of our users use mod_security. Mod_security sees a lack of Accept
   491  	// headers as bot behavior and rejects requests. While this is a bug in
   492  	// mod_security's rules (given that the HTTP specs disagree with that
   493  	// requirement), we add the Accept header now in order to fix our
   494  	// mod_security users' mysterious breakages. See
   495  	// <https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/265> and
   496  	// <https://github.com/letsencrypt/boulder/issues/1019>. This was done
   497  	// because it's a one-line fix with no downside. We're not likely to want to
   498  	// do many more things to satisfy misunderstandings around HTTP.
   499  	initialReq.Header.Set("Accept", "*/*")
   500  
   501  	// Set up the initial validation request and a base validation record
   502  	dialer, baseRecord, err := va.setupHTTPValidation(initialReq.URL.String(), target)
   503  	if err != nil {
   504  		return nil, []core.ValidationRecord{}, newIPError(target.cur, err)
   505  	}
   506  
   507  	// Build a transport for this validation that will use the preresolvedDialer's
   508  	// DialContext function
   509  	transport := httpTransport(dialer.DialContext)
   510  
   511  	va.log.AuditInfof("Attempting to validate HTTP-01 for %q with GET to %q",
   512  		initialReq.Host, initialReq.URL.String())
   513  
   514  	// Create a closure around records & numRedirects we can use with a HTTP
   515  	// client to process redirects per our own policy (e.g. resolving IP
   516  	// addresses explicitly, not following redirects to ports != [80,443], etc)
   517  	records := []core.ValidationRecord{baseRecord}
   518  	numRedirects := 0
   519  	processRedirect := func(req *http.Request, via []*http.Request) error {
   520  		va.log.Debugf("processing a HTTP redirect from the server to %q", req.URL.String())
   521  		// Only process up to maxRedirect redirects
   522  		if numRedirects > maxRedirect {
   523  			return berrors.ConnectionFailureError("Too many redirects")
   524  		}
   525  		numRedirects++
   526  		va.metrics.http01Redirects.Inc()
   527  
   528  		if req.Response.TLS != nil && req.Response.TLS.Version < tls.VersionTLS12 {
   529  			return berrors.ConnectionFailureError(
   530  				"validation attempt was redirected to an HTTPS server that doesn't " +
   531  					"support TLSv1.2 or better. See " +
   532  					"https://community.letsencrypt.org/t/rejecting-sha-1-csrs-and-validation-using-tls-1-0-1-1-urls/175144")
   533  		}
   534  
   535  		// If the response contains an HTTP 303 or any other forbidden redirect,
   536  		// do not follow it. The four allowed redirect status codes are defined
   537  		// explicitly in BRs Section 3.2.2.4.19. Although the go stdlib currently
   538  		// limits redirects to a set of status codes with only one additional
   539  		// entry (303), we capture the full list of allowed codes here in case the
   540  		// go stdlib expands the set of redirects it follows in the future.
   541  		acceptableRedirects := map[int]struct{}{
   542  			301: {}, 302: {}, 307: {}, 308: {},
   543  		}
   544  		if _, present := acceptableRedirects[req.Response.StatusCode]; !present {
   545  			return berrors.ConnectionFailureError("received disallowed redirect status code")
   546  		}
   547  
   548  		// Lowercase the redirect host immediately, as the dialer and redirect
   549  		// validation expect it to have been lowercased already.
   550  		req.URL.Host = strings.ToLower(req.URL.Host)
   551  
   552  		// Extract the redirect target's host and port. This will return an error if
   553  		// the redirect request scheme, host or port is not acceptable.
   554  		redirHost, redirPort, err := va.extractRequestTarget(req)
   555  		if err != nil {
   556  			return err
   557  		}
   558  
   559  		redirPath := req.URL.Path
   560  		if len(redirPath) > maxPathSize {
   561  			return berrors.ConnectionFailureError("Redirect target too long")
   562  		}
   563  
   564  		// If the redirect URL has query parameters we need to preserve
   565  		// those in the redirect path
   566  		redirQuery := ""
   567  		if req.URL.RawQuery != "" {
   568  			redirQuery = req.URL.RawQuery
   569  		}
   570  
   571  		// Check for a redirect loop. If any URL is found twice before the
   572  		// redirect limit, return error.
   573  		for _, record := range records {
   574  			if req.URL.String() == record.URL {
   575  				return berrors.ConnectionFailureError("Redirect loop detected")
   576  			}
   577  		}
   578  
   579  		// Create a validation target for the redirect host. This will resolve IP
   580  		// addresses for the host explicitly.
   581  		redirTarget, err := va.newHTTPValidationTarget(ctx, redirHost, redirPort, redirPath, redirQuery)
   582  		if err != nil {
   583  			return err
   584  		}
   585  
   586  		// Setup validation for the target. This will produce a preresolved dialer we can
   587  		// assign to the client transport in order to connect to the redirect target using
   588  		// the IP address we selected.
   589  		redirDialer, redirRecord, err := va.setupHTTPValidation(req.URL.String(), redirTarget)
   590  		records = append(records, redirRecord)
   591  		if err != nil {
   592  			return err
   593  		}
   594  
   595  		va.log.Debugf("following redirect to host %q url %q", req.Host, req.URL.String())
   596  		// Replace the transport's DialContext with the new preresolvedDialer for
   597  		// the redirect.
   598  		transport.DialContext = redirDialer.DialContext
   599  		return nil
   600  	}
   601  
   602  	// Create a new HTTP client configured to use the customized transport and
   603  	// to check HTTP redirects encountered with processRedirect
   604  	client := http.Client{
   605  		Transport:     transport,
   606  		CheckRedirect: processRedirect,
   607  	}
   608  
   609  	// Make the initial validation request. This may result in redirects being
   610  	// followed.
   611  	httpResponse, err := client.Do(initialReq)
   612  	// If there was an error and its a kind of error we consider a fallback error,
   613  	// then try to fallback.
   614  	if err != nil && fallbackErr(err) {
   615  		// Try to advance to another IP. If there was an error advancing we don't
   616  		// have a fallback address to use and must return the original error.
   617  		advanceTargetIPErr := target.nextIP()
   618  		if advanceTargetIPErr != nil {
   619  			return nil, records, newIPError(records[len(records)-1].AddressUsed, err)
   620  		}
   621  
   622  		// setup another validation to retry the target with the new IP and append
   623  		// the retry record.
   624  		retryDialer, retryRecord, err := va.setupHTTPValidation(initialReq.URL.String(), target)
   625  		if err != nil {
   626  			return nil, records, newIPError(records[len(records)-1].AddressUsed, err)
   627  		}
   628  
   629  		records = append(records, retryRecord)
   630  		va.metrics.http01Fallbacks.Inc()
   631  		// Replace the transport's dialer with the preresolvedDialer for the retry
   632  		// host.
   633  		transport.DialContext = retryDialer.DialContext
   634  
   635  		// Perform the retry
   636  		httpResponse, err = client.Do(initialReq)
   637  		// If the retry still failed there isn't anything more to do, return the
   638  		// error immediately.
   639  		if err != nil {
   640  			return nil, records, newIPError(records[len(records)-1].AddressUsed, err)
   641  		}
   642  	} else if err != nil {
   643  		// if the error was not a fallbackErr then return immediately.
   644  		return nil, records, newIPError(records[len(records)-1].AddressUsed, err)
   645  	}
   646  
   647  	if httpResponse.StatusCode != 200 {
   648  		return nil, records, newIPError(records[len(records)-1].AddressUsed, berrors.UnauthorizedError("Invalid response from %s: %d",
   649  			records[len(records)-1].URL, httpResponse.StatusCode))
   650  	}
   651  
   652  	// At this point we've made a successful request (be it from a retry or
   653  	// otherwise) and can read and process the response body.
   654  	body, err := io.ReadAll(&io.LimitedReader{R: httpResponse.Body, N: maxResponseSize})
   655  	closeErr := httpResponse.Body.Close()
   656  	if err == nil {
   657  		err = closeErr
   658  	}
   659  	if err != nil {
   660  		return nil, records, newIPError(records[len(records)-1].AddressUsed, berrors.UnauthorizedError("Error reading HTTP response body: %v", err))
   661  	}
   662  
   663  	// io.LimitedReader will silently truncate a Reader so if the
   664  	// resulting payload is the same size as maxResponseSize fail
   665  	if len(body) >= maxResponseSize {
   666  		return nil, records, newIPError(records[len(records)-1].AddressUsed, berrors.UnauthorizedError("Invalid response from %s: %q",
   667  			records[len(records)-1].URL, body))
   668  	}
   669  
   670  	return body, records, nil
   671  }
   672  
   673  func (va *ValidationAuthorityImpl) validateHTTP01(ctx context.Context, ident identifier.ACMEIdentifier, token string, keyAuthorization string) ([]core.ValidationRecord, error) {
   674  	if ident.Type != identifier.TypeDNS && ident.Type != identifier.TypeIP {
   675  		va.log.Info(fmt.Sprintf("Identifier type for HTTP-01 challenge was not DNS or IP: %s", ident))
   676  		return nil, berrors.MalformedError("Identifier type for HTTP-01 challenge was not DNS or IP")
   677  	}
   678  
   679  	// Perform the fetch
   680  	path := fmt.Sprintf(".well-known/acme-challenge/%s", token)
   681  	body, validationRecords, err := va.processHTTPValidation(ctx, ident, "/"+path)
   682  	if err != nil {
   683  		return validationRecords, err
   684  	}
   685  	payload := strings.TrimRightFunc(string(body), unicode.IsSpace)
   686  
   687  	if payload != keyAuthorization {
   688  		problem := berrors.UnauthorizedError("The key authorization file from the server did not match this challenge. Expected %q (got %q)",
   689  			keyAuthorization, payload)
   690  		va.log.Infof("%s for %s", problem, ident)
   691  		return validationRecords, problem
   692  	}
   693  
   694  	return validationRecords, nil
   695  }