diff --git a/test/boulder-config-next.json b/test/boulder-config-next.json index d23f2b817..2af757992 100644 --- a/test/boulder-config-next.json +++ b/test/boulder-config-next.json @@ -349,7 +349,6 @@ }, "common": { - "baseURL": "http://127.0.0.1:4000", "issuerCert": "test/test-ca.pem", "dnsResolver": "127.0.0.1:8053", "dnsTimeout": "10s", diff --git a/wfe/web-front-end.go b/wfe/web-front-end.go index 0b2c9fbf5..5f8e318f7 100644 --- a/wfe/web-front-end.go +++ b/wfe/web-front-end.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "net" "net/http" + "net/url" "regexp" "strconv" "strings" @@ -30,18 +31,18 @@ import ( // Paths are the ACME-spec identified URL path-segments for various methods const ( - DirectoryPath = "/directory" - NewRegPath = "/acme/new-reg" - RegPath = "/acme/reg/" - NewAuthzPath = "/acme/new-authz" - AuthzPath = "/acme/authz/" - ChallengePath = "/acme/challenge/" - NewCertPath = "/acme/new-cert" - CertPath = "/acme/cert/" - RevokeCertPath = "/acme/revoke-cert" - TermsPath = "/terms" - IssuerPath = "/acme/issuer-cert" - BuildIDPath = "/build" + directoryPath = "/directory" + newRegPath = "/acme/new-reg" + regPath = "/acme/reg/" + newAuthzPath = "/acme/new-authz" + authzPath = "/acme/authz/" + challengePath = "/acme/challenge/" + newCertPath = "/acme/new-cert" + certPath = "/acme/cert/" + revokeCertPath = "/acme/revoke-cert" + termsPath = "/terms" + issuerPath = "/acme/issuer-cert" + buildIDPath = "/build" ) // WebFrontEndImpl provides all the logic for Boulder's web-facing interface, @@ -56,17 +57,7 @@ type WebFrontEndImpl struct { clk clock.Clock // URL configuration parameters - BaseURL string - NewReg string - RegBase string - NewAuthz string - AuthzBase string - ChallengeBase string - NewCert string - CertBase string - - // JSON encoded endpoint directory - DirectoryJSON []byte + BaseURL string // Issuer certificate (DER) for /acme/issuer-cert IssuerCert []byte @@ -192,43 +183,76 @@ func marshalIndent(v interface{}) ([]byte, error) { return json.MarshalIndent(v, "", " ") } -// Handler returns an http.Handler that uses various functions for -// various ACME-specified paths. -func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) { - wfe.NewReg = wfe.BaseURL + NewRegPath - wfe.RegBase = wfe.BaseURL + RegPath - wfe.NewAuthz = wfe.BaseURL + NewAuthzPath - wfe.AuthzBase = wfe.BaseURL + AuthzPath - wfe.ChallengeBase = wfe.BaseURL + ChallengePath - wfe.NewCert = wfe.BaseURL + NewCertPath - wfe.CertBase = wfe.BaseURL + CertPath +func (wfe *WebFrontEndImpl) relativeEndpoint(request *http.Request, endpoint string) string { + var result string + proto := "http" + host := request.Host - // Only generate directory once - directory := map[string]string{ - "new-reg": wfe.NewReg, - "new-authz": wfe.NewAuthz, - "new-cert": wfe.NewCert, - "revoke-cert": wfe.BaseURL + RevokeCertPath, + // If the request was received via TLS, use `https://` for the protocol + if request.TLS != nil { + proto = "https" } - directoryJSON, err := marshalIndent(directory) + + // Allow upstream proxies to specify the forwarded protocol. Allow this value + // to override our own guess. + if specifiedProto := request.Header.Get("X-Forwarded-Proto"); specifiedProto != "" { + proto = specifiedProto + } + + // Default to "localhost" when no request.Host is provided. Otherwise requests + // with an empty `Host` produce results like `http:///acme/new-authz` + if request.Host == "" { + host = "localhost" + } + + if wfe.BaseURL != "" { + result = fmt.Sprintf("%s%s", wfe.BaseURL, endpoint) + } else { + resultUrl := url.URL{Scheme: proto, Host: host, Path: endpoint} + result = resultUrl.String() + } + + return result +} + +func (wfe *WebFrontEndImpl) relativeDirectory(request *http.Request, directory map[string]string) ([]byte, error) { + // Create an empty map sized equal to the provided directory to store the + // relative-ized result + relativeDir := make(map[string]string, len(directory)) + + // Copy each entry of the provided directory into the new relative map. If + // `wfe.BaseURL` != "", use the old behaviour and prefix each endpoint with + // the `BaseURL`. Otherwise, prefix each endpoint using the request protocol + // & host. + for k, v := range directory { + relativeDir[k] = wfe.relativeEndpoint(request, v) + } + + directoryJSON, err := marshalIndent(relativeDir) + // This should never happen since we are just marshalling known strings if err != nil { return nil, err } - wfe.DirectoryJSON = directoryJSON + return directoryJSON, nil +} + +// Handler returns an http.Handler that uses various functions for +// various ACME-specified paths. +func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) { m := http.NewServeMux() - wfe.HandleFunc(m, DirectoryPath, wfe.Directory, "GET") - wfe.HandleFunc(m, NewRegPath, wfe.NewRegistration, "POST") - wfe.HandleFunc(m, NewAuthzPath, wfe.NewAuthorization, "POST") - wfe.HandleFunc(m, NewCertPath, wfe.NewCertificate, "POST") - wfe.HandleFunc(m, RegPath, wfe.Registration, "POST") - wfe.HandleFunc(m, AuthzPath, wfe.Authorization, "GET") - wfe.HandleFunc(m, ChallengePath, wfe.Challenge, "GET", "POST") - wfe.HandleFunc(m, CertPath, wfe.Certificate, "GET") - wfe.HandleFunc(m, RevokeCertPath, wfe.RevokeCertificate, "POST") - wfe.HandleFunc(m, TermsPath, wfe.Terms, "GET") - wfe.HandleFunc(m, IssuerPath, wfe.Issuer, "GET") - wfe.HandleFunc(m, BuildIDPath, wfe.BuildID, "GET") + wfe.HandleFunc(m, directoryPath, wfe.Directory, "GET") + wfe.HandleFunc(m, newRegPath, wfe.NewRegistration, "POST") + wfe.HandleFunc(m, newAuthzPath, wfe.NewAuthorization, "POST") + wfe.HandleFunc(m, newCertPath, wfe.NewCertificate, "POST") + wfe.HandleFunc(m, regPath, wfe.Registration, "POST") + wfe.HandleFunc(m, authzPath, wfe.Authorization, "GET") + wfe.HandleFunc(m, challengePath, wfe.Challenge, "GET", "POST") + wfe.HandleFunc(m, certPath, wfe.Certificate, "GET") + wfe.HandleFunc(m, revokeCertPath, wfe.RevokeCertificate, "POST") + wfe.HandleFunc(m, termsPath, wfe.Terms, "GET") + wfe.HandleFunc(m, issuerPath, wfe.Issuer, "GET") + wfe.HandleFunc(m, buildIDPath, wfe.BuildID, "GET") // We don't use our special HandleFunc for "/" because it matches everything, // meaning we can wind up returning 405 when we mean to return 404. See // https://github.com/letsencrypt/boulder/issues/717 @@ -269,7 +293,7 @@ func (wfe *WebFrontEndImpl) Index(ctx context.Context, logEvent *requestEvent, r JSON directory is available at %s. - `, DirectoryPath, DirectoryPath))) + `, directoryPath, directoryPath))) addCacheHeader(response, wfe.IndexCacheDuration.Seconds()) } @@ -281,11 +305,27 @@ func addCacheHeader(w http.ResponseWriter, age float64) { w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%.f", age)) } -// Directory is an HTTP request handler that simply provides the directory -// object stored in the WFE's DirectoryJSON member. +// Directory is an HTTP request handler that provides the directory +// object stored in the WFE's DirectoryEndpoints member with paths prefixed +// using the `request.Host` of the HTTP request. func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { + directoryEndpoints := map[string]string{ + "new-reg": newRegPath, + "new-authz": newAuthzPath, + "new-cert": newCertPath, + "revoke-cert": revokeCertPath, + } + response.Header().Set("Content-Type", "application/json") - response.Write(wfe.DirectoryJSON) + + relDir, err := wfe.relativeDirectory(request, directoryEndpoints) + if err != nil { + marshalProb := probs.ServerInternal("unable to marshal JSON directory") + wfe.sendError(response, logEvent, marshalProb, nil) + return + } + + response.Write(relDir) } // The ID is always the last slash-separated token in the path @@ -509,7 +549,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *reque } if existingReg, err := wfe.SA.GetRegistrationByKey(ctx, *key); err == nil { - response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID)) + response.Header().Set("Location", wfe.relativeEndpoint(request, fmt.Sprintf("%s%d", regPath, existingReg.ID))) // TODO(#595): check for missing registration err wfe.sendError(response, logEvent, probs.Conflict("Registration key is already in use"), err) return @@ -550,7 +590,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *reque // Use an explicitly typed variable. Otherwise `go vet' incorrectly complains // that reg.ID is a string being passed to %d. - regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID) + regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%d", regPath, reg.ID)) responseBody, err := marshalIndent(reg) if err != nil { // ServerInternal because we just created this registration, and it @@ -562,7 +602,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *reque response.Header().Add("Location", regURL) response.Header().Set("Content-Type", "application/json") - response.Header().Add("Link", link(wfe.NewAuthz, "next")) + response.Header().Add("Link", link(wfe.relativeEndpoint(request, newAuthzPath), "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } @@ -605,8 +645,8 @@ func (wfe *WebFrontEndImpl) NewAuthorization(ctx context.Context, logEvent *requ logEvent.Extra["AuthzID"] = authz.ID // Make a URL for this authz, then blow away the ID and RegID before serializing - authzURL := wfe.AuthzBase + string(authz.ID) - wfe.prepAuthorizationForDisplay(&authz) + authzURL := wfe.relativeEndpoint(request, authzPath+string(authz.ID)) + wfe.prepAuthorizationForDisplay(request, &authz) responseBody, err := marshalIndent(authz) if err != nil { // ServerInternal because we generated the authz, it should be OK @@ -615,7 +655,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(ctx context.Context, logEvent *requ } response.Header().Add("Location", authzURL) - response.Header().Add("Link", link(wfe.NewCert, "next")) + response.Header().Add("Link", link(wfe.relativeEndpoint(request, newCertPath), "next")) response.Header().Set("Content-Type", "application/json") response.WriteHeader(http.StatusCreated) if _, err = response.Write(responseBody); err != nil { @@ -782,11 +822,13 @@ func (wfe *WebFrontEndImpl) NewCertificate(ctx context.Context, logEvent *reques return } serial := parsedCertificate.SerialNumber - certURL := wfe.CertBase + core.SerialToString(serial) + certURL := wfe.relativeEndpoint(request, certPath+core.SerialToString(serial)) + + relativeIssuerPath := wfe.relativeEndpoint(request, issuerPath) // TODO Content negotiation response.Header().Add("Location", certURL) - response.Header().Add("Link", link(wfe.BaseURL+IssuerPath, "up")) + response.Header().Add("Link", link(relativeIssuerPath, "up")) response.Header().Set("Content-Type", "application/pkix-cert") response.WriteHeader(http.StatusCreated) if _, err = response.Write(cert.DER); err != nil { @@ -810,7 +852,7 @@ func (wfe *WebFrontEndImpl) Challenge( // Challenge URIs are of the form /acme/challenge//. // Here we parse out the id components. TODO: Use a better tool to parse out // URL structure: https://github.com/letsencrypt/boulder/issues/437 - slug := strings.Split(request.URL.Path[len(ChallengePath):], "/") + slug := strings.Split(request.URL.Path[len(challengePath):], "/") if len(slug) != 2 { notFound() return @@ -866,8 +908,8 @@ func (wfe *WebFrontEndImpl) Challenge( // fields. // TODO: Come up with a cleaner way to do this. // https://github.com/letsencrypt/boulder/issues/761 -func (wfe *WebFrontEndImpl) prepChallengeForDisplay(authz core.Authorization, challenge *core.Challenge) { - challenge.URI = fmt.Sprintf("%s%s/%d", wfe.ChallengeBase, authz.ID, challenge.ID) +func (wfe *WebFrontEndImpl) prepChallengeForDisplay(request *http.Request, authz core.Authorization, challenge *core.Challenge) { + challenge.URI = wfe.relativeEndpoint(request, fmt.Sprintf("%s%s/%d", challengePath, authz.ID, challenge.ID)) challenge.AccountKey = nil // 0 is considered "empty" for the purpose of the JSON omitempty tag. challenge.ID = 0 @@ -876,9 +918,9 @@ func (wfe *WebFrontEndImpl) prepChallengeForDisplay(authz core.Authorization, ch // prepAuthorizationForDisplay takes a core.Authorization and prepares it for // display to the client by clearing its ID and RegistrationID fields, and // preparing all its challenges. -func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(authz *core.Authorization) { +func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(request *http.Request, authz *core.Authorization) { for i := range authz.Challenges { - wfe.prepChallengeForDisplay(*authz, &authz.Challenges[i]) + wfe.prepChallengeForDisplay(request, *authz, &authz.Challenges[i]) } authz.ID = "" authz.RegistrationID = 0 @@ -892,7 +934,7 @@ func (wfe *WebFrontEndImpl) getChallenge( challenge *core.Challenge, logEvent *requestEvent) { - wfe.prepChallengeForDisplay(authz, challenge) + wfe.prepChallengeForDisplay(request, authz, challenge) jsonReply, err := marshalIndent(challenge) if err != nil { @@ -903,7 +945,7 @@ func (wfe *WebFrontEndImpl) getChallenge( return } - authzURL := wfe.AuthzBase + string(authz.ID) + authzURL := wfe.relativeEndpoint(request, authzPath+string(authz.ID)) response.Header().Add("Location", challenge.URI) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(authzURL, "up")) @@ -965,7 +1007,7 @@ func (wfe *WebFrontEndImpl) postChallenge( // assumption: UpdateAuthorization does not modify order of challenges challenge := updatedAuthorization.Challenges[challengeIndex] - wfe.prepChallengeForDisplay(authz, &challenge) + wfe.prepChallengeForDisplay(request, authz, &challenge) jsonReply, err := marshalIndent(challenge) if err != nil { // ServerInternal because we made the challenges, they should be OK @@ -974,7 +1016,7 @@ func (wfe *WebFrontEndImpl) postChallenge( return } - authzURL := wfe.AuthzBase + string(authz.ID) + authzURL := wfe.relativeEndpoint(request, authzPath+string(authz.ID)) response.Header().Add("Location", challenge.URI) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(authzURL, "up")) @@ -1052,7 +1094,7 @@ func (wfe *WebFrontEndImpl) Registration(ctx context.Context, logEvent *requestE return } response.Header().Set("Content-Type", "application/json") - response.Header().Add("Link", link(wfe.NewAuthz, "next")) + response.Header().Add("Link", link(wfe.relativeEndpoint(request, newAuthzPath), "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } @@ -1086,7 +1128,7 @@ func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *request return } - wfe.prepAuthorizationForDisplay(&authz) + wfe.prepAuthorizationForDisplay(request, &authz) jsonReply, err := marshalIndent(authz) if err != nil { @@ -1095,7 +1137,7 @@ func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *request wfe.sendError(response, logEvent, probs.ServerInternal("Failed to JSON marshal authz"), err) return } - response.Header().Add("Link", link(wfe.NewCert, "next")) + response.Header().Add("Link", link(wfe.relativeEndpoint(request, newCertPath), "next")) response.Header().Set("Content-Type", "application/json") response.WriteHeader(http.StatusOK) if _, err = response.Write(jsonReply); err != nil { @@ -1113,13 +1155,13 @@ func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *requestEv path := request.URL.Path // Certificate paths consist of the CertBase path, plus exactly sixteen hex // digits. - if !strings.HasPrefix(path, CertPath) { - logEvent.AddError("this request path should not have gotten to Certificate: %#v is not a prefix of %#v", path, CertPath) + if !strings.HasPrefix(path, certPath) { + logEvent.AddError("this request path should not have gotten to Certificate: %#v is not a prefix of %#v", path, certPath) wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil) addNoCacheHeader(response) return } - serial := path[len(CertPath):] + serial := path[len(certPath):] if !core.ValidSerial(serial) { logEvent.AddError("certificate serial provided was not valid: %s", serial) wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil) @@ -1145,7 +1187,7 @@ func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *requestEv // TODO Content negotiation response.Header().Set("Content-Type", "application/pkix-cert") - response.Header().Add("Link", link(IssuerPath, "up")) + response.Header().Add("Link", link(issuerPath, "up")) response.WriteHeader(http.StatusOK) if _, err = response.Write(cert.DER); err != nil { logEvent.AddError(err.Error()) diff --git a/wfe/web-front-end_test.go b/wfe/web-front-end_test.go index 1969f6b69..776112cd1 100644 --- a/wfe/web-front-end_test.go +++ b/wfe/web-front-end_test.go @@ -221,13 +221,6 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock) { wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy) test.AssertNotError(t, err, "Unable to create WFE") - wfe.NewReg = wfe.BaseURL + NewRegPath - wfe.RegBase = wfe.BaseURL + RegPath - wfe.NewAuthz = wfe.BaseURL + NewAuthzPath - wfe.AuthzBase = wfe.BaseURL + AuthzPath - wfe.ChallengeBase = wfe.BaseURL + ChallengePath - wfe.NewCert = wfe.BaseURL + NewCertPath - wfe.CertBase = wfe.BaseURL + CertPath wfe.SubscriberAgreementURL = agreementURL wfe.log = blog.NewMock() @@ -523,7 +516,7 @@ func TestIndex(t *testing.T) { }) test.AssertEquals(t, responseWriter.Code, http.StatusOK) test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n") - test.Assert(t, strings.Contains(responseWriter.Body.String(), DirectoryPath), + test.Assert(t, strings.Contains(responseWriter.Body.String(), directoryPath), "directory path not found") test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10") @@ -539,6 +532,10 @@ func TestIndex(t *testing.T) { } func TestDirectory(t *testing.T) { + // Note: using `wfe.BaseURL` to test the non-relative /directory behaviour + // This tests to ensure the `Host` in the following `http.Request` is not + // used.by setting `BaseURL` using `localhost`, sending `127.0.0.1` in the Host, + // and expecting `localhost` in the JSON result. wfe, _ := setupWFE(t) wfe.BaseURL = "http://localhost:4300" mux, err := wfe.Handler() @@ -550,12 +547,59 @@ func TestDirectory(t *testing.T) { mux.ServeHTTP(responseWriter, &http.Request{ Method: "GET", URL: url, + Host: "127.0.0.1:4300", }) test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json") test.AssertEquals(t, responseWriter.Code, http.StatusOK) assertJSONEquals(t, responseWriter.Body.String(), `{"new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert"}`) } +func TestRelativeDirectory(t *testing.T) { + wfe, _ := setupWFE(t) + mux, err := wfe.Handler() + test.AssertNotError(t, err, "Problem setting up HTTP handlers") + + dirTests := []struct { + host string + protoHeader string + result string + }{ + // Test '' (No host header) with no proto header + {"", "", `{"new-authz":"http://localhost/acme/new-authz","new-cert":"http://localhost/acme/new-cert","new-reg":"http://localhost/acme/new-reg","revoke-cert":"http://localhost/acme/revoke-cert"}`}, + // Test localhost:4300 with no proto header + {"localhost:4300", "", `{"new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert"}`}, + // Test 127.0.0.1:4300 with no proto header + {"127.0.0.1:4300", "", `{"new-authz":"http://127.0.0.1:4300/acme/new-authz","new-cert":"http://127.0.0.1:4300/acme/new-cert","new-reg":"http://127.0.0.1:4300/acme/new-reg","revoke-cert":"http://127.0.0.1:4300/acme/revoke-cert"}`}, + // Test localhost:4300 with HTTP proto header + {"localhost:4300", "http", `{"new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert"}`}, + // Test localhost:4300 with HTTPS proto header + {"localhost:4300", "https", `{"new-authz":"https://localhost:4300/acme/new-authz","new-cert":"https://localhost:4300/acme/new-cert","new-reg":"https://localhost:4300/acme/new-reg","revoke-cert":"https://localhost:4300/acme/revoke-cert"}`}, + } + + url, _ := url.Parse("/directory") + + for _, tt := range dirTests { + var headers map[string][]string + responseWriter := httptest.NewRecorder() + + if tt.protoHeader != "" { + headers = map[string][]string{ + "X-Forwarded-Proto": {tt.protoHeader}, + } + } + + mux.ServeHTTP(responseWriter, &http.Request{ + Method: "GET", + Host: tt.host, + URL: url, + Header: headers, + }) + test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json") + test.AssertEquals(t, responseWriter.Code, http.StatusOK) + assertJSONEquals(t, responseWriter.Body.String(), tt.result) + } +} + // TODO: Write additional test cases for: // - RA returns with a failure func TestIssueCertificate(t *testing.T) { @@ -590,7 +634,7 @@ func TestIssueCertificate(t *testing.T) { // GET instead of POST should be rejected mux.ServeHTTP(responseWriter, &http.Request{ Method: "GET", - URL: mustParseURL(NewCertPath), + URL: mustParseURL(newCertPath), }) assertJSONEquals(t, responseWriter.Body.String(), @@ -685,10 +729,10 @@ func TestIssueCertificate(t *testing.T) { string(cert.Raw)) test.AssertEquals( t, responseWriter.Header().Get("Location"), - "/acme/cert/0000ff0000000000000e4b4f67d86e818c46") + "http://localhost/acme/cert/0000ff0000000000000e4b4f67d86e818c46") test.AssertEquals( t, responseWriter.Header().Get("Link"), - `;rel="up"`) + `;rel="up"`) test.AssertEquals( t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert") @@ -702,7 +746,7 @@ func TestIssueCertificate(t *testing.T) { func TestGetChallenge(t *testing.T) { wfe, _ := setupWFE(t) - challengeURL := "/acme/challenge/valid/23" + challengeURL := "http://localhost/acme/challenge/valid/23" for _, method := range []string{"GET", "HEAD"} { resp := httptest.NewRecorder() @@ -722,14 +766,14 @@ func TestGetChallenge(t *testing.T) { "application/json") test.AssertEquals(t, resp.Header().Get("Link"), - `;rel="up"`) + `;rel="up"`) // Body is only relevant for GET. For HEAD, body will // be discarded by HandleFunc() anyway, so it doesn't // matter what Challenge() writes to it. if method == "GET" { assertJSONEquals( t, resp.Body.String(), - `{"type":"dns","uri":"/acme/challenge/valid/23"}`) + `{"type":"dns","uri":"http://localhost/acme/challenge/valid/23"}`) } } } @@ -748,7 +792,7 @@ func TestChallenge(t *testing.T) { `), &key) test.AssertNotError(t, err, "Could not unmarshal testing key") - challengeURL := "/acme/challenge/valid/23" + challengeURL := "http://localhost/acme/challenge/valid/23" wfe.Challenge(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath(challengeURL, signRequest(t, `{"resource":"challenge"}`, wfe.nonceService))) @@ -759,10 +803,10 @@ func TestChallenge(t *testing.T) { challengeURL) test.AssertEquals( t, responseWriter.Header().Get("Link"), - `;rel="up"`) + `;rel="up"`) assertJSONEquals( t, responseWriter.Body.String(), - `{"type":"dns","uri":"/acme/challenge/valid/23"}`) + `{"type":"dns","uri":"http://localhost/acme/challenge/valid/23"}`) // Expired challenges should be inaccessible challengeURL = "/acme/challenge/expired/23" @@ -818,7 +862,7 @@ func TestNewECDSARegistration(t *testing.T) { test.AssertEquals(t, reg.Agreement, "http://example.invalid/terms") test.AssertEquals(t, reg.InitialIP.String(), "1.1.1.1") - test.AssertEquals(t, responseWriter.Header().Get("Location"), "/acme/reg/0") + test.AssertEquals(t, responseWriter.Header().Get("Location"), "http://localhost/acme/reg/0") key, err = jose.LoadPrivateKey([]byte(testE1KeyPrivatePEM)) test.AssertNotError(t, err, "Failed to load key") @@ -835,7 +879,7 @@ func TestNewECDSARegistration(t *testing.T) { wfe.NewRegistration(ctx, newRequestEvent(), responseWriter, makePostRequest(result.FullSerialize())) assertJSONEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Registration key is already in use","status":409}`) - test.AssertEquals(t, responseWriter.Header().Get("Location"), "/acme/reg/3") + test.AssertEquals(t, responseWriter.Header().Get("Location"), "http://localhost/acme/reg/3") test.AssertEquals(t, responseWriter.Code, 409) } @@ -866,7 +910,7 @@ func TestNewRegistration(t *testing.T) { { &http.Request{ Method: "GET", - URL: mustParseURL(NewRegPath), + URL: mustParseURL(newRegPath), }, `{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`, }, @@ -875,7 +919,7 @@ func TestNewRegistration(t *testing.T) { { &http.Request{ Method: "POST", - URL: mustParseURL(NewRegPath), + URL: mustParseURL(newRegPath), Header: map[string][]string{ "Content-Length": {"0"}, }, @@ -885,20 +929,20 @@ func TestNewRegistration(t *testing.T) { // POST, but body that isn't valid JWS { - makePostRequestWithPath(NewRegPath, "hi"), + makePostRequestWithPath(newRegPath, "hi"), `{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`, }, // POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON. { - makePostRequestWithPath(NewRegPath, fooBody.FullSerialize()), + makePostRequestWithPath(newRegPath, fooBody.FullSerialize()), `{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`, }, // Same signed body, but payload modified by one byte, breaking signature. // should fail JWS verification. { - makePostRequestWithPath(NewRegPath, ` + makePostRequestWithPath(newRegPath, ` { "header": { "alg": "RS256", @@ -915,7 +959,7 @@ func TestNewRegistration(t *testing.T) { `{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}`, }, { - makePostRequestWithPath(NewRegPath, wrongAgreementBody.FullSerialize()), + makePostRequestWithPath(newRegPath, wrongAgreementBody.FullSerialize()), `{"type":"urn:acme:error:malformed","detail":"Provided agreement URL [https://letsencrypt.org/im-bad] does not match current agreement URL [` + agreementURL + `]","status":400}`, }, } @@ -940,14 +984,14 @@ func TestNewRegistration(t *testing.T) { test.AssertEquals( t, responseWriter.Header().Get("Location"), - "/acme/reg/0") + "http://localhost/acme/reg/0") links := responseWriter.Header()["Link"] - test.AssertEquals(t, contains(links, ";rel=\"next\""), true) + test.AssertEquals(t, contains(links, ";rel=\"next\""), true) test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true) test.AssertEquals( t, responseWriter.Header().Get("Link"), - `;rel="next"`) + `;rel="next"`) key, err = jose.LoadPrivateKey([]byte(test1KeyPrivatePEM)) test.AssertNotError(t, err, "Failed to load key") @@ -969,7 +1013,7 @@ func TestNewRegistration(t *testing.T) { `{"type":"urn:acme:error:malformed","detail":"Registration key is already in use","status":409}`) test.AssertEquals( t, responseWriter.Header().Get("Location"), - "/acme/reg/1") + "http://localhost/acme/reg/1") test.AssertEquals(t, responseWriter.Code, 409) } @@ -1131,7 +1175,7 @@ func TestAuthorization(t *testing.T) { // GET instead of POST should be rejected mux.ServeHTTP(responseWriter, &http.Request{ Method: "GET", - URL: mustParseURL(NewAuthzPath), + URL: mustParseURL(newAuthzPath), }) assertJSONEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`) @@ -1185,10 +1229,10 @@ func TestAuthorization(t *testing.T) { test.AssertEquals( t, responseWriter.Header().Get("Location"), - "/acme/authz/bkrPh2u0JUf18-rVBZtOOWWb3GuIiliypL-hBM9Ak1Q") + "http://localhost/acme/authz/bkrPh2u0JUf18-rVBZtOOWWb3GuIiliypL-hBM9Ak1Q") test.AssertEquals( t, responseWriter.Header().Get("Link"), - `;rel="next"`) + `;rel="next"`) assertJSONEquals(t, responseWriter.Body.String(), `{"identifier":{"type":"dns","value":"test.com"}}`) @@ -1226,7 +1270,7 @@ func TestRegistration(t *testing.T) { // Test invalid method mux.ServeHTTP(responseWriter, &http.Request{ Method: "MAKE-COFFEE", - URL: mustParseURL(RegPath), + URL: mustParseURL(regPath), Body: makeBody("invalid"), }) assertJSONEquals(t, @@ -1237,7 +1281,7 @@ func TestRegistration(t *testing.T) { // Test GET proper entry returns 405 mux.ServeHTTP(responseWriter, &http.Request{ Method: "GET", - URL: mustParseURL(RegPath), + URL: mustParseURL(regPath), }) assertJSONEquals(t, responseWriter.Body.String(), @@ -1295,7 +1339,7 @@ func TestRegistration(t *testing.T) { makePostRequestWithPath("/1", result.FullSerialize())) test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error") links := responseWriter.Header()["Link"] - test.AssertEquals(t, contains(links, ";rel=\"next\""), true) + test.AssertEquals(t, contains(links, ";rel=\"next\""), true) test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true) responseWriter.Body.Reset()