VA: Fix SimplifiedHTTP01 w/ HTTPS redirects. (#3959)
The VA code activated by the `SimplifiedHTTP01` flag had a regression that constructed validation URLs for HTTP-01 challenge redirects to a HTTPS host incorrectly. The regression is fixed and the unit tests are updated to cover this case. An integration test is not included in this commit but will be done as follow-up work since it requires adjusting existing integration tests for HTTP-01 to use the challtestrv instead of Certbot's ACME standalone server.
This commit is contained in:
parent
142ff9c075
commit
8a610e5828
33
va/http.go
33
va/http.go
|
|
@ -75,14 +75,15 @@ func newHTTPClient(checkRedirect redirectChecker) http.Client {
|
|||
|
||||
// httpValidationURL constructs a URL for the given IP address, path and port
|
||||
// combination. The port is omitted from the URL if it is the default HTTP
|
||||
// port. It is only used for constructing initial HTTP-01 validation requests
|
||||
// and so does not need to support constructing a URL with an HTTPS scheme.
|
||||
func httpValidationURL(validationIP net.IP, path string, port int) *url.URL {
|
||||
// port or the default HTTPS port. The protocol scheme of the URL is HTTP unless
|
||||
// useHTTPS is true. UseHTTPS should only be true when constructing validation
|
||||
// URLs based on a redirect from an initial HTTP validation request.
|
||||
func httpValidationURL(validationIP net.IP, path string, port int, useHTTPS bool) *url.URL {
|
||||
urlHost := validationIP.String()
|
||||
|
||||
// If the port is something other than the conventional HTTP port, put it in
|
||||
// the URL.
|
||||
if port != 80 {
|
||||
// If the port is something other than the conventional HTTP or HTTPS port,
|
||||
// put it in the URL explicitly using `net.JoinHostPort`.
|
||||
if port != 80 && port != 443 {
|
||||
urlHost = net.JoinHostPort(validationIP.String(), strconv.Itoa(port))
|
||||
}
|
||||
|
||||
|
|
@ -90,12 +91,17 @@ func httpValidationURL(validationIP net.IP, path string, port int) *url.URL {
|
|||
// `net.JoinHostPort` then we have to manually surround the IPv6 address
|
||||
// with square brackets to make a valid IPv6 URL (e.g "http://[::1]/foo" not
|
||||
// "http://::1/foo")
|
||||
if port == 80 && validationIP.To4() == nil {
|
||||
if (port == 80 || port == 443) && validationIP.To4() == nil {
|
||||
urlHost = fmt.Sprintf("[%s]", urlHost)
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if useHTTPS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Scheme: scheme,
|
||||
Host: urlHost,
|
||||
Path: path,
|
||||
}
|
||||
|
|
@ -287,8 +293,17 @@ func (va *ValidationAuthorityImpl) setupHTTPValidation(
|
|||
"host %q has no IP addresses remaining to use",
|
||||
target.host)
|
||||
}
|
||||
|
||||
var useHTTPS bool
|
||||
// If we are mutating an existing redirected request and the original request
|
||||
// URL uses HTTPS then we must construct a validation URL using HTTPS. In all
|
||||
// other cases we construct an HTTP URL.
|
||||
if req != nil && req.URL.Scheme == "https" {
|
||||
useHTTPS = true
|
||||
}
|
||||
|
||||
record.AddressUsed = targetIP
|
||||
url := httpValidationURL(targetIP, target.path, target.port)
|
||||
url := httpValidationURL(targetIP, target.path, target.port, useHTTPS)
|
||||
record.URL = url.String()
|
||||
|
||||
// If there's no provided HTTP request to mutate (e.g. a redirect request
|
||||
|
|
|
|||
|
|
@ -52,36 +52,69 @@ func TestHTTPValidationURL(t *testing.T) {
|
|||
IP string
|
||||
Path string
|
||||
Port int
|
||||
UseHTTPS bool
|
||||
ExpectedURL string
|
||||
}{
|
||||
{
|
||||
Name: "IPv4 Standard port",
|
||||
Name: "IPv4 Standard HTTP port",
|
||||
IP: "10.10.10.10",
|
||||
Path: egPath,
|
||||
Port: 80,
|
||||
ExpectedURL: fmt.Sprintf("http://10.10.10.10%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv4 Non-standard port",
|
||||
Name: "IPv4 Non-standard HTTP port",
|
||||
IP: "15.15.15.15",
|
||||
Path: egPath,
|
||||
Port: 8080,
|
||||
ExpectedURL: fmt.Sprintf("http://15.15.15.15:8080%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv6 Standard port",
|
||||
Name: "IPv6 Standard HTTP port",
|
||||
IP: "::1",
|
||||
Path: egPath,
|
||||
Port: 80,
|
||||
ExpectedURL: fmt.Sprintf("http://[::1]%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv6 Non-standard port",
|
||||
Name: "IPv6 Non-standard HTTP port",
|
||||
IP: "::1",
|
||||
Path: egPath,
|
||||
Port: 8080,
|
||||
ExpectedURL: fmt.Sprintf("http://[::1]:8080%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv4 Standard HTTPS port",
|
||||
IP: "10.10.10.10",
|
||||
Path: egPath,
|
||||
Port: 443,
|
||||
UseHTTPS: true,
|
||||
ExpectedURL: fmt.Sprintf("https://10.10.10.10%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv4 Non-standard HTTPS port",
|
||||
IP: "15.15.15.15",
|
||||
Path: egPath,
|
||||
Port: 4443,
|
||||
UseHTTPS: true,
|
||||
ExpectedURL: fmt.Sprintf("https://15.15.15.15:4443%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv6 Standard HTTPS port",
|
||||
IP: "::1",
|
||||
Path: egPath,
|
||||
Port: 443,
|
||||
UseHTTPS: true,
|
||||
ExpectedURL: fmt.Sprintf("https://[::1]%s", egPath),
|
||||
},
|
||||
{
|
||||
Name: "IPv6 Non-standard HTTPS port",
|
||||
IP: "::1",
|
||||
Path: egPath,
|
||||
Port: 4443,
|
||||
UseHTTPS: true,
|
||||
ExpectedURL: fmt.Sprintf("https://[::1]:4443%s", egPath),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
|
@ -90,7 +123,7 @@ func TestHTTPValidationURL(t *testing.T) {
|
|||
if ipAddr == nil {
|
||||
t.Fatalf("Failed to parse test case %q IP %q", tc.Name, tc.IP)
|
||||
}
|
||||
url := httpValidationURL(ipAddr, tc.Path, tc.Port)
|
||||
url := httpValidationURL(ipAddr, tc.Path, tc.Port, tc.UseHTTPS)
|
||||
test.AssertEquals(t, url.String(), tc.ExpectedURL)
|
||||
})
|
||||
}
|
||||
|
|
@ -281,9 +314,13 @@ func TestSetupHTTPValidation(t *testing.T) {
|
|||
return target
|
||||
}
|
||||
|
||||
inputURL, err := url.Parse("http://ipv4.and.ipv6.localhost/yellow/brick/road")
|
||||
httpInputURL, err := url.Parse("http://ipv4.and.ipv6.localhost/yellow/brick/road")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct test inputURL")
|
||||
t.Fatalf("Failed to construct test httpInputURL")
|
||||
}
|
||||
httpsInputURL, err := url.Parse("https://ipv4.and.ipv6.localhost/yellow/brick/road")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct test httpsInputURL")
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
|
|
@ -328,10 +365,10 @@ func TestSetupHTTPValidation(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "non-nil input req",
|
||||
Name: "non-nil non-standard port input req",
|
||||
InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", 808, "/yellow/brick/road"),
|
||||
InputReq: &http.Request{
|
||||
URL: inputURL,
|
||||
URL: httpInputURL,
|
||||
},
|
||||
ExpectedRequestHost: "ipv4.and.ipv6.localhost",
|
||||
ExpectedRequestURL: "http://[::1]:808/yellow/brick/road",
|
||||
|
|
@ -343,6 +380,38 @@ func TestSetupHTTPValidation(t *testing.T) {
|
|||
AddressUsed: net.ParseIP("::1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "non-nil HTTP input req",
|
||||
InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpPort, "/yellow/brick/road"),
|
||||
InputReq: &http.Request{
|
||||
URL: httpInputURL,
|
||||
},
|
||||
ExpectedRequestHost: "ipv4.and.ipv6.localhost",
|
||||
ExpectedRequestURL: "http://[::1]/yellow/brick/road",
|
||||
ExpectedRecord: core.ValidationRecord{
|
||||
Hostname: "ipv4.and.ipv6.localhost",
|
||||
Port: strconv.Itoa(va.httpPort),
|
||||
URL: "http://[::1]/yellow/brick/road",
|
||||
AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
|
||||
AddressUsed: net.ParseIP("::1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "non-nil HTTPS input req",
|
||||
InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpsPort, "/yellow/brick/road"),
|
||||
InputReq: &http.Request{
|
||||
URL: httpsInputURL,
|
||||
},
|
||||
ExpectedRequestHost: "ipv4.and.ipv6.localhost",
|
||||
ExpectedRequestURL: "https://[::1]/yellow/brick/road",
|
||||
ExpectedRecord: core.ValidationRecord{
|
||||
Hostname: "ipv4.and.ipv6.localhost",
|
||||
Port: strconv.Itoa(va.httpsPort),
|
||||
URL: "https://[::1]/yellow/brick/road",
|
||||
AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
|
||||
AddressUsed: net.ParseIP("::1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
|
|
|||
Loading…
Reference in New Issue