mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 17:33:47 +00:00
http, rpc, utils: make vhosts into map, address review concerns
This commit is contained in:
parent
543df3f69c
commit
dbf5bb1f4c
5 changed files with 36 additions and 33 deletions
|
|
@ -385,7 +385,7 @@ var (
|
||||||
}
|
}
|
||||||
RPCVirtualHostsFlag = cli.StringFlag{
|
RPCVirtualHostsFlag = cli.StringFlag{
|
||||||
Name: "rpcvhosts",
|
Name: "rpcvhosts",
|
||||||
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Set to * to disable this protection.",
|
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
|
||||||
Value: "localhost",
|
Value: "localhost",
|
||||||
}
|
}
|
||||||
RPCApiFlag = cli.StringFlag{
|
RPCApiFlag = cli.StringFlag{
|
||||||
|
|
@ -682,7 +682,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
|
||||||
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name))
|
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.HTTPVirtualHostnames = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name))
|
cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// setWS creates the WebSocket RPC listener interface string from the set
|
// setWS creates the WebSocket RPC listener interface string from the set
|
||||||
|
|
|
||||||
14
node/api.go
14
node/api.go
|
|
@ -114,7 +114,7 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartRPC starts the HTTP RPC API server.
|
// StartRPC starts the HTTP RPC API server.
|
||||||
func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, hosts *string) (bool, error) {
|
func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
|
||||||
api.node.lock.Lock()
|
api.node.lock.Lock()
|
||||||
defer api.node.lock.Unlock()
|
defer api.node.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -141,11 +141,11 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedHosts := api.node.config.HTTPVirtualHostnames
|
allowedVHosts := api.node.config.HTTPVirtualHosts
|
||||||
if hosts != nil {
|
if vhosts != nil {
|
||||||
allowedHosts = nil
|
allowedVHosts = nil
|
||||||
for _, host := range strings.Split(*host, ",") {
|
for _, vhost := range strings.Split(*host, ",") {
|
||||||
allowedHosts = append(allowedHosts, strings.TrimSpace(host))
|
allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +157,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedHosts); err != nil {
|
if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
||||||
|
|
@ -105,14 +105,14 @@ type Config struct {
|
||||||
// useless for custom HTTP clients.
|
// useless for custom HTTP clients.
|
||||||
HTTPCors []string `toml:",omitempty"`
|
HTTPCors []string `toml:",omitempty"`
|
||||||
|
|
||||||
// HTTPVirtualHostnames is the list of virtual hostnames which are allowed on incoming requests.
|
// HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
|
||||||
// This is by default {'localhost'}. Using this prevents attacks like
|
// This is by default {'localhost'}. Using this prevents attacks like
|
||||||
// DNS rebinding, which bypasses SOP by simply masquerading as being within the same
|
// DNS rebinding, which bypasses SOP by simply masquerading as being within the same
|
||||||
// origin. These attacks do not utilize CORS, since they are not cross-domain.
|
// origin. These attacks do not utilize CORS, since they are not cross-domain.
|
||||||
// By explicitly checking the Host-header, the server will not allow requests
|
// By explicitly checking the Host-header, the server will not allow requests
|
||||||
// made against the server with a malicious host domain.
|
// made against the server with a malicious host domain.
|
||||||
// Requests using ip address directly are not affected
|
// Requests using ip address directly are not affected
|
||||||
HTTPVirtualHostnames []string `toml:",omitempty"`
|
HTTPVirtualHosts []string `toml:",omitempty"`
|
||||||
|
|
||||||
// HTTPModules is a list of API modules to expose via the HTTP RPC interface.
|
// HTTPModules is a list of API modules to expose via the HTTP RPC interface.
|
||||||
// If the module list is empty, all RPC API endpoints designated public will be
|
// If the module list is empty, all RPC API endpoints designated public will be
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHostnames); err != nil {
|
if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts); err != nil {
|
||||||
n.stopIPC()
|
n.stopIPC()
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
return err
|
||||||
|
|
@ -365,7 +365,7 @@ func (n *Node) stopIPC() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// startHTTP initializes and starts the HTTP RPC endpoint.
|
// startHTTP initializes and starts the HTTP RPC endpoint.
|
||||||
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, allowedHosts []string) error {
|
func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string) error {
|
||||||
// Short circuit if the HTTP endpoint isn't being exposed
|
// Short circuit if the HTTP endpoint isn't being exposed
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -393,9 +393,9 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors
|
||||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go rpc.NewHTTPServer(cors, allowedHosts, handler).Serve(listener)
|
go rpc.NewHTTPServer(cors, vhosts, handler).Serve(listener)
|
||||||
n.log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint))
|
n.log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint))
|
||||||
n.log.Info(fmt.Sprintf("HTTP config: cors %v, vhosts%v", cors, allowedHosts))
|
n.log.Info(fmt.Sprintf("HTTP config: cors %v, vhosts%v", cors, vhosts))
|
||||||
// All listeners booted successfully
|
// All listeners booted successfully
|
||||||
n.httpEndpoint = endpoint
|
n.httpEndpoint = endpoint
|
||||||
n.httpListener = listener
|
n.httpListener = listener
|
||||||
|
|
|
||||||
39
rpc/http.go
39
rpc/http.go
|
|
@ -143,10 +143,10 @@ func (t *httpReadWriteNopCloser) Close() error {
|
||||||
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
||||||
//
|
//
|
||||||
// Deprecated: Server implements http.Handler
|
// Deprecated: Server implements http.Handler
|
||||||
func NewHTTPServer(cors []string, hosts []string, srv *Server) *http.Server {
|
func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server {
|
||||||
// Wrap the CORS-handler within a host-handler
|
// Wrap the CORS-handler within a host-handler
|
||||||
handler := newCorsHandler(srv, cors)
|
handler := newCorsHandler(srv, cors)
|
||||||
handler = newHostHandler(hosts, handler)
|
handler = newVHostHandler(vhosts, handler)
|
||||||
return &http.Server{Handler: handler}
|
return &http.Server{Handler: handler}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,29 +193,26 @@ func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler {
|
||||||
if len(allowedOrigins) == 0 {
|
if len(allowedOrigins) == 0 {
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: allowedOrigins,
|
AllowedOrigins: allowedOrigins,
|
||||||
AllowedMethods: []string{http.MethodPost, http.MethodGet},
|
AllowedMethods: []string{http.MethodPost, http.MethodGet},
|
||||||
MaxAge: 600,
|
MaxAge: 600,
|
||||||
AllowedHeaders: []string{"*"},
|
AllowedHeaders: []string{"*"},
|
||||||
Debug: true,
|
|
||||||
})
|
})
|
||||||
return c.Handler(srv)
|
return c.Handler(srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hostHandler is a handler which validates the Host-header of incoming requests.
|
// virtalHostHandler is a handler which validates the Host-header of incoming requests.
|
||||||
// The hostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers,
|
// The virtalHostHandler 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
|
// 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.
|
// which domain was used, and validate that against a whitelist.
|
||||||
type hostHandler struct {
|
type virtalHostHandler struct {
|
||||||
AllowedHosts []string
|
vhosts map[string]struct{}
|
||||||
next http.Handler
|
next http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
|
// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
|
||||||
func (h *hostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *virtalHostHandler) 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 is not set, we can continue serving since a browser would set the Host header
|
||||||
if r.Host == "" {
|
if r.Host == "" {
|
||||||
h.next.ServeHTTP(w, r)
|
h.next.ServeHTTP(w, r)
|
||||||
|
|
@ -233,16 +230,22 @@ func (h *hostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
}
|
}
|
||||||
// Not an ip address, but a hostname. Need to validate
|
// Not an ip address, but a hostname. Need to validate
|
||||||
for _, allowedHost := range h.AllowedHosts {
|
if _, exist := h.vhosts["*"]; exist {
|
||||||
if strings.ToLower(allowedHost) == strings.ToLower(host) || allowedHost == "*" {
|
h.next.ServeHTTP(w, r)
|
||||||
h.next.ServeHTTP(w, r)
|
return
|
||||||
return
|
}
|
||||||
}
|
if _, exist := h.vhosts[host]; exist {
|
||||||
|
h.next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
http.Error(w, "invalid host specified", http.StatusForbidden)
|
http.Error(w, "invalid host specified", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostHandler(allowedHosts []string, next http.Handler) http.Handler {
|
func newVHostHandler(vhosts []string, next http.Handler) http.Handler {
|
||||||
return &hostHandler{allowedHosts, next}
|
vhostMap := make(map[string]struct{})
|
||||||
|
for _, allowedHost := range vhosts {
|
||||||
|
vhostMap[strings.ToLower(allowedHost)] = struct{}{}
|
||||||
|
}
|
||||||
|
return &virtalHostHandler{vhostMap, next}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue