va: add specific error for broken HTTP-01 redirects. (#4171)

Often folks will mis-configure their webserver to send an HTTP redirect missing
a `/' between the FQDN and the path.

E.g. in Apache using:

Redirect / https://bad-redirect.org

Instead of:

Redirect / https://bad-redirect.org/

Will produce an invalid HTTP-01 redirect target like:

https://bad-redirect.org.well-known/acme-challenge/xxxx

This happens frequently enough we want to return a distinct error message
for this case by detecting the redirect targets ending in ".well-known".

After the "Simple HTTP-01" code landed this case was previously getting an error
message of the form:
> "Invalid hostname in redirect target, must end in IANA registered TLD"

Resolves https://github.com/letsencrypt/boulder/issues/3606
This commit is contained in:
Daniel McCarney 2019-04-23 13:50:47 -04:00 committed by Jacob Hoffman-Andrews
parent 7b49849f87
commit 2f3c703a72
3 changed files with 60 additions and 0 deletions

View File

@ -63,6 +63,43 @@ def rand_http_chall(client):
return d, c.chall return d, c.chall
raise Exception("No HTTP-01 challenge found for random domain authz") raise Exception("No HTTP-01 challenge found for random domain authz")
def test_http_challenge_broken_redirect():
"""
test_http_challenge_broken_redirect tests that a common webserver
mis-configuration receives the correct specialized error message when attempting
an HTTP-01 challenge.
"""
client = chisel2.make_client()
# Create an authz for a random domain and get its HTTP-01 challenge token
d, chall = rand_http_chall(client)
token = chall.encode("token")
# Create a broken HTTP redirect similar to a sort we see frequently "in the wild"
challengePath = "/.well-known/acme-challenge/{0}".format(token)
redirect = "http://{0}.well-known/acme-challenge/bad-bad-bad".format(d)
challSrv.add_http_redirect(
challengePath,
redirect)
# Expect the specialized error message
expectedError = "Fetching {0}: Invalid host in redirect target \"{1}.well-known\". Check webserver config for missing '/' in redirect target.".format(redirect, d)
# NOTE(@cpu): Can't use chisel2.expect_problem here because it doesn't let
# us interrogate the detail message easily.
try:
chisel2.auth_and_issue([d], client=client, chall_type="http-01")
except acme_errors.ValidationError as e:
for authzr in e.failed_authzrs:
c = chisel2.get_chall(authzr, challenges.HTTP01)
error = c.error
if error is None or error.typ != "urn:ietf:params:acme:error:connection":
raise Exception("Expected connection prob, got %s" % (error.__str__()))
if error.detail != expectedError:
raise Exception("Expected prob detail %s, got %s" % (expectedError, error.detail))
challSrv.remove_http_redirect(challengePath)
def test_http_challenge_loop_redirect(): def test_http_challenge_loop_redirect():
client = chisel2.make_client() client = chisel2.make_client()

View File

@ -314,6 +314,22 @@ func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (stri
"Only domain names are supported, not IP addresses", reqHost) "Only domain names are supported, not IP addresses", reqHost)
} }
// Often folks will misconfigure their webserver to send an HTTP redirect
// missing a `/' between the FQDN and the path. E.g. in Apache using:
// Redirect / https://bad-redirect.org
// Instead of
// Redirect / https://bad-redirect.org/
// Will produce an invalid HTTP-01 redirect target like:
// https://bad-redirect.org.well-known/acme-challenge/xxxx
// This happens frequently enough we want to return a distinct error message
// for this case by detecting the reqHost ending in ".well-known".
if strings.HasSuffix(reqHost, ".well-known") {
return "", 0, berrors.ConnectionFailureError(
"Invalid host in redirect target %q. Check webserver config for missing '/' in redirect target.",
reqHost,
)
}
if _, err := iana.ExtractSuffix(reqHost); err != nil { if _, err := iana.ExtractSuffix(reqHost); err != nil {
return "", 0, berrors.ConnectionFailureError( return "", 0, berrors.ConnectionFailureError(
"Invalid hostname in redirect target, must end in IANA registered TLD") "Invalid hostname in redirect target, must end in IANA registered TLD")

View File

@ -232,6 +232,13 @@ func TestExtractRequestTarget(t *testing.T) {
}, },
ExpectedError: errors.New("Invalid empty hostname in redirect target"), ExpectedError: errors.New("Invalid empty hostname in redirect target"),
}, },
{
Name: "invalid .well-known hostname",
Req: &http.Request{
URL: mustURL(t, "https://my.webserver.is.misconfigured.well-known/acme-challenge/xxx"),
},
ExpectedError: errors.New(`Invalid host in redirect target "my.webserver.is.misconfigured.well-known". Check webserver config for missing '/' in redirect target.`),
},
{ {
Name: "invalid non-iana hostname", Name: "invalid non-iana hostname",
Req: &http.Request{ Req: &http.Request{