Factor out context.go from wfe and wfe2. (#3086)

* Move probs.go to web.

* Move probs_test.go

* Factor out probs.go from wfe

* Move context.go

* Extract context.go into web package.

* Add a constructor for TopHandler.
This commit is contained in:
Jacob Hoffman-Andrews 2017-09-26 10:54:14 -07:00 committed by Daniel McCarney
parent 966e02313f
commit 97265c9184
8 changed files with 95 additions and 185 deletions

View File

@ -1,4 +1,4 @@
package wfe
package web
import (
"encoding/json"
@ -7,11 +7,10 @@ import (
"golang.org/x/net/context"
"github.com/jmhodges/clock"
blog "github.com/letsencrypt/boulder/log"
)
type requestEvent struct {
type RequestEvent struct {
RealIP string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Method string `json:",omitempty"`
@ -26,29 +25,35 @@ type requestEvent struct {
Extra map[string]interface{} `json:",omitempty"`
}
func (e *requestEvent) AddError(msg string, args ...interface{}) {
func (e *RequestEvent) AddError(msg string, args ...interface{}) {
e.Errors = append(e.Errors, fmt.Sprintf(msg, args...))
}
type wfeHandlerFunc func(context.Context, *requestEvent, http.ResponseWriter, *http.Request)
type WFEHandlerFunc func(context.Context, *RequestEvent, http.ResponseWriter, *http.Request)
func (f wfeHandlerFunc) ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request) {
func (f WFEHandlerFunc) ServeHTTP(e *RequestEvent, w http.ResponseWriter, r *http.Request) {
ctx := context.TODO()
f(ctx, e, w, r)
}
type wfeHandler interface {
ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request)
ServeHTTP(e *RequestEvent, w http.ResponseWriter, r *http.Request)
}
type topHandler struct {
type TopHandler struct {
wfe wfeHandler
log blog.Logger
clk clock.Clock
}
func (th *topHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logEvent := &requestEvent{
func NewTopHandler(log blog.Logger, wfe wfeHandler) *TopHandler {
return &TopHandler{
wfe: wfe,
log: log,
}
}
func (th *TopHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logEvent := &RequestEvent{
RealIP: r.Header.Get("X-Real-IP"),
Method: r.Method,
UserAgent: r.Header.Get("User-Agent"),
@ -59,7 +64,7 @@ func (th *topHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
th.wfe.ServeHTTP(logEvent, w, r)
}
func (th *topHandler) logEvent(logEvent *requestEvent) {
func (th *TopHandler) logEvent(logEvent *RequestEvent) {
var msg string
if len(logEvent.Errors) != 0 {
msg = "Terminated request"
@ -78,7 +83,7 @@ func (th *topHandler) logEvent(logEvent *requestEvent) {
// 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 {
func GetClientAddr(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + r.RemoteAddr
}

View File

@ -148,7 +148,7 @@ func NewWebFrontEndImpl(
// * 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 wfeHandlerFunc, methods ...string) {
func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h web.WFEHandlerFunc, methods ...string) {
methodsMap := make(map[string]bool)
for _, m := range methods {
methodsMap[m] = true
@ -159,10 +159,8 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfe
methodsMap["HEAD"] = true
}
methodsStr := strings.Join(methods, ", ")
handler := http.StripPrefix(pattern, &topHandler{
log: wfe.log,
clk: clock.Default(),
wfe: wfeHandlerFunc(func(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
handler := http.StripPrefix(pattern, web.NewTopHandler(wfe.log,
web.WFEHandlerFunc(func(ctx context.Context, logEvent *web.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()
@ -210,7 +208,7 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfe
h(ctx, logEvent, response, request)
cancel()
}),
})
))
mux.Handle(pattern, handler)
}
@ -218,7 +216,7 @@ func marshalIndent(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, logEvent *requestEvent, status int, v interface{}) error {
func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, logEvent *web.RequestEvent, status int, v interface{}) error {
jsonReply, err := marshalIndent(v)
if err != nil {
return err // All callers are responsible for handling this error
@ -324,18 +322,14 @@ func (wfe *WebFrontEndImpl) Handler() http.Handler {
// 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.Handle("/", &topHandler{
log: wfe.log,
clk: clock.Default(),
wfe: wfeHandlerFunc(wfe.Index),
})
m.Handle("/", web.NewTopHandler(wfe.log, web.WFEHandlerFunc(wfe.Index)))
return measured_http.New(m, wfe.clk)
}
// Method implementations
// Index serves a simple identification page. It is not part of the ACME spec.
func (wfe *WebFrontEndImpl) Index(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Index(ctx context.Context, logEvent *web.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.
@ -378,7 +372,7 @@ func addRequesterHeader(w http.ResponseWriter, requester int64) {
// 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) {
func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
directoryEndpoints := map[string]interface{}{
"new-reg": newRegPath,
"new-authz": newAuthzPath,
@ -463,10 +457,10 @@ func (wfe *WebFrontEndImpl) extractJWSKey(body string) (*jose.JSONWebKey, *jose.
// 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
// the key itself. verifyPOST also appends its errors to web.RequestEvent.Errors so
// code calling it does not need to if they immediately return a response to the
// user.
func (wfe *WebFrontEndImpl) verifyPOST(ctx context.Context, logEvent *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JSONWebKey, core.Registration, *probs.ProblemDetails) {
func (wfe *WebFrontEndImpl) verifyPOST(ctx context.Context, logEvent *web.RequestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JSONWebKey, core.Registration, *probs.ProblemDetails) {
// 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
@ -589,7 +583,7 @@ func (wfe *WebFrontEndImpl) verifyPOST(ctx context.Context, logEvent *requestEve
// and, if the ProblemDetails.Type is ServerInternalProblem, audit logs the
// internal ierr. The rendered Problem will have its Type prefixed with the ACME
// v1 namespace.
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, prob *probs.ProblemDetails, ierr error) {
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *web.RequestEvent, prob *probs.ProblemDetails, ierr error) {
// Determine the HTTP status code to use for this problem
code := probs.ProblemDetailsToStatusCode(prob)
@ -628,7 +622,7 @@ func link(url, relation string) string {
}
// NewRegistration is used by clients to submit a new registration/account
func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
body, key, _, prob := wfe.verifyPOST(ctx, logEvent, request, false, core.ResourceNewReg)
addRequesterHeader(response, logEvent.Requester)
@ -698,7 +692,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *reque
}
// NewAuthorization is used by clients to submit a new ID Authorization
func (wfe *WebFrontEndImpl) NewAuthorization(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) NewAuthorization(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
body, _, currReg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceNewAuthz)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
@ -762,7 +756,7 @@ func (wfe *WebFrontEndImpl) regHoldsAuthorizations(ctx context.Context, regID in
}
// RevokeCertificate is used by clients to request the revocation of a cert.
func (wfe *WebFrontEndImpl) RevokeCertificate(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) RevokeCertificate(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
// We don't ask verifyPOST to verify there is a corresponding registration,
// because anyone with the right private key can revoke a certificate.
body, requestKey, registration, prob := wfe.verifyPOST(ctx, logEvent, request, false, core.ResourceRevokeCert)
@ -861,7 +855,7 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
CSR string
Registration core.Registration
}{
ClientAddr: getClientAddr(request),
ClientAddr: web.GetClientAddr(request),
CSR: hex.EncodeToString(cr.Bytes),
Registration: registration,
}
@ -870,7 +864,7 @@ 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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) NewCertificate(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
body, _, reg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceNewCert)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
@ -981,7 +975,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(ctx context.Context, logEvent *reques
// responses to the server's challenges.
func (wfe *WebFrontEndImpl) Challenge(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
@ -1079,7 +1073,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
request *http.Request,
authz core.Authorization,
challenge *core.Challenge,
logEvent *requestEvent) {
logEvent *web.RequestEvent) {
wfe.prepChallengeForDisplay(request, authz, challenge)
@ -1102,7 +1096,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
request *http.Request,
authz core.Authorization,
challengeIndex int,
logEvent *requestEvent) {
logEvent *web.RequestEvent) {
body, _, currReg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceChallenge)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
@ -1160,7 +1154,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
}
// Registration is used by a client to submit an update to their registration.
func (wfe *WebFrontEndImpl) Registration(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Registration(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
body, _, currReg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceRegistration)
addRequesterHeader(response, logEvent.Requester)
@ -1251,7 +1245,7 @@ func (wfe *WebFrontEndImpl) Registration(ctx context.Context, logEvent *requestE
}
}
func (wfe *WebFrontEndImpl) deactivateAuthorization(ctx context.Context, authz *core.Authorization, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) bool {
func (wfe *WebFrontEndImpl) deactivateAuthorization(ctx context.Context, authz *core.Authorization, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) bool {
body, _, reg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceAuthz)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
@ -1288,7 +1282,7 @@ func (wfe *WebFrontEndImpl) deactivateAuthorization(ctx context.Context, authz *
// Authorization is used by clients to submit an update to one of their
// authorizations.
func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
// Requests to this handler should have a path that leads to a known authz
id := request.URL.Path
authz, err := wfe.SA.GetAuthorization(ctx, id)
@ -1335,7 +1329,7 @@ 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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
serial := request.URL.Path
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
@ -1384,12 +1378,12 @@ func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *requestEv
// 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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Terms(ctx context.Context, logEvent *web.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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Issuer(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
// TODO Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusOK)
@ -1400,7 +1394,7 @@ func (wfe *WebFrontEndImpl) Issuer(ctx context.Context, logEvent *requestEvent,
}
// BuildID tells the requestor what build we're running.
func (wfe *WebFrontEndImpl) BuildID(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) BuildID(ctx context.Context, logEvent *web.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())
@ -1465,7 +1459,7 @@ func (wfe *WebFrontEndImpl) setCORSHeaders(response http.ResponseWriter, request
}
// KeyRollover allows a user to change their signing key
func (wfe *WebFrontEndImpl) KeyRollover(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) KeyRollover(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
body, _, reg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceKeyChange)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
@ -1526,7 +1520,7 @@ func (wfe *WebFrontEndImpl) KeyRollover(ctx context.Context, logEvent *requestEv
response.Write(jsonReply)
}
func (wfe *WebFrontEndImpl) deactivateRegistration(ctx context.Context, reg core.Registration, response http.ResponseWriter, request *http.Request, logEvent *requestEvent) {
func (wfe *WebFrontEndImpl) deactivateRegistration(ctx context.Context, reg core.Registration, response http.ResponseWriter, request *http.Request, logEvent *web.RequestEvent) {
err := wfe.RA.DeactivateRegistration(ctx, reg)
if err != nil {
wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error deactivating registration"), err)

View File

@ -38,6 +38,7 @@ import (
"github.com/letsencrypt/boulder/revocation"
"github.com/letsencrypt/boulder/test"
vaPB "github.com/letsencrypt/boulder/va/proto"
"github.com/letsencrypt/boulder/web"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
@ -457,7 +458,7 @@ func TestHandleFunc(t *testing.T) {
mux = http.NewServeMux()
rw = httptest.NewRecorder()
stubCalled = false
wfe.HandleFunc(mux, "/test", func(context.Context, *requestEvent, http.ResponseWriter, *http.Request) {
wfe.HandleFunc(mux, "/test", func(context.Context, *web.RequestEvent, http.ResponseWriter, *http.Request) {
stubCalled = true
}, allowed...)
req.URL = mustParseURL("/test")
@ -752,7 +753,7 @@ func TestRandomDirectoryKey(t *testing.T) {
responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/directory")
wfe.Directory(ctx, &requestEvent{}, responseWriter, &http.Request{
wfe.Directory(ctx, &web.RequestEvent{}, responseWriter, &http.Request{
Method: "GET",
URL: url,
Host: "127.0.0.1:4300",
@ -778,7 +779,7 @@ func TestRandomDirectoryKey(t *testing.T) {
headers := map[string][]string{
"User-Agent": {"LetsEncryptPythonClient"},
}
wfe.Directory(ctx, &requestEvent{}, responseWriter, &http.Request{
wfe.Directory(ctx, &web.RequestEvent{}, responseWriter, &http.Request{
Method: "GET",
URL: url,
Host: "127.0.0.1:4300",
@ -2182,8 +2183,8 @@ func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {
test.AssertEquals(t, 0, len(body))
}
func newRequestEvent() *requestEvent {
return &requestEvent{Extra: make(map[string]interface{})}
func newRequestEvent() *web.RequestEvent {
return &web.RequestEvent{Extra: make(map[string]interface{})}
}
func TestVerifyPOSTInvalidJWK(t *testing.T) {

View File

@ -1,86 +0,0 @@
package wfe2
import (
"encoding/json"
"fmt"
"net/http"
"golang.org/x/net/context"
"github.com/jmhodges/clock"
blog "github.com/letsencrypt/boulder/log"
)
type requestEvent struct {
RealIP string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Method string `json:",omitempty"`
Errors []string `json:",omitempty"`
Requester int64 `json:",omitempty"`
Contacts *[]string `json:",omitempty"`
RequestNonce string `json:",omitempty"`
ResponseNonce string `json:",omitempty"`
UserAgent string `json:",omitempty"`
Code int
Payload string `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(context.Context, *requestEvent, http.ResponseWriter, *http.Request)
func (f wfeHandlerFunc) ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request) {
ctx := context.TODO()
f(ctx, e, w, r)
}
type wfeHandler interface {
ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request)
}
type topHandler struct {
wfe wfeHandler
log blog.Logger
clk clock.Clock
}
func (th *topHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logEvent := &requestEvent{
RealIP: r.Header.Get("X-Real-IP"),
Method: r.Method,
UserAgent: r.Header.Get("User-Agent"),
Extra: make(map[string]interface{}, 0),
}
defer th.logEvent(logEvent)
th.wfe.ServeHTTP(logEvent, w, r)
}
func (th *topHandler) logEvent(logEvent *requestEvent) {
var msg string
if len(logEvent.Errors) != 0 {
msg = "Terminated request"
} else {
msg = "Successful request"
}
jsonEvent, err := json.Marshal(logEvent)
if err != nil {
th.log.AuditErr(fmt.Sprintf("%s - failed to marshal logEvent - %s", msg, err))
return
}
th.log.Info(fmt.Sprintf("%s JSON=%s", msg, jsonEvent))
}
// 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

@ -20,6 +20,7 @@ import (
"github.com/letsencrypt/boulder/core"
berrors "github.com/letsencrypt/boulder/errors"
"github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/web"
)
var sigAlgErr = errors.New("no signature algorithms suitable for given key type")
@ -162,7 +163,7 @@ func (wfe *WebFrontEndImpl) validPOSTRequest(request *http.Request) *probs.Probl
// provided logEvent is mutated to set the observed RequestNonce.
// NOTE: this function assumes the JWS has already been verified with the
// correct public key.
func (wfe *WebFrontEndImpl) validNonce(jws *jose.JSONWebSignature, logEvent *requestEvent) *probs.ProblemDetails {
func (wfe *WebFrontEndImpl) validNonce(jws *jose.JSONWebSignature, logEvent *web.RequestEvent) *probs.ProblemDetails {
// validNonce is called after validPOSTRequest() and parseJWS() which
// defend against the incorrect number of signatures.
header := jws.Signatures[0].Header
@ -366,7 +367,7 @@ func (wfe *WebFrontEndImpl) lookupJWK(
jws *jose.JSONWebSignature,
ctx context.Context,
request *http.Request,
logEvent *requestEvent) (*jose.JSONWebKey, *core.Registration, *probs.ProblemDetails) {
logEvent *web.RequestEvent) (*jose.JSONWebKey, *core.Registration, *probs.ProblemDetails) {
// We expect the request to be using an embedded Key ID auth type and to not
// contain the mutually exclusive embedded JWK.
if prob := wfe.enforceJWSAuthType(jws, embeddedKeyID); prob != nil {
@ -427,7 +428,7 @@ func (wfe *WebFrontEndImpl) validJWSForKey(
jws *jose.JSONWebSignature,
jwk *jose.JSONWebKey,
request *http.Request,
logEvent *requestEvent) ([]byte, *probs.ProblemDetails) {
logEvent *web.RequestEvent) ([]byte, *probs.ProblemDetails) {
// Check that the public key and JWS algorithms match expected
if err := checkAlgorithm(jwk, jws); err != nil {
@ -484,7 +485,7 @@ func (wfe *WebFrontEndImpl) validJWSForAccount(
jws *jose.JSONWebSignature,
request *http.Request,
ctx context.Context,
logEvent *requestEvent) ([]byte, *jose.JSONWebSignature, *core.Registration, *probs.ProblemDetails) {
logEvent *web.RequestEvent) ([]byte, *jose.JSONWebSignature, *core.Registration, *probs.ProblemDetails) {
// Lookup the account and JWK for the key ID that authenticated the JWS
pubKey, account, prob := wfe.lookupJWK(jws, ctx, request, logEvent)
if prob != nil {
@ -505,7 +506,7 @@ func (wfe *WebFrontEndImpl) validJWSForAccount(
func (wfe *WebFrontEndImpl) validPOSTForAccount(
request *http.Request,
ctx context.Context,
logEvent *requestEvent) ([]byte, *jose.JSONWebSignature, *core.Registration, *probs.ProblemDetails) {
logEvent *web.RequestEvent) ([]byte, *jose.JSONWebSignature, *core.Registration, *probs.ProblemDetails) {
// Parse the JWS from the POST request
jws, prob := wfe.parseJWSRequest(request)
if prob != nil {
@ -528,7 +529,7 @@ func (wfe *WebFrontEndImpl) validPOSTForAccount(
func (wfe *WebFrontEndImpl) validSelfAuthenticatedJWS(
jws *jose.JSONWebSignature,
request *http.Request,
logEvent *requestEvent) ([]byte, *jose.JSONWebKey, *probs.ProblemDetails) {
logEvent *web.RequestEvent) ([]byte, *jose.JSONWebKey, *probs.ProblemDetails) {
// Extract the embedded JWK from the parsed JWS
pubKey, prob := wfe.extractJWK(jws)
if prob != nil {
@ -554,7 +555,7 @@ func (wfe *WebFrontEndImpl) validSelfAuthenticatedJWS(
// using `validSelfAuthenticatedJWS`.
func (wfe *WebFrontEndImpl) validSelfAuthenticatedPOST(
request *http.Request,
logEvent *requestEvent) ([]byte, *jose.JSONWebKey, *probs.ProblemDetails) {
logEvent *web.RequestEvent) ([]byte, *jose.JSONWebKey, *probs.ProblemDetails) {
// Parse the JWS from the POST request
jws, prob := wfe.parseJWSRequest(request)
if prob != nil {
@ -588,7 +589,7 @@ type rolloverRequest struct {
func (wfe *WebFrontEndImpl) validKeyRollover(
outerJWS *jose.JSONWebSignature,
innerJWS *jose.JSONWebSignature,
logEvent *requestEvent) (*rolloverRequest, *probs.ProblemDetails) {
logEvent *web.RequestEvent) (*rolloverRequest, *probs.ProblemDetails) {
// Extract the embedded JWK from the inner JWS
jwk, prob := wfe.extractJWK(innerJWS)
@ -616,7 +617,7 @@ func (wfe *WebFrontEndImpl) validKeyRollover(
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverJWSVerifyFailed"}).Inc()
return nil, probs.Malformed("Inner JWS does not verify with embedded JWK")
}
// NOTE(@cpu): we do not stomp the requestEvent's payload here since that is set
// NOTE(@cpu): we do not stomp the web.RequestEvent's payload here since that is set
// from the outerJWS in validPOSTForAccount and contains the inner JWS and inner
// payload already.

View File

@ -136,7 +136,7 @@ func NewWebFrontEndImpl(
// * 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 wfeHandlerFunc, methods ...string) {
func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h web.WFEHandlerFunc, methods ...string) {
methodsMap := make(map[string]bool)
for _, m := range methods {
methodsMap[m] = true
@ -147,10 +147,8 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfe
methodsMap["HEAD"] = true
}
methodsStr := strings.Join(methods, ", ")
handler := http.StripPrefix(pattern, &topHandler{
log: wfe.log,
clk: clock.Default(),
wfe: wfeHandlerFunc(func(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
handler := http.StripPrefix(pattern, web.NewTopHandler(wfe.log,
web.WFEHandlerFunc(func(ctx context.Context, logEvent *web.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()
@ -198,7 +196,7 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfe
h(ctx, logEvent, response, request)
cancel()
}),
})
))
mux.Handle(pattern, handler)
}
@ -206,7 +204,7 @@ func marshalIndent(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, logEvent *requestEvent, status int, v interface{}) error {
func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, logEvent *web.RequestEvent, status int, v interface{}) error {
jsonReply, err := marshalIndent(v)
if err != nil {
return err // All callers are responsible for handling this error
@ -319,18 +317,14 @@ func (wfe *WebFrontEndImpl) Handler() http.Handler {
// 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.Handle("/", &topHandler{
log: wfe.log,
clk: clock.Default(),
wfe: wfeHandlerFunc(wfe.Index),
})
m.Handle("/", web.NewTopHandler(wfe.log, web.WFEHandlerFunc(wfe.Index)))
return measured_http.New(m, wfe.clk)
}
// Method implementations
// Index serves a simple identification page. It is not part of the ACME spec.
func (wfe *WebFrontEndImpl) Index(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Index(ctx context.Context, logEvent *web.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.
@ -373,7 +367,7 @@ func addRequesterHeader(w http.ResponseWriter, requester int64) {
// 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) {
func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
directoryEndpoints := map[string]interface{}{
"new-account": newAcctPath,
"revoke-cert": revokeCertPath,
@ -409,7 +403,7 @@ func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *requestEven
// and, if the ProblemDetails.Type is ServerInternalProblem, audit logs the
// internal ierr. The rendered Problem will have its Type prefixed with the ACME
// v2 error namespace.
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, prob *probs.ProblemDetails, ierr error) {
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *web.RequestEvent, prob *probs.ProblemDetails, ierr error) {
// Determine the HTTP status code to use for this problem
code := probs.ProblemDetailsToStatusCode(prob)
@ -450,7 +444,7 @@ func link(url, relation string) string {
// NewAccount is used by clients to submit a new account
func (wfe *WebFrontEndImpl) NewAccount(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
@ -562,7 +556,7 @@ func (wfe *WebFrontEndImpl) processRevocation(
acctID int64,
authorizedToRevoke authorizedToRevokeCert,
request *http.Request,
logEvent *requestEvent) *probs.ProblemDetails {
logEvent *web.RequestEvent) *probs.ProblemDetails {
// Read the revoke request from the JWS payload
var revokeRequest struct {
CertificateDER core.JSONBuffer `json:"certificate"`
@ -642,7 +636,7 @@ func (wfe *WebFrontEndImpl) revokeCertByKeyID(
ctx context.Context,
outerJWS *jose.JSONWebSignature,
request *http.Request,
logEvent *requestEvent) *probs.ProblemDetails {
logEvent *web.RequestEvent) *probs.ProblemDetails {
// For Key ID revocations we authenticate the outer JWS by using
// `validJWSForAccount` similar to other WFE endpoints
jwsBody, _, acct, prob := wfe.validJWSForAccount(outerJWS, request, ctx, logEvent)
@ -674,7 +668,7 @@ func (wfe *WebFrontEndImpl) revokeCertByJWK(
ctx context.Context,
outerJWS *jose.JSONWebSignature,
request *http.Request,
logEvent *requestEvent) *probs.ProblemDetails {
logEvent *web.RequestEvent) *probs.ProblemDetails {
// We maintain the requestKey as a var that is closed-over by the
// `authorizedToRevoke` function to use
var requestKey *jose.JSONWebKey
@ -707,7 +701,7 @@ func (wfe *WebFrontEndImpl) revokeCertByJWK(
// used.
func (wfe *WebFrontEndImpl) RevokeCertificate(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
@ -755,7 +749,7 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
CSR string
Registration core.Registration
}{
ClientAddr: getClientAddr(request),
ClientAddr: web.GetClientAddr(request),
CSR: hex.EncodeToString(cr.Bytes),
Registration: account,
}
@ -766,7 +760,7 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
// responses to the server's challenges.
func (wfe *WebFrontEndImpl) Challenge(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
@ -864,7 +858,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
request *http.Request,
authz core.Authorization,
challenge *core.Challenge,
logEvent *requestEvent) {
logEvent *web.RequestEvent) {
wfe.prepChallengeForDisplay(request, authz, challenge)
@ -887,7 +881,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
request *http.Request,
authz core.Authorization,
challengeIndex int,
logEvent *requestEvent) {
logEvent *web.RequestEvent) {
body, _, currAcct, prob := wfe.validPOSTForAccount(request, ctx, logEvent)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
@ -947,7 +941,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
// Account is used by a client to submit an update to their account.
func (wfe *WebFrontEndImpl) Account(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
body, _, currAcct, prob := wfe.validPOSTForAccount(request, ctx, logEvent)
@ -1045,7 +1039,7 @@ func (wfe *WebFrontEndImpl) Account(
func (wfe *WebFrontEndImpl) deactivateAuthorization(
ctx context.Context,
authz *core.Authorization,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) bool {
body, _, acct, prob := wfe.validPOSTForAccount(request, ctx, logEvent)
@ -1085,7 +1079,7 @@ func (wfe *WebFrontEndImpl) deactivateAuthorization(
// Authorization is used by clients to submit an update to one of their
// authorizations.
func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
// Requests to this handler should have a path that leads to a known authz
id := request.URL.Path
authz, err := wfe.SA.GetAuthorization(ctx, id)
@ -1129,7 +1123,7 @@ 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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
serial := request.URL.Path
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
@ -1176,12 +1170,12 @@ func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *requestEv
// 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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Terms(ctx context.Context, logEvent *web.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(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Issuer(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
// TODO Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusOK)
@ -1192,7 +1186,7 @@ func (wfe *WebFrontEndImpl) Issuer(ctx context.Context, logEvent *requestEvent,
}
// BuildID tells the requestor what build we're running.
func (wfe *WebFrontEndImpl) BuildID(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) BuildID(ctx context.Context, logEvent *web.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())
@ -1259,7 +1253,7 @@ func (wfe *WebFrontEndImpl) setCORSHeaders(response http.ResponseWriter, request
// KeyRollover allows a user to change their signing key
func (wfe *WebFrontEndImpl) KeyRollover(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
// Validate the outer JWS on the key rollover in standard fashion using
@ -1343,7 +1337,7 @@ func (wfe *WebFrontEndImpl) deactivateAccount(
acct core.Registration,
response http.ResponseWriter,
request *http.Request,
logEvent *requestEvent) {
logEvent *web.RequestEvent) {
err := wfe.RA.DeactivateRegistration(ctx, acct)
if err != nil {
wfe.sendError(response, logEvent,
@ -1388,7 +1382,7 @@ type orderJSON struct {
// NewOrder is used by clients to create a new order object from a CSR
func (wfe *WebFrontEndImpl) NewOrder(
ctx context.Context,
logEvent *requestEvent,
logEvent *web.RequestEvent,
response http.ResponseWriter,
request *http.Request) {
body, _, acct, prob := wfe.validPOSTForAccount(request, ctx, logEvent)
@ -1460,7 +1454,7 @@ func (wfe *WebFrontEndImpl) NewOrder(
}
// Order is used to retrieve a existing order object
func (wfe *WebFrontEndImpl) Order(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) Order(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
fields := strings.SplitN(request.URL.Path, "/", 2)
if len(fields) != 2 {
wfe.sendError(response, logEvent, probs.Malformed("Invalid request path"), nil)

View File

@ -37,6 +37,7 @@ import (
rapb "github.com/letsencrypt/boulder/ra/proto"
"github.com/letsencrypt/boulder/revocation"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/web"
)
const (
@ -403,7 +404,7 @@ func TestHandleFunc(t *testing.T) {
mux = http.NewServeMux()
rw = httptest.NewRecorder()
stubCalled = false
wfe.HandleFunc(mux, "/test", func(context.Context, *requestEvent, http.ResponseWriter, *http.Request) {
wfe.HandleFunc(mux, "/test", func(context.Context, *web.RequestEvent, http.ResponseWriter, *http.Request) {
stubCalled = true
}, allowed...)
req.URL = mustParseURL("/test")
@ -1563,8 +1564,8 @@ func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {
test.AssertEquals(t, 0, len(body))
}
func newRequestEvent() *requestEvent {
return &requestEvent{Extra: make(map[string]interface{})}
func newRequestEvent() *web.RequestEvent {
return &web.RequestEvent{Extra: make(map[string]interface{})}
}
func TestHeaderBoulderRequester(t *testing.T) {