add context to WFE errors

This change makes the use of requestEvent more prominent and provides
some nice handler types to make that better.

In order to avoid stomping on Error, requestEvent gains an Errors string
slice.

Fixes #552
This commit is contained in:
Jeff Hodges 2015-10-15 17:53:50 -07:00
parent 98fea98f4f
commit 7d04ea98cf
3 changed files with 317 additions and 282 deletions

85
wfe/context.go Normal file
View File

@ -0,0 +1,85 @@
package wfe
import (
"fmt"
"net/http"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
)
type requestEvent struct {
ID string `json:",omitempty"`
RealIP string `json:",omitempty"`
ClientAddr string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Method string `json:",omitempty"`
RequestTime time.Time `json:",omitempty"`
ResponseTime time.Time `json:",omitempty"`
Errors []string
Requester int64 `json:",omitempty"`
Contacts []*core.AcmeURL `json:",omitempty"`
Extra map[string]interface{} `json:",omitempty"`
}
func (e *requestEvent) AddError(msg string, args ...interface{}) {
e.Errors = append(e.Errors, fmt.Sprintf(msg, args...))
}
type wfeHandlerFunc func(*requestEvent, http.ResponseWriter, *http.Request)
func (f wfeHandlerFunc) ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request) {
f(e, w, r)
}
type wfeHandler interface {
ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request)
}
type wfeTopHandler struct {
h wfeHandler
log *blog.AuditLogger
clk clock.Clock
}
func (t *wfeTopHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logEvent := &requestEvent{
ID: core.NewToken(),
RealIP: r.Header.Get("X-Real-IP"),
ClientAddr: getClientAddr(r),
Method: r.Method,
RequestTime: time.Now(),
Extra: make(map[string]interface{}, 0),
}
if r.URL != nil {
logEvent.Endpoint = r.URL.String()
}
defer t.logEvent(logEvent)
t.h.ServeHTTP(logEvent, w, r)
}
func (t *wfeTopHandler) logEvent(logEvent *requestEvent) {
logEvent.ResponseTime = t.clk.Now()
var msg string
if len(logEvent.Errors) != 0 {
msg = "Terminated request"
} else {
msg = "Successful request"
}
t.log.InfoObject(msg, logEvent)
}
// Comma-separated list of HTTP clients involved in making this
// request, starting with the original requestor and ending with the
// remote end of our TCP connection (which is typically our own
// proxy).
func getClientAddr(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + r.RemoteAddr
}
return r.RemoteAddr
}

View File

@ -19,6 +19,7 @@ import (
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
@ -115,21 +116,6 @@ func statusCodeFromError(err interface{}) int {
}
}
type requestEvent struct {
ID string `json:",omitempty"`
RealIP string `json:",omitempty"`
ClientAddr string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Method string `json:",omitempty"`
RequestTime time.Time `json:",omitempty"`
ResponseTime time.Time `json:",omitempty"`
Error string `json:",omitempty"`
Requester int64 `json:",omitempty"`
Contacts []*core.AcmeURL `json:",omitempty"`
Extra map[string]interface{} `json:",omitempty"`
}
// NewWebFrontEndImpl constructs a web service for Boulder
func NewWebFrontEndImpl(stats statsd.Statter) (WebFrontEndImpl, error) {
logger := blog.GetAuditLogger()
@ -173,7 +159,7 @@ func (mrw BodylessResponseWriter) Write(buf []byte) (int, error) {
// * Never send a body in response to a HEAD request. Anything
// written by the handler will be discarded if the method is HEAD.
// Also, all handlers that accept GET automatically accept HEAD.
func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h func(http.ResponseWriter, *http.Request), methods ...string) {
func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfeHandlerFunc, methods ...string) {
methodsMap := make(map[string]bool)
for _, m := range methods {
methodsMap[m] = true
@ -184,38 +170,41 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h fun
methodsMap["HEAD"] = true
}
methodsStr := strings.Join(methods, ", ")
mux.HandleFunc(pattern, func(response http.ResponseWriter, request *http.Request) {
// We do not propagate errors here, because (1) they should be
// transient, and (2) they fail closed.
nonce, err := wfe.nonceService.Nonce()
if err == nil {
response.Header().Set("Replay-Nonce", nonce)
}
mux.Handle(pattern, &wfeTopHandler{
log: wfe.log,
clk: clock.Default(),
h: wfeHandlerFunc(func(e *requestEvent, response http.ResponseWriter, request *http.Request) {
// We do not propagate errors here, because (1) they should be
// transient, and (2) they fail closed.
nonce, err := wfe.nonceService.Nonce()
if err == nil {
response.Header().Set("Replay-Nonce", nonce)
}
switch request.Method {
case "HEAD":
// Whether or not we're sending a 405 error,
// we should comply with HTTP spec by not
// sending a body.
response = BodylessResponseWriter{response}
case "OPTIONS":
wfe.Options(response, request, methodsStr, methodsMap)
return
}
switch request.Method {
case "HEAD":
// Whether or not we're sending a 405 error,
// we should comply with HTTP spec by not
// sending a body.
response = BodylessResponseWriter{response}
case "OPTIONS":
wfe.Options(response, request, methodsStr, methodsMap)
return
}
if !methodsMap[request.Method] {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
logEvent.Error = "Method not allowed"
response.Header().Set("Allow", methodsStr)
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
return
}
if !methodsMap[request.Method] {
msg := "Method not allowed"
e.AddError(msg)
response.Header().Set("Allow", methodsStr)
wfe.sendError(response, msg, request.Method, http.StatusMethodNotAllowed)
return
}
wfe.setCORSHeaders(response, request, "")
wfe.setCORSHeaders(response, request, "")
// Call the wrapped handler.
h(response, request)
// Call the wrapped handler.
h(e, response, request)
}),
})
}
@ -259,29 +248,30 @@ func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) {
// 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
m.HandleFunc("/", wfe.Index)
m.Handle("/", &wfeTopHandler{
log: wfe.log,
clk: clock.Default(),
h: wfeHandlerFunc(wfe.Index),
})
return m, nil
}
// Method implementations
// Index serves a simple identification page. It is not part of the ACME spec.
func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) Index(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
// http://golang.org/pkg/net/http/#example_ServeMux_Handle
// The "/" pattern matches everything, so we need to check
// that we're at the root here.
if request.URL.Path != "/" {
logEvent.Error = "Resource not found"
logEvent.AddError("Resource not found")
http.NotFound(response, request)
response.Header().Set("Content-Type", "application/problem+json")
return
}
if request.Method != "GET" {
logEvent.Error = "Bad method"
logEvent.AddError("Bad method")
response.Header().Set("Allow", "GET")
response.WriteHeader(http.StatusMethodNotAllowed)
return
@ -309,7 +299,7 @@ func addCacheHeader(w http.ResponseWriter, age float64) {
// Directory is an HTTP request handler that simply provides the directory
// object stored in the WFE's DirectoryJSON member.
func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Directory(e *requestEvent, response http.ResponseWriter, request *http.Request) {
response.Header().Set("Content-Type", "application/json")
response.Write(wfe.DirectoryJSON)
}
@ -326,39 +316,44 @@ const (
)
// verifyPOST reads and parses the request body, looks up the Registration
// corresponding to its JWK, verifies the JWS signature,
// checks that the resource field is present and correct in the JWS protected
// header, and returns the JWS payload bytes, the key used to verify, and the
// corresponding Registration (or error).
// If regCheck is false, verifyPOST will still try to look up a registration
// object, and will return it if found. However, if no registration object is
// found, verifyPOST will attempt to verify the JWS using the key in the JWS
// headers, and return the key plus a dummy registration if successful. If a
// caller passes regCheck = false, it should plan on validating the key itself.
func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) {
// corresponding to its JWK, verifies the JWS signature, checks that the
// resource field is present and correct in the JWS protected header, and
// returns the JWS payload bytes, the key used to verify, and the corresponding
// Registration (or error). If regCheck is false, verifyPOST will still try to
// look up a registration object, and will return it if found. However, if no
// registration object is found, verifyPOST will attempt to verify the JWS using
// the key in the JWS headers, and return the key plus a dummy registration if
// successful. If a caller passes regCheck = false, it should plan on validating
// the key itself. verifyPOST also appends its errors to requestEvent.Errors so
// code calling it does not need to if they imediately return a response to the
// user.
func (wfe *WebFrontEndImpl) verifyPOST(e *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) {
var err error
// TODO: We should return a pointer to a registration, which can be nil,
// rather the a registration value with a sentinel value.
// https://github.com/letsencrypt/boulder/issues/877
reg := core.Registration{ID: -1}
reg := core.Registration{ID: 0}
if _, ok := request.Header["Content-Length"]; !ok {
err = core.LengthRequiredError("Content-Length header is required for POST.")
wfe.log.Debug(err.Error())
wfe.stats.Inc("WFE.HTTP.ClientErrors.LengthRequiredError", 1, 1.0)
e.AddError("missing Content-Length header on POST")
return nil, nil, reg, err
}
// Read body
if request.Body == nil {
err = core.MalformedRequestError("No body on POST")
wfe.log.Debug(err.Error())
wfe.stats.Inc("WFE.Errors.NoPOSTBody", 1, 1.0)
e.AddError("no body on POST")
return nil, nil, reg, err
}
bodyBytes, err := ioutil.ReadAll(request.Body)
if err != nil {
err = core.InternalServerError(err.Error())
wfe.log.Debug(err.Error())
err = core.InternalServerError("unable to read request body")
wfe.stats.Inc("WFE.Errors.UnableToReadRequestBody", 1, 1.0)
e.AddError("unable to read request body")
return nil, nil, reg, err
}
@ -367,7 +362,8 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
parsedJws, err := jose.ParseSigned(body)
if err != nil {
puberr := core.SignatureValidationError("Parse error reading JWS")
wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error()))
wfe.stats.Inc("WFE.Errors.UnableToParseJWS", 1, 1.0)
e.AddError("could not JSON parse body into JWS: %s", err)
return nil, nil, reg, puberr
}
@ -378,19 +374,22 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
// *anyway*, so it could always lie about what key was used by faking
// the signature itself.
if len(parsedJws.Signatures) > 1 {
err = core.SignatureValidationError("Too many signatures on POST")
wfe.log.Debug(err.Error())
err = core.SignatureValidationError("Too many signatures in POST body")
wfe.stats.Inc("WFE.Errors.TooManyJWSSignaturesInPOST", 1, 1.0)
e.AddError("too many signatures in POST body: %d", len(parsedJws.Signatures))
return nil, nil, reg, err
}
if len(parsedJws.Signatures) == 0 {
err = core.SignatureValidationError("POST JWS not signed")
wfe.log.Debug(err.Error())
wfe.stats.Inc("WFE.Errors.JWSNotSignedInPOST", 1, 1.0)
e.AddError("no signatures in POST body")
return nil, nil, reg, err
}
submittedKey := parsedJws.Signatures[0].Header.JsonWebKey
if submittedKey == nil {
err = core.SignatureValidationError("No JWK in JWS header")
wfe.log.Debug(err.Error())
wfe.stats.Inc("WFE.Errors.NoJWKInJWSSignatureHeader", 1, 1.0)
e.AddError("no JWK in JWS signature header in POST body")
return nil, nil, reg, err
}
@ -404,34 +403,46 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
// are "good". But when we are verifying against any submitted key, we want
// to check its quality before doing the verify.
if err = core.GoodKey(submittedKey.Key); err != nil {
wfe.stats.Inc("WFE.Errors.JWKRejectedByGoodKey", 1, 1.0)
e.AddError("JWK in request was rejected by GoodKey: %s", err)
return nil, nil, reg, err
}
key = submittedKey
} else if err != nil {
// For all other errors, or if regCheck is true, return error immediately.
wfe.stats.Inc("WFE.Errors.UnableToGetRegistrationByKey", 1, 1.0)
e.AddError("unable to fetch registration by the given JWK: %s", err)
return nil, nil, reg, err
} else {
// If the lookup was successful, use that key.
key = &reg.Key
e.Requester = reg.ID
e.Contacts = reg.Contact
}
payload, header, err := parsedJws.Verify(key)
if err != nil {
puberr := core.SignatureValidationError("JWS verification error")
wfe.log.Debug(string(body))
wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error()))
wfe.stats.Inc("WFE.Errors.JWSVerificationFailed", 1, 1.0)
n := len(body)
if n > 100 {
n = 100
}
e.AddError("verification of JWS with the JWK failed: %v; body: %s", err, body[:n])
return nil, nil, reg, puberr
}
// Check that the request has a known anti-replay nonce
// i.e., Nonce is in protected header and
if err != nil || len(header.Nonce) == 0 {
wfe.stats.Inc("WFE.Errors.JWSMissingNonce", 1, 1.0)
e.AddError("JWS is missing an anti-replay nonce")
err = core.SignatureValidationError("JWS has no anti-replay nonce")
wfe.log.Debug(err.Error())
return nil, nil, reg, err
} else if !wfe.nonceService.Valid(header.Nonce) {
wfe.stats.Inc("WFE.Errors.JWSInvalidNonce", 1, 1.0)
e.AddError("JWS has an invalid anti-replay nonce")
err = core.SignatureValidationError(fmt.Sprintf("JWS has invalid anti-replay nonce"))
wfe.log.Debug(err.Error())
return nil, nil, reg, err
}
@ -441,17 +452,20 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
}
err = json.Unmarshal([]byte(payload), &parsedRequest)
if err != nil {
wfe.stats.Inc("WFE.Errors.UnparsableJWSPayload", 1, 1.0)
e.AddError("unable to JSON parse resource from JWS payload: %s", err)
puberr := core.SignatureValidationError("Request payload did not parse as JSON")
wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error()))
return nil, nil, reg, puberr
}
if parsedRequest.Resource == "" {
wfe.stats.Inc("WFE.Errors.NoResourceInJWSPayload", 1, 1.0)
e.AddError("JWS request payload does not specifiy a resource")
err = core.MalformedRequestError("Request payload does not specify a resource")
wfe.log.Debug(err.Error())
return nil, nil, reg, err
} else if resource != core.AcmeResource(parsedRequest.Resource) {
err = core.MalformedRequestError(fmt.Sprintf("Request payload has invalid resource: %s != %s", parsedRequest.Resource, resource))
wfe.log.Debug(err.Error())
wfe.stats.Inc("WFE.Errors.MismatchedResourceInJWSPayload", 1, 1.0)
e.AddError("JWS request payload does not match resource")
err = core.MalformedRequestError(fmt.Sprintf("JWS resource payload does not match the HTTP resource: %s != %s", parsedRequest.Resource, resource))
return nil, nil, reg, err
}
@ -517,34 +531,33 @@ func link(url, relation string) string {
}
// NewRegistration is used by clients to submit a new registration/account
func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
body, key, _, err := wfe.verifyPOST(request, false, core.ResourceNewReg)
body, key, _, err := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg)
if err != nil {
logEvent.Error = err.Error()
// verifyPOST handles its own setting of logEvent.Errors
wfe.sendError(response, malformedJWS, err, statusCodeFromError(err))
return
}
if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil {
logEvent.Error = "Registration key is already in use"
logEvent.AddError("Registration key is already in use")
response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID))
wfe.sendError(response, logEvent.Error, nil, http.StatusConflict)
wfe.sendError(response, "Registration key is already in use", nil, http.StatusConflict)
return
}
var init core.Registration
err = json.Unmarshal(body, &init)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to unmarshal Registration: %s", err)
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
return
}
if len(init.Agreement) > 0 && init.Agreement != wfe.SubscriberAgreementURL {
logEvent.Error = fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL)
wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest)
msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL)
logEvent.AddError(msg)
wfe.sendError(response, msg, nil, http.StatusBadRequest)
return
}
init.Key = *key
@ -554,15 +567,15 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
if err == nil {
init.InitialIP = net.ParseIP(host)
} else {
logEvent.Error = "Couldn't parse RemoteAddr"
wfe.sendError(response, logEvent.Error, nil, http.StatusInternalServerError)
logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr)
wfe.sendError(response, "couldn't parse the remote (that is, the client's) address", nil, http.StatusInternalServerError)
return
}
}
reg, err := wfe.RA.NewRegistration(init)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to create new registration: %s", err)
wfe.sendError(response, "Error creating new registration", err, statusCodeFromError(err))
return
}
@ -574,7 +587,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID)
responseBody, err := json.Marshal(reg)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to marsh registration: %s", err)
// StatusInternalServerError because we just created this registration, it should be OK.
wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError)
return
@ -592,13 +605,10 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
}
// NewAuthorization is used by clients to submit a new ID Authorization
func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceNewAuthz)
func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewAuthz)
if err != nil {
logEvent.Error = err.Error()
// verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS
respCode := statusCodeFromError(err)
if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -608,20 +618,18 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
wfe.sendError(response, respMsg, err, respCode)
return
}
logEvent.Requester = currReg.ID
logEvent.Contacts = currReg.Contact
// Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update.
if currReg.Agreement == "" {
logEvent.Error = "Must agree to subscriber agreement before any further actions"
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden)
logEvent.AddError("Must agree to subscriber agreement before any further actions")
wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
return
}
var init core.Authorization
if err = json.Unmarshal(body, &init); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to JSON unmarshal Authorization: %s", err)
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
return
}
@ -630,7 +638,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
// Create new authz and return
authz, err := wfe.RA.NewAuthorization(init, currReg.ID)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to create new authz: %s", err)
wfe.sendError(response, "Error creating new authz", err, statusCodeFromError(err))
return
}
@ -641,7 +649,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
wfe.prepAuthorizationForDisplay(&authz)
responseBody, err := json.Marshal(authz)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to marshal authz: %s", err)
// StatusInternalServerError because we generated the authz, it should be OK
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
return
@ -652,40 +660,36 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusCreated)
if _, err = response.Write(responseBody); err != nil {
logEvent.Error = err.Error()
logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
// RevokeCertificate is used by clients to request the revocation of a cert.
func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
// We don't ask verifyPOST to verify there is a correponding registration,
// because anyone with the right private key can revoke a certificate.
body, requestKey, registration, err := wfe.verifyPOST(request, false, core.ResourceRevokeCert)
body, requestKey, registration, err := wfe.verifyPOST(logEvent, request, false, core.ResourceRevokeCert)
if err != nil {
logEvent.Error = err.Error()
// verifyPOST handles its own setting of logEvent.Errors
wfe.sendError(response, malformedJWS, err, statusCodeFromError(err))
return
}
logEvent.Requester = registration.ID
logEvent.Contacts = registration.Contact
type RevokeRequest struct {
CertificateDER core.JSONBuffer `json:"certificate"`
}
var revokeRequest RevokeRequest
if err = json.Unmarshal(body, &revokeRequest); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to JSON unmarshal RevokeRequest: %s", err)
wfe.log.Debug(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body)))
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
}
providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to parse revoke certificate DER: %s", err)
wfe.log.Debug("Couldn't parse cert in revoke request.")
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
@ -700,7 +704,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
}
parsedCertificate, err := x509.ParseCertificate(cert.DER)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to parse certificate DER: %s", err)
// InternalServerError because this is a failure to decode from our DB.
wfe.sendError(response, "Invalid certificate", err, http.StatusInternalServerError)
return
@ -712,25 +716,24 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
certStatus, err := wfe.SA.GetCertificateStatus(serial)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to get certificate status: %s", err)
wfe.sendError(response, "Certificate status not yet available", err, http.StatusNotFound)
return
}
logEvent.Extra["CertificateStatus"] = certStatus.Status
if certStatus.Status == core.OCSPStatusRevoked {
logEvent.Error = "Certificate already revoked"
wfe.sendError(response, logEvent.Error, "", http.StatusConflict)
logEvent.AddError("Certificate already revoked: %#v", serial)
wfe.sendError(response, "Certificate already revoked", "", http.StatusConflict)
return
}
// TODO: Implement method of revocation by authorizations on account.
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
registration.ID == cert.RegistrationID) {
logEvent.Error = "Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."
wfe.log.Debug("Key mismatch for revoke")
logEvent.AddError("Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.")
wfe.sendError(response,
logEvent.Error,
"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.",
requestKey,
http.StatusForbidden)
return
@ -739,7 +742,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
// Use revocation code 0, meaning "unspecified"
err = wfe.RA.RevokeCertificateWithReg(*parsedCertificate, 0, registration.ID)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("failed to revoke certificate: %s", err)
wfe.sendError(response, "Failed to revoke certificate", err, statusCodeFromError(err))
} else {
wfe.log.Debug(fmt.Sprintf("Revoked %v", serial))
@ -762,13 +765,10 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
// NewCertificate is used by clients to request the issuance of a cert for an
// authorized identifier.
func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
body, _, reg, err := wfe.verifyPOST(request, true, core.ResourceNewCert)
func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
body, _, reg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewCert)
if err != nil {
logEvent.Error = err.Error()
// verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS
respCode := statusCodeFromError(err)
if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -778,20 +778,18 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
wfe.sendError(response, respMsg, err, respCode)
return
}
logEvent.Requester = reg.ID
logEvent.Contacts = reg.Contact
// Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update.
if reg.Agreement == "" {
logEvent.Error = "Must agree to subscriber agreement before any further actions"
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden)
logEvent.AddError("Must agree to subscriber agreement before any further actions")
wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
return
}
var certificateRequest core.CertificateRequest
if err = json.Unmarshal(body, &certificateRequest); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to JSON unmarshal CertificateRequest: %s", err)
wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest)
return
}
@ -803,7 +801,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// a bad key from the client is just a malformed request and doesn't need to
// be audited.
if err = core.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("CSR public key failed GoodKey: %s", err)
wfe.sendError(response, "Invalid key in certificate request", err, http.StatusBadRequest)
return
}
@ -819,7 +817,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// RA for secondary validation.
cert, err := wfe.RA.NewCertificate(certificateRequest, reg.ID)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to create new cert: %s", err)
wfe.sendError(response, "Error creating new cert", err, statusCodeFromError(err))
return
}
@ -830,7 +828,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// enumerate and mirror our certificates.
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to parse certificate: %s", err)
wfe.sendError(response,
"Error creating new cert", err,
http.StatusBadRequest)
@ -845,7 +843,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusCreated)
if _, err = response.Write(cert.DER); err != nil {
logEvent.Error = err.Error()
logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
@ -853,10 +851,9 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// Challenge handles POST requests to challenge URLs. Such requests are clients'
// responses to the server's challenges.
func (wfe *WebFrontEndImpl) Challenge(
logEvent *requestEvent,
response http.ResponseWriter,
request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
notFound := func() {
wfe.sendError(response, "No such registration", request.URL.Path, http.StatusNotFound)
@ -900,10 +897,10 @@ func (wfe *WebFrontEndImpl) Challenge(
switch request.Method {
case "GET", "HEAD":
wfe.getChallenge(response, request, authz, &challenge, &logEvent)
wfe.getChallenge(response, request, authz, &challenge, logEvent)
case "POST":
wfe.postChallenge(response, request, authz, challengeIndex, &logEvent)
wfe.postChallenge(response, request, authz, challengeIndex, logEvent)
}
}
@ -941,7 +938,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
jsonReply, err := json.Marshal(challenge)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to marshal challenge: %s", err)
// InternalServerError because this is a failure to decode data passed in
// by the caller, which got it from the DB.
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
@ -955,7 +952,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
response.WriteHeader(http.StatusAccepted)
if _, err := response.Write(jsonReply); err != nil {
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
logEvent.Error = err.Error()
logEvent.AddError(err.Error())
return
}
}
@ -966,9 +963,9 @@ func (wfe *WebFrontEndImpl) postChallenge(
authz core.Authorization,
challengeIndex int,
logEvent *requestEvent) {
body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceChallenge)
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge)
if err != nil {
logEvent.Error = err.Error()
// verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS
respCode := http.StatusBadRequest
if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -978,30 +975,28 @@ func (wfe *WebFrontEndImpl) postChallenge(
wfe.sendError(response, respMsg, err, respCode)
return
}
logEvent.Requester = currReg.ID
logEvent.Contacts = currReg.Contact
// Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update.
if currReg.Agreement == "" {
logEvent.Error = "Must agree to subscriber agreement before any further actions"
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden)
logEvent.AddError("Registration didn't agree to subscriber agreement before any further actions")
wfe.sendError(response, "Registration didn't agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
return
}
// Check that the registration ID matching the key used matches
// the registration ID on the authz object
if currReg.ID != authz.RegistrationID {
logEvent.Error = fmt.Sprintf("User: %v != Authorization: %v", currReg.ID, authz.RegistrationID)
logEvent.AddError("User registration id: %d != Authorization registration id: %v", currReg.ID, authz.RegistrationID)
wfe.sendError(response, "User registration ID doesn't match registration ID in authorization",
logEvent.Error,
"",
http.StatusForbidden)
return
}
var challengeUpdate core.Challenge
if err = json.Unmarshal(body, &challengeUpdate); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("error JSON unmarshalling challenge response: %s", err)
wfe.sendError(response, "Error unmarshaling challenge response", err, http.StatusBadRequest)
return
}
@ -1009,7 +1004,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
// Ask the RA to update this authorization
updatedAuthorization, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeUpdate)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to update challenge: %s", err)
wfe.sendError(response, "Unable to update challenge", err, statusCodeFromError(err))
return
}
@ -1019,7 +1014,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
wfe.prepChallengeForDisplay(authz, &challenge)
jsonReply, err := json.Marshal(challenge)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("failed to marshal challenge: %s", err)
// StatusInternalServerError because we made the challenges, they should be OK
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
return
@ -1031,20 +1026,18 @@ func (wfe *WebFrontEndImpl) postChallenge(
response.Header().Add("Link", link(authzURL, "up"))
response.WriteHeader(http.StatusAccepted)
if _, err = response.Write(jsonReply); err != nil {
logEvent.Error = err.Error()
logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
return
}
}
// Registration is used by a client to submit an update to their registration.
func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceRegistration)
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration)
if err != nil {
logEvent.Error = err.Error()
// verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS
respCode := statusCodeFromError(err)
if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -1054,38 +1047,37 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
wfe.sendError(response, respMsg, err, respCode)
return
}
logEvent.Requester = currReg.ID
logEvent.Contacts = currReg.Contact
// Requests to this handler should have a path that leads to a known
// registration
idStr := parseIDFromPath(request.URL.Path)
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("registration ID must be an integer, was %#v", idStr)
wfe.sendError(response, "Registration ID must be an integer", err, http.StatusBadRequest)
return
} else if id <= 0 {
logEvent.Error = "Registration ID must be a positive non-zero integer"
wfe.sendError(response, logEvent.Error, id, http.StatusBadRequest)
logEvent.AddError("Registration ID must be a positive non-zero integer, was %d", id)
wfe.sendError(response, "Registration ID must be a positive non-zero integer", id, http.StatusBadRequest)
return
} else if id != currReg.ID {
logEvent.Error = "Request signing key did not match registration key"
wfe.sendError(response, logEvent.Error, "", http.StatusForbidden)
logEvent.AddError("Request signing key did not match registration key: %d != %d", id, currReg.ID)
wfe.sendError(response, "Request signing key did not match registration key", "", http.StatusForbidden)
return
}
var update core.Registration
err = json.Unmarshal(body, &update)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to JSON parse registration: %s", err)
wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest)
return
}
if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL {
logEvent.Error = fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL)
wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest)
msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL)
logEvent.AddError(msg)
wfe.sendError(response, msg, nil, http.StatusBadRequest)
return
}
@ -1098,14 +1090,14 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
// Ask the RA to update this authorization.
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to update registration: %s", err)
wfe.sendError(response, "Unable to update registration", err, statusCodeFromError(err))
return
}
jsonReply, err := json.Marshal(updatedReg)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to marshal updated registration: %s", err)
// StatusInternalServerError because we just generated the reg, it should be OK
wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError)
return
@ -1121,14 +1113,12 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
// Authorization is used by clients to submit an update to one of their
// authorizations.
func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
// Requests to this handler should have a path that leads to a known authz
id := parseIDFromPath(request.URL.Path)
authz, err := wfe.SA.GetAuthorization(id)
if err != nil {
logEvent.AddError("No such authorization at id %s", id)
wfe.sendError(response,
"Unable to find authorization", err,
http.StatusNotFound)
@ -1144,16 +1134,16 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
jsonReply, err := json.Marshal(authz)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("Failed to JSON marshal authz: %s", err)
// InternalServerError because this is a failure to decode from our DB.
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
wfe.sendError(response, "Failed to JSON marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Add("Link", link(wfe.NewCert, "next"))
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusOK)
if _, err = response.Write(jsonReply); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to write authorization update response: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
@ -1162,32 +1152,29 @@ var allHex = regexp.MustCompile("^[0-9a-f]+$")
// Certificate is used by clients to request a copy of their current certificate, or to
// request a reissuance of the certificate.
func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) Certificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
path := request.URL.Path
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
// digits.
if !strings.HasPrefix(path, CertPath) {
logEvent.Error = "Certificate not found"
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound)
logEvent.AddError("this request path should not have gotten to Certificate: %#v is not a prefix of %#v", path, CertPath)
wfe.sendError(response, "Certificate not found", path, http.StatusNotFound)
addNoCacheHeader(response)
return
}
serial := path[len(CertPath):]
if !core.ValidSerial(serial) {
logEvent.Error = "Certificate not found"
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound)
logEvent.AddError("certificate serial provided was not valid: %s", serial)
wfe.sendError(response, "Certificate not found", serial, http.StatusNotFound)
addNoCacheHeader(response)
return
}
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
logEvent.Extra["RequestedSerial"] = serial
cert, err := wfe.SA.GetCertificate(serial)
if err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to get certificate by serial id %#v: %s", serial, err)
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
} else {
@ -1204,7 +1191,7 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
response.Header().Add("Link", link(IssuerPath, "up"))
response.WriteHeader(http.StatusOK)
if _, err = response.Write(cert.DER); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to write new certificate response: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
return
@ -1212,39 +1199,30 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
// Terms is used by the client to obtain the current Terms of Service /
// Subscriber Agreement to which the subscriber must agree.
func (wfe *WebFrontEndImpl) Terms(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) Terms(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, wfe.SubscriberAgreementURL, http.StatusFound)
}
// Issuer obtains the issuer certificate used by this instance of Boulder.
func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) Issuer(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
addCacheHeader(response, wfe.IssuerCacheDuration.Seconds())
// TODO Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusOK)
if _, err := response.Write(wfe.IssuerCert); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to write issuer certificate response: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
// BuildID tells the requestor what build we're running.
func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
func (wfe *WebFrontEndImpl) BuildID(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
response.Header().Set("Content-Type", "text/plain")
response.WriteHeader(http.StatusOK)
detailsString := fmt.Sprintf("Boulder=(%s %s)", core.GetBuildID(), core.GetBuildTime())
if _, err := fmt.Fprintln(response, detailsString); err != nil {
logEvent.Error = err.Error()
logEvent.AddError("unable to print build information: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
@ -1303,40 +1281,3 @@ func (wfe *WebFrontEndImpl) setCORSHeaders(response http.ResponseWriter, request
response.Header().Set("Access-Control-Expose-Headers", "Link, Replay-Nonce")
response.Header().Set("Access-Control-Max-Age", "86400")
}
func (wfe *WebFrontEndImpl) logRequestDetails(logEvent *requestEvent) {
logEvent.ResponseTime = time.Now()
var msg string
if logEvent.Error != "" {
msg = "Terminated request"
} else {
msg = "Successful request"
}
wfe.log.InfoObject(msg, logEvent)
}
func (wfe *WebFrontEndImpl) populateRequestEvent(request *http.Request) (logEvent requestEvent) {
logEvent = requestEvent{
ID: core.NewToken(),
RealIP: request.Header.Get("X-Real-IP"),
ClientAddr: getClientAddr(request),
Method: request.Method,
RequestTime: time.Now(),
Extra: make(map[string]interface{}, 0),
}
if request.URL != nil {
logEvent.Endpoint = request.URL.String()
}
return
}
// Comma-separated list of HTTP clients involved in making this
// request, starting with the original requestor and ending with the
// remote end of our TCP connection (which is typically our own
// proxy).
func getClientAddr(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + r.RemoteAddr
}
return r.RemoteAddr
}

View File

@ -271,7 +271,7 @@ func TestHandleFunc(t *testing.T) {
mux = http.NewServeMux()
rw = httptest.NewRecorder()
stubCalled = false
wfe.HandleFunc(mux, "/test", func(http.ResponseWriter, *http.Request) {
wfe.HandleFunc(mux, "/test", func(*requestEvent, http.ResponseWriter, *http.Request) {
stubCalled = true
}, allowed...)
req.URL = mustParseURL("/test")
@ -463,7 +463,7 @@ func TestIndexPOST(t *testing.T) {
wfe := setupWFE(t)
responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/")
wfe.Index(responseWriter, &http.Request{
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
Method: "POST",
URL: url,
})
@ -474,7 +474,7 @@ func TestPOST404(t *testing.T) {
wfe := setupWFE(t)
responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/foobar")
wfe.Index(responseWriter, &http.Request{
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
Method: "POST",
URL: url,
})
@ -488,7 +488,7 @@ func TestIndex(t *testing.T) {
responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/")
wfe.Index(responseWriter, &http.Request{
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
Method: "GET",
URL: url,
})
@ -501,7 +501,7 @@ func TestIndex(t *testing.T) {
responseWriter.Body.Reset()
responseWriter.Header().Del("Cache-Control")
url, _ = url.Parse("/foo")
wfe.Index(responseWriter, &http.Request{
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
URL: url,
})
//test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
@ -566,7 +566,7 @@ func TestIssueCertificate(t *testing.T) {
// POST, but no body.
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, &http.Request{
wfe.NewCertificate(newRequestEvent(), responseWriter, &http.Request{
Method: "POST",
Header: map[string][]string{
"Content-Length": []string{"0"},
@ -578,14 +578,14 @@ func TestIssueCertificate(t *testing.T) {
// POST, but body that isn't valid JWS
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, makePostRequest("hi"))
wfe.NewCertificate(newRequestEvent(), responseWriter, makePostRequest("hi"))
test.AssertEquals(t,
responseWriter.Body.String(),
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, "foo", &wfe.nonceService)))
test.AssertEquals(t,
responseWriter.Body.String(),
@ -593,7 +593,7 @@ func TestIssueCertificate(t *testing.T) {
// Valid, signed JWS body, payload is '{}'
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(
signRequest(t, "{}", &wfe.nonceService)))
test.AssertEquals(t,
@ -602,7 +602,7 @@ func TestIssueCertificate(t *testing.T) {
// Valid, signed JWS body, payload is '{"resource":"new-cert"}'
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{"resource":"new-cert"}`, &wfe.nonceService)))
test.AssertEquals(t,
responseWriter.Body.String(),
@ -613,7 +613,8 @@ func TestIssueCertificate(t *testing.T) {
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=foo.com | \
// sed 's/foo.com/fob.com/' | b64url
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(),
responseWriter,
makePostRequest(signRequest(t, `{
"resource":"new-cert",
"csr": "MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHZm9iLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKzHhqcMSTVjBu61vufGVmIYM4mMbWXgndHOUWnIqSKcNtFtPQ465tcZRT5ITIZWXGjsmgDrj31qvG3t5qLwyaF5hsTvFHK72nLMAQhdgM6481Qe9yaoaulWpkGr_9LVz4jQ9pGAaLVamXGpSxV-ipTOo79Sev4aZE8ksD9atEfWtcOD9w8_zj74vpWjTAHN49Q88chlChVqakn0zSfHPfS-jF8g0UTddBuF0Ti3sZChjxzbo6LwZ4182xX7XPnOLav3AGj0Su7j5XMl3OpenOrlWulWJeZIHq5itGW321j306XiGdbrdWH4K7JygICFds6oolwQRGBY6yinAtCgkTcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBxPiHOtKuBxtvecMNtLkTSuTyEkusQGnjoFDaKe5oqwGYQgy0YBii2-BbaPmqS4ZaDc-vDz_RLeKH5ZiH-NliYR1V_CRtpFLQi18g_2pLQnZLVO3ENs-SM37nU_nBGn9O93t2bkssoM3fZmtgp3R2W7I_wvx7Z8oWKa4boTeBAg_q9Gmi6QskZBddK7A4S_vOR0frU6QSPK_ksPhvovp9fwb6CVKrlJWf556UwRPWgbkW39hvTxK2KHhrUEg3oawNkWde2jZtnZ9e-9zpw8-_5O0X7-YN0ucbFTfQybce_ReuLlGepiHT5bvVavBZoIvqw1XOgSMvGgZFU8tAWMBlj"
@ -626,7 +627,7 @@ func TestIssueCertificate(t *testing.T) {
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=meep.com | b64url
mockLog.Clear()
responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{
"resource":"new-cert",
"csr": "MIICWDCCAUACAQAwEzERMA8GA1UEAwwIbWVlcC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaqzue57mgXEoGTZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5-UED2bFRL1MsyBdbmCF_-pu3i-ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1_TBOwTAuFkHePmkNT63uPm9My_hNzsSm1o-Q519Cf7ry-JQmOVgz_jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoXRUlQjXXkNTj23OxxdT_cVLcLJjytyG6e5izME2R2aCkDBWIc1a4_sRJ0R396auPXG6KhJ7o_AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEALu046p76aKgvoAEHFINkMTgKokPXf9mZ4IZx_BKz-qs1MPMxVtPIrQDVweBH6tYT7Hfj2naLry6SpZ3vUNP_FYeTFWgW1V03LiqacX-QQgbEYtn99Dt3ScGyzb7EH833ztb3vDJ_-ha_CJplIrg-kHBBrlLFWXhh-I9K1qLRTNpbhZ18ooFde4Sbhkw9o9fKivGhx9aYr7ZbjRsNtKit_DsG1nwEXz53TMJ2vB9IQY29coJv_n5NFLkvBfzbG5faRNiFcimPYBO2jFdaA2mWzfxltLtwMF_dBwzTXDpMo3TVT9zEdV8YpsWqr63igqGDZVpKenlkqvRTeGJVayVuMA"
@ -639,7 +640,7 @@ func TestIssueCertificate(t *testing.T) {
mockLog.Clear()
responseWriter.Body.Reset()
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=not-an-example.com | b64url
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{
"resource":"new-cert",
"csr": "MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSbm90LWFuLWV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJAma2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1MIY-NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf_qbt4vmSGKloIim41QXt55QFW6O-84s8Kd2OE6df0wTsEwLhZB3j5pDU-t7j5vTMv4Tc7EptaPkOdfQn-68viUJjlYM_4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI115DU49tzscXU_3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd_emrj1xuioSe6PwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAE_T1nWU38XVYL28hNVSXU0rW5IBUKtbvr0qAkD4kda4HmQRTYkt-LNSuvxoZCC9lxijjgtJi-OJe_DCTdZZpYzewlVvcKToWSYHYQ6Wm1-fxxD_XzphvZOujpmBySchdiz7QSVWJmVZu34XD5RJbIcrmj_cjRt42J1hiTFjNMzQu9U6_HwIMmliDL-soFY2RTvvZf-dAFvOUQ-Wbxt97eM1PbbmxJNWRhbAmgEpe9PWDPTpqV5AK56VAa991cQ1P8ZVmPss5hvwGWhOtpnpTZVHN3toGNYFKqxWPboirqushQlfKiFqT9rpRgM3-mFjOHidGqsKEkTdmfSVlVEk3oo="
@ -680,7 +681,7 @@ func TestGetChallenge(t *testing.T) {
req, err := http.NewRequest(method, challengeURL, nil)
test.AssertNotError(t, err, "Could not make NewRequest")
wfe.Challenge(resp, req)
wfe.Challenge(newRequestEvent(), resp, req)
test.AssertEquals(t,
resp.Code,
http.StatusAccepted)
@ -722,7 +723,7 @@ func TestChallenge(t *testing.T) {
test.AssertNotError(t, err, "Could not unmarshal testing key")
challengeURL := "/acme/challenge/valid/23"
wfe.Challenge(responseWriter,
wfe.Challenge(newRequestEvent(), responseWriter,
makePostRequestWithPath(challengeURL,
signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService)))
@ -842,7 +843,7 @@ func TestNewRegistration(t *testing.T) {
nonce, err = wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce)
wfe.NewRegistration(responseWriter,
wfe.NewRegistration(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize()))
var reg core.Registration
@ -878,7 +879,7 @@ func TestNewRegistration(t *testing.T) {
test.AssertNotError(t, err, "Unable to create nonce")
result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce)
wfe.NewRegistration(responseWriter,
wfe.NewRegistration(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t,
responseWriter.Body.String(),
@ -946,7 +947,7 @@ func TestRevokeCertificateCertKey(t *testing.T) {
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
@ -970,7 +971,7 @@ func TestRevokeCertificateAccountKey(t *testing.T) {
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
@ -994,7 +995,7 @@ func TestRevokeCertificateWrongKey(t *testing.T) {
test.AssertNotError(t, err, "Unable to create revoke request")
result, _ := accountKeySigner2.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 403)
test.AssertEquals(t, responseWriter.Body.String(),
@ -1038,7 +1039,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 409)
test.AssertEquals(t, responseWriter.Body.String(),
@ -1064,7 +1065,7 @@ func TestAuthorization(t *testing.T) {
// POST, but no body.
responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, &http.Request{
wfe.NewAuthorization(newRequestEvent(), responseWriter, &http.Request{
Method: "POST",
Header: map[string][]string{
"Content-Length": []string{"0"},
@ -1074,12 +1075,12 @@ func TestAuthorization(t *testing.T) {
// POST, but body that isn't valid JWS
responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, makePostRequest("hi"))
wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest("hi"))
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter,
wfe.NewAuthorization(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, "foo", &wfe.nonceService)))
test.AssertEquals(t,
responseWriter.Body.String(),
@ -1088,7 +1089,7 @@ func TestAuthorization(t *testing.T) {
// Same signed body, but payload modified by one byte, breaking signature.
// should fail JWS verification.
responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, makePostRequest(`
wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest(`
{
"header": {
"alg": "RS256",
@ -1107,7 +1108,7 @@ func TestAuthorization(t *testing.T) {
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error"}`)
responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter,
wfe.NewAuthorization(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{"resource":"new-authz","identifier":{"type":"dns","value":"test.com"}}`, &wfe.nonceService)))
test.AssertEquals(
@ -1166,7 +1167,7 @@ func TestRegistration(t *testing.T) {
responseWriter.Body.Reset()
// Test POST invalid JSON
wfe.Registration(responseWriter, makePostRequestWithPath("/2", "invalid"))
wfe.Registration(newRequestEvent(), responseWriter, makePostRequestWithPath("/2", "invalid"))
test.AssertEquals(t,
responseWriter.Body.String(),
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
@ -1184,7 +1185,7 @@ func TestRegistration(t *testing.T) {
test.AssertNotError(t, err, "Unable to create nonce")
result, err := signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce)
test.AssertNotError(t, err, "Unable to sign")
wfe.Registration(responseWriter,
wfe.Registration(newRequestEvent(), responseWriter,
makePostRequestWithPath("/2", result.FullSerialize()))
test.AssertEquals(t,
responseWriter.Body.String(),
@ -1204,7 +1205,7 @@ func TestRegistration(t *testing.T) {
result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"https://letsencrypt.org/im-bad"}`), nonce)
// Test POST valid JSON with registration up in the mock
wfe.Registration(responseWriter,
wfe.Registration(newRequestEvent(), responseWriter,
makePostRequestWithPath("/1", result.FullSerialize()))
test.AssertEquals(t,
responseWriter.Body.String(),
@ -1216,7 +1217,7 @@ func TestRegistration(t *testing.T) {
test.AssertNotError(t, err, "Unable to create nonce")
result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce)
test.AssertNotError(t, err, "Couldn't sign")
wfe.Registration(responseWriter,
wfe.Registration(newRequestEvent(), responseWriter,
makePostRequestWithPath("/1", result.FullSerialize()))
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
links := responseWriter.Header()["Link"]
@ -1237,7 +1238,7 @@ func TestTermsRedirect(t *testing.T) {
responseWriter := httptest.NewRecorder()
path, _ := url.Parse("/terms")
wfe.Terms(responseWriter, &http.Request{
wfe.Terms(newRequestEvent(), responseWriter, &http.Request{
Method: "GET",
URL: path,
})
@ -1254,7 +1255,7 @@ func TestIssuer(t *testing.T) {
responseWriter := httptest.NewRecorder()
wfe.Issuer(responseWriter, &http.Request{
wfe.Issuer(newRequestEvent(), responseWriter, &http.Request{
Method: "GET",
})
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
@ -1264,6 +1265,9 @@ func TestIssuer(t *testing.T) {
func TestGetCertificate(t *testing.T) {
wfe := setupWFE(t)
mux, err := wfe.Handler()
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
wfe.CertCacheDuration = time.Second * 10
wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7
wfe.SA = &mocks.StorageAuthority{}
@ -1279,12 +1283,13 @@ func TestGetCertificate(t *testing.T) {
// Valid serial, cached
req, _ := http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000b2", nil)
req.RemoteAddr = "192.168.0.1"
wfe.Certificate(responseWriter, req)
mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert")
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match")
t.Logf("UGH %#v", mockLog.GetAll()[0])
reqlogs := mockLog.GetAllMatching(`Successful request`)
test.AssertEquals(t, len(reqlogs), 1)
test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO)
@ -1296,7 +1301,7 @@ func TestGetCertificate(t *testing.T) {
req, _ = http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000ff", nil)
req.RemoteAddr = "192.168.0.1"
req.Header.Set("X-Forwarded-For", "192.168.99.99")
wfe.Certificate(responseWriter, req)
mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
@ -1309,7 +1314,7 @@ func TestGetCertificate(t *testing.T) {
// Invalid serial, no cache
responseWriter = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/acme/cert/nothex", nil)
wfe.Certificate(responseWriter, req)
mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
@ -1317,7 +1322,7 @@ func TestGetCertificate(t *testing.T) {
// Invalid serial, no cache
responseWriter = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/acme/cert/00000000000000", nil)
wfe.Certificate(responseWriter, req)
mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
@ -1359,7 +1364,7 @@ func TestLogCsrPem(t *testing.T) {
func TestLengthRequired(t *testing.T) {
wfe := setupWFE(t)
_, _, _, err := wfe.verifyPOST(&http.Request{
_, _, _, err := wfe.verifyPOST(newRequestEvent(), &http.Request{
Method: "POST",
URL: mustParseURL("/"),
}, false, "resource")
@ -1387,7 +1392,7 @@ func TestVerifyPOSTUsesStoredKey(t *testing.T) {
wfe.SA = &mockSADifferentStoredKey{mocks.StorageAuthority{}}
// signRequest signs with test1Key, but our special mock returns a
// registration with test2Key
_, _, _, err := wfe.verifyPOST(makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo")
_, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo")
test.AssertError(t, err, "No error returned when provided key differed from stored key.")
}
@ -1398,7 +1403,7 @@ func TestBadKeyCSR(t *testing.T) {
// CSR with a bad (512 bit RSA) key.
// openssl req -outform der -new -newkey rsa:512 -nodes -keyout foo.com.key
// -subj /CN=foo.com | base64 -w0 | sed -e 's,+,-,g' -e 's,/,_,g'
wfe.NewCertificate(responseWriter,
wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{
"resource":"new-cert",
"csr": "MIHLMHcCAQAwEjEQMA4GA1UEAwwHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDCZftp4x4owgjBnwOKfzihIPedT-BUmV2fuQPMqaUlc8yJUp13vcO5uxUlaBm8leM7Dj_sgTDP_JgykorlYo73AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAEaQ2QBhweK-kp1ejQCedUhMit_wG-uTBtKnc3M82f6_fztLkhg1vWQ782nmhbEI5orXp6QtNHgJYnBpqA9Ut00"
@ -1431,3 +1436,7 @@ func TestStatusCodeFromError(t *testing.T) {
}
}
}
func newRequestEvent() *requestEvent {
return &requestEvent{Extra: make(map[string]interface{})}
}