From 339ba89ed2c003a4d23d7fcf30d7af15f34c3e96 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 25 Mar 2020 14:44:59 +0100 Subject: [PATCH] move handler creation to pkg node, remove deprecated function --- cmd/geth/retesteth.go | 4 +- node/node.go | 8 +-- node/rpcstack.go | 157 ++++++++++++++++++++++++++++++++++++++++++ rpc/gzip.go | 87 +++++++++++------------ rpc/http.go | 123 --------------------------------- 5 files changed, 202 insertions(+), 177 deletions(-) create mode 100644 node/rpcstack.go diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index 43cb8191fb..c1536921a8 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -891,8 +891,8 @@ func retesteth(ctx *cli.Context) error { srv := rpc.NewServer() - handler := rpc.NewHTTPHandlerStack(srv, cors, vhosts) - handler = rpc.NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) + handler := node.NewHTTPHandlerStack(srv, cors, vhosts) + handler = node.NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) // start http server var RetestethHTTPTimeouts = rpc.HTTPTimeouts{ diff --git a/node/node.go b/node/node.go index 52ac182ca7..0748ae6be7 100644 --- a/node/node.go +++ b/node/node.go @@ -372,7 +372,7 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors srv := rpc.NewServer() - err := RegisterApisFromWhitelist(apis, modules, srv) + err := registerApisFromWhitelist(apis, modules, srv) var ws http.Handler if n.httpEndpoint == n.wsEndpoint { @@ -380,7 +380,7 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors } // wrap handler in websocket handler only if websocket port is the same as http rpc - handler := n.AddWebsocketHandler(rpc.NewHTTPHandlerStack(srv, cors, vhosts), ws) + handler := n.AddWebsocketHandler(NewHTTPHandlerStack(srv, cors, vhosts), ws) listener, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, timeouts, handler) if err != nil { @@ -405,7 +405,7 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors // AddWebsocketHandler creates the handler stack necessary to handle both http rpc requests and websocket requests func (n *Node) AddWebsocketHandler(handler http.Handler, websocket http.Handler) http.Handler { if websocket != nil { - return rpc.NewWebsocketUpgradeHandler(handler, websocket) + return NewWebsocketUpgradeHandler(handler, websocket) } return handler @@ -697,7 +697,7 @@ func (n *Node) apis() []rpc.API { } } -func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server) error { +func registerApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server) error { // Generate the whitelist based on the allowed modules whitelist := make(map[string]bool) for _, module := range modules { diff --git a/node/rpcstack.go b/node/rpcstack.go new file mode 100644 index 0000000000..3c40031537 --- /dev/null +++ b/node/rpcstack.go @@ -0,0 +1,157 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node + +import ( + "compress/gzip" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/rs/cors" +) + +// NewHTTPHandlerStack returns wrapped http-related handlers +func NewHTTPHandlerStack(srv *rpc.Server, cors []string, vhosts []string) http.Handler { + // Wrap the CORS-handler within a host-handler + handler := newCorsHandler(srv, cors) + handler = newVHostHandler(vhosts, handler) + return newGzipHandler(handler) +} + +func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { + // disable CORS support if user has not specified a custom CORS configuration + if len(allowedOrigins) == 0 { + return srv + } + c := cors.New(cors.Options{ + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{http.MethodPost, http.MethodGet}, + MaxAge: 600, + AllowedHeaders: []string{"*"}, + }) + return c.Handler(srv) +} + +// virtualHostHandler is a handler which validates the Host-header of incoming requests. +// The virtualHostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, +// since they do in-domain requests against the RPC api. Instead, we can see on the Host-header +// which domain was used, and validate that against a whitelist. +type virtualHostHandler struct { + vhosts map[string]struct{} + next http.Handler +} + +func newVHostHandler(vhosts []string, next http.Handler) http.Handler { + vhostMap := make(map[string]struct{}) + for _, allowedHost := range vhosts { + vhostMap[strings.ToLower(allowedHost)] = struct{}{} + } + return &virtualHostHandler{vhostMap, next} +} + +// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler +func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // if r.Host is not set, we can continue serving since a browser would set the Host header + if r.Host == "" { + h.next.ServeHTTP(w, r) + return + } + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + // Either invalid (too many colons) or no port specified + host = r.Host + } + if ipAddr := net.ParseIP(host); ipAddr != nil { + // It's an IP address, we can serve that + h.next.ServeHTTP(w, r) + return + + } + // Not an IP address, but a hostname. Need to validate + if _, exist := h.vhosts["*"]; exist { + h.next.ServeHTTP(w, r) + return + } + if _, exist := h.vhosts[host]; exist { + h.next.ServeHTTP(w, r) + return + } + http.Error(w, "invalid host specified", http.StatusForbidden) +} + +var gzPool = sync.Pool{ + New: func() interface{} { + w := gzip.NewWriter(ioutil.Discard) + return w + }, +} + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (w *gzipResponseWriter) WriteHeader(status int) { + w.Header().Del("Content-Length") + w.ResponseWriter.WriteHeader(status) +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func newGzipHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + next.ServeHTTP(w, r) + return + } + + w.Header().Set("Content-Encoding", "gzip") + + gz := gzPool.Get().(*gzip.Writer) + defer gzPool.Put(gz) + + gz.Reset(w) + defer gz.Close() + + next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) + }) +} + +func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if isWebsocket(r) { + ws.ServeHTTP(w, r) + log.Debug("serving websocket request") + return + } + + h.ServeHTTP(w, r) + }) +} + +func isWebsocket(r *http.Request) bool { + return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && + strings.ToLower(r.Header.Get("Connection")) == "upgrade" +} diff --git a/rpc/gzip.go b/rpc/gzip.go index a14fd09d54..4b1f838192 100644 --- a/rpc/gzip.go +++ b/rpc/gzip.go @@ -16,51 +16,42 @@ package rpc -import ( - "compress/gzip" - "io" - "io/ioutil" - "net/http" - "strings" - "sync" -) - -var gzPool = sync.Pool{ - New: func() interface{} { - w := gzip.NewWriter(ioutil.Discard) - return w - }, -} - -type gzipResponseWriter struct { - io.Writer - http.ResponseWriter -} - -func (w *gzipResponseWriter) WriteHeader(status int) { - w.Header().Del("Content-Length") - w.ResponseWriter.WriteHeader(status) -} - -func (w *gzipResponseWriter) Write(b []byte) (int, error) { - return w.Writer.Write(b) -} - -func newGzipHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { - next.ServeHTTP(w, r) - return - } - - w.Header().Set("Content-Encoding", "gzip") - - gz := gzPool.Get().(*gzip.Writer) - defer gzPool.Put(gz) - - gz.Reset(w) - defer gz.Close() - - next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) - }) -} +//var gzPool = sync.Pool{ +// New: func() interface{} { +// w := gzip.NewWriter(ioutil.Discard) +// return w +// }, +//} +// +//type gzipResponseWriter struct { +// io.Writer +// http.ResponseWriter +//} +// +//func (w *gzipResponseWriter) WriteHeader(status int) { +// w.Header().Del("Content-Length") +// w.ResponseWriter.WriteHeader(status) +//} +// +//func (w *gzipResponseWriter) Write(b []byte) (int, error) { +// return w.Writer.Write(b) +//} +// +//func newGzipHandler(next http.Handler) http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { +// next.ServeHTTP(w, r) +// return +// } +// +// w.Header().Set("Content-Encoding", "gzip") +// +// gz := gzPool.Get().(*gzip.Writer) +// defer gzPool.Put(gz) +// +// gz.Reset(w) +// defer gz.Close() +// +// next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) +// }) +//} diff --git a/rpc/http.go b/rpc/http.go index 5ac3c689cf..436d7c309a 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -25,14 +25,9 @@ import ( "io" "io/ioutil" "mime" - "net" "net/http" - "strings" "sync" "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/rs/cors" ) const ( @@ -209,38 +204,6 @@ func (t *httpServerConn) RemoteAddr() string { // SetWriteDeadline does nothing and always returns nil. func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } -// NewHTTPServer creates a new HTTP RPC server around an API provider. -// -// Deprecated: Server implements http.Handler -func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv http.Handler, ws http.Handler) *http.Server { - // Wrap the CORS-handler within a host-handler - handler := newCorsHandler(srv, cors) - handler = newVHostHandler(vhosts, handler) - handler = newGzipHandler(handler) - handler = NewWebsocketUpgradeHandler(handler, ws) - - // Make sure timeout values are meaningful - if timeouts.ReadTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", DefaultHTTPTimeouts.ReadTimeout) - timeouts.ReadTimeout = DefaultHTTPTimeouts.ReadTimeout - } - if timeouts.WriteTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", DefaultHTTPTimeouts.WriteTimeout) - timeouts.WriteTimeout = DefaultHTTPTimeouts.WriteTimeout - } - if timeouts.IdleTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", DefaultHTTPTimeouts.IdleTimeout) - timeouts.IdleTimeout = DefaultHTTPTimeouts.IdleTimeout - } - // Bundle and start the HTTP server - return &http.Server{ - Handler: handler, - ReadTimeout: timeouts.ReadTimeout, - WriteTimeout: timeouts.WriteTimeout, - IdleTimeout: timeouts.IdleTimeout, - } -} - // ServeHTTP serves JSON-RPC requests over HTTP. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Permit dumb empty requests for remote health-checks (AWS) @@ -297,89 +260,3 @@ func validateRequest(r *http.Request) (int, error) { err := fmt.Errorf("invalid content type, only %s is supported", contentType) return http.StatusUnsupportedMediaType, err } - -// NewHTTPHandlerStack returns wrapped http-related handlers -func NewHTTPHandlerStack(srv *Server, cors []string, vhosts []string) http.Handler { - // Wrap the CORS-handler within a host-handler - handler := newCorsHandler(srv, cors) - handler = newVHostHandler(vhosts, handler) - return newGzipHandler(handler) -} - -func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { - // disable CORS support if user has not specified a custom CORS configuration - if len(allowedOrigins) == 0 { - return srv - } - c := cors.New(cors.Options{ - AllowedOrigins: allowedOrigins, - AllowedMethods: []string{http.MethodPost, http.MethodGet}, - MaxAge: 600, - AllowedHeaders: []string{"*"}, - }) - return c.Handler(srv) -} - -// virtualHostHandler is a handler which validates the Host-header of incoming requests. -// The virtualHostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, -// since they do in-domain requests against the RPC api. Instead, we can see on the Host-header -// which domain was used, and validate that against a whitelist. -type virtualHostHandler struct { - vhosts map[string]struct{} - next http.Handler -} - -// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler -func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // if r.Host is not set, we can continue serving since a browser would set the Host header - if r.Host == "" { - h.next.ServeHTTP(w, r) - return - } - host, _, err := net.SplitHostPort(r.Host) - if err != nil { - // Either invalid (too many colons) or no port specified - host = r.Host - } - if ipAddr := net.ParseIP(host); ipAddr != nil { - // It's an IP address, we can serve that - h.next.ServeHTTP(w, r) - return - - } - // Not an IP address, but a hostname. Need to validate - if _, exist := h.vhosts["*"]; exist { - h.next.ServeHTTP(w, r) - return - } - if _, exist := h.vhosts[host]; exist { - h.next.ServeHTTP(w, r) - return - } - http.Error(w, "invalid host specified", http.StatusForbidden) -} - -func newVHostHandler(vhosts []string, next http.Handler) http.Handler { - vhostMap := make(map[string]struct{}) - for _, allowedHost := range vhosts { - vhostMap[strings.ToLower(allowedHost)] = struct{}{} - } - return &virtualHostHandler{vhostMap, next} -} - -func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if isWebsocket(r) { - ws.ServeHTTP(w, r) - log.Debug("serving websocket request") - return - } - - h.ServeHTTP(w, r) - }) -} - -func isWebsocket(r *http.Request) bool { - return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && - strings.ToLower(r.Header.Get("Connection")) == "upgrade" -}