Skip to content

Commit

Permalink
feat(dot/rpc) Protect unsafe RPC methods (ChainSafe#1723)
Browse files Browse the repository at this point in the history
* chore: remove the need of unsub func

* chore: create function to expose unsafe ws methods

* feat: protect unsafe rpc methods

* chore: update test names

* chore: fix some lint warnings

* chore: resolve CI warnings on the code

* chore: use PostRequest function

* chore: remove unsafe listeners

* chore: change to pointer

* chore: change to pointer

* chore: resolve deepsource warning

* fix: update RPC calls when is not a subscription method

* chore: remove unecessary conditional branch

* chore: enable unsafe rpc to tests

* chore: stopping svc on tests

* chore: resolve failing tests

* chore: use fmt.Sprintf

* chore: remove unecessary log

* chore: remove hardcoded port value

* chore: change test port

* chore: remove unused funciton

* chore: reverting the test case

* chore: send ws message when error occurs

* chore: remove tests.yml workflow
  • Loading branch information
EclesioMeloJunior authored and timwu20 committed Dec 6, 2021
1 parent 56b25f8 commit 10ed3de
Show file tree
Hide file tree
Showing 18 changed files with 502 additions and 175 deletions.
4 changes: 4 additions & 0 deletions cmd/gossamer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,12 +686,16 @@ func setDotNetworkConfig(ctx *cli.Context, tomlCfg ctoml.NetworkConfig, cfg *dot
func setDotRPCConfig(ctx *cli.Context, tomlCfg ctoml.RPCConfig, cfg *dot.RPCConfig) {
cfg.Enabled = tomlCfg.Enabled
cfg.External = tomlCfg.External
cfg.Unsafe = tomlCfg.Unsafe
cfg.UnsafeExternal = tomlCfg.UnsafeExternal
cfg.Port = tomlCfg.Port
cfg.Host = tomlCfg.Host
cfg.Modules = tomlCfg.Modules
cfg.WSPort = tomlCfg.WSPort
cfg.WS = tomlCfg.WS
cfg.WSExternal = tomlCfg.WSExternal
cfg.WSUnsafe = tomlCfg.WSUnsafe
cfg.WSUnsafeExternal = tomlCfg.WSUnsafeExternal

// check --rpc flag and update node configuration
if enabled := ctx.GlobalBool(RPCEnabledFlag.Name); enabled || cfg.Enabled {
Expand Down
24 changes: 24 additions & 0 deletions cmd/gossamer/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,16 @@ var (
Name: "rpc-external",
Usage: "Enable external HTTP-RPC connections",
}
// RPCEnabledFlag Enable the HTTP-RPC
RPCUnsafeEnabledFlag = cli.BoolFlag{
Name: "rpc-unsafe",
Usage: "Enable the HTTP-RPC server to unsafe procedures",
}
// RPCExternalFlag Enable the external HTTP-RPC
RPCUnsafeExternalFlag = cli.BoolFlag{
Name: "rpc-unsafe-external",
Usage: "Enable external HTTP-RPC connections to unsafe procedures",
}
// RPCHostFlag HTTP-RPC server listening hostname
RPCHostFlag = cli.StringFlag{
Name: "rpchost",
Expand Down Expand Up @@ -219,6 +229,16 @@ var (
Name: "ws-external",
Usage: "Enable external websocket connections",
}
// WSFlag Enable the websockets server
WSUnsafeFlag = cli.BoolFlag{
Name: "ws-unsafe",
Usage: "Enable access to websocket unsafe calls",
}
// WSExternalFlag Enable external websocket connections
WSUnsafeExternalFlag = cli.BoolFlag{
Name: "ws-unsafe-external",
Usage: "Enable external access to websocket unsafe calls",
}
)

// Account management flags
Expand Down Expand Up @@ -329,11 +349,15 @@ var (
// rpc flags
RPCEnabledFlag,
RPCExternalFlag,
RPCUnsafeEnabledFlag,
RPCUnsafeExternalFlag,
RPCHostFlag,
RPCPortFlag,
RPCModulesFlag,
WSFlag,
WSExternalFlag,
WSUnsafeFlag,
WSUnsafeExternalFlag,
WSPortFlag,

// metrics flag
Expand Down
28 changes: 20 additions & 8 deletions dot/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,26 @@ type CoreConfig struct {

// RPCConfig is to marshal/unmarshal toml RPC config vars
type RPCConfig struct {
Enabled bool
External bool
Port uint32
Host string
Modules []string
WSPort uint32
WS bool
WSExternal bool
Enabled bool
External bool
Unsafe bool
UnsafeExternal bool
Port uint32
Host string
Modules []string
WSPort uint32
WS bool
WSExternal bool
WSUnsafe bool
WSUnsafeExternal bool
}

func (r *RPCConfig) isRPCEnabled() bool {
return r.Enabled || r.External || r.Unsafe || r.UnsafeExternal
}

func (r *RPCConfig) isWSEnabled() bool {
return r.WS || r.WSExternal || r.WSUnsafe || r.WSUnsafeExternal
}

// StateConfig is the config for the State service
Expand Down
20 changes: 12 additions & 8 deletions dot/config/toml/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,16 @@ type CoreConfig struct {

// RPCConfig is to marshal/unmarshal toml RPC config vars
type RPCConfig struct {
Enabled bool `toml:"enabled,omitempty"`
External bool `toml:"external,omitempty"`
Port uint32 `toml:"port,omitempty"`
Host string `toml:"host,omitempty"`
Modules []string `toml:"modules,omitempty"`
WSPort uint32 `toml:"ws-port,omitempty"`
WS bool `toml:"ws,omitempty"`
WSExternal bool `toml:"ws-external,omitempty"`
Enabled bool `toml:"enabled,omitempty"`
Unsafe bool `toml:"unsafe,omitempty"`
UnsafeExternal bool `toml:"unsafe-external,omitempty"`
External bool `toml:"external,omitempty"`
Port uint32 `toml:"port,omitempty"`
Host string `toml:"host,omitempty"`
Modules []string `toml:"modules,omitempty"`
WSPort uint32 `toml:"ws-port,omitempty"`
WS bool `toml:"ws,omitempty"`
WSExternal bool `toml:"ws-external,omitempty"`
WSUnsafe bool `toml:"ws-unsafe,omitempty"`
WSUnsafeExternal bool `toml:"ws-unsafe-external,omitempty"`
}
7 changes: 2 additions & 5 deletions dot/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,7 @@ func InitNode(cfg *Config) error {
config := state.Config{
Path: cfg.Global.BasePath,
LogLevel: cfg.Global.LogLvl,
PrunerCfg: struct {
Mode pruner.Mode
RetainedBlocks int64
}{
PrunerCfg: pruner.Config{
Mode: cfg.Global.Pruning,
RetainedBlocks: cfg.Global.RetainBlocks,
},
Expand Down Expand Up @@ -310,7 +307,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
nodeSrvcs = append(nodeSrvcs, sysSrvc)

// check if rpc service is enabled
if enabled := cfg.RPC.Enabled || cfg.RPC.WS; enabled {
if enabled := cfg.RPC.isRPCEnabled() || cfg.RPC.isWSEnabled(); enabled {
rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, sysSrvc, fg)
nodeSrvcs = append(nodeSrvcs, rpcSrvc)
} else {
Expand Down
44 changes: 44 additions & 0 deletions dot/rpc/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ package rpc

import (
"errors"
"fmt"
"net"
"strings"

"github.com/ChainSafe/gossamer/dot/rpc/modules"
"github.com/go-playground/validator/v10"
"github.com/gorilla/rpc/v2"
"github.com/jpillora/ipfilter"
)
Expand All @@ -35,6 +39,7 @@ func LocalhostFilter() *ipfilter.IPFilter {
// LocalRequestOnly HTTP handler to restrict to only local connections
func LocalRequestOnly(r *rpc.RequestInfo, i interface{}) error {
ip, _, err := net.SplitHostPort(r.Request.RemoteAddr)

if err != nil {
return errors.New("unable to parse IP")
}
Expand All @@ -44,3 +49,42 @@ func LocalRequestOnly(r *rpc.RequestInfo, i interface{}) error {
}
return errors.New("external HTTP request refused")
}

func snakeCaseFormat(method string) (string, error) {
parts := strings.Split(method, ".")
if len(parts) < 2 {
return "", fmt.Errorf("invalid rpc method format %s, should be 'module.FunctionName'", method)
}

service, funcName := parts[0], parts[1]
funcName = strings.ToLower(string(funcName[0])) + funcName[1:]
return strings.Join([]string{service, funcName}, "_"), nil
}

func rpcValidator(cfg *HTTPServerConfig, validate *validator.Validate) func(r *rpc.RequestInfo, i interface{}) error {
return func(r *rpc.RequestInfo, v interface{}) error {
var (
err error
rpcmethod string
)

if rpcmethod, err = snakeCaseFormat(r.Method); err != nil {
return err
}

isUnsafe := modules.IsUnsafe(rpcmethod)
if isUnsafe && !cfg.rpcUnsafeEnabled() {
return fmt.Errorf("unsafe rpc method %s cannot be reachable", rpcmethod)
}

if err = validate.Struct(v); err != nil {
return err
}

if !cfg.exposeRPC() || modules.IsUnsafe(rpcmethod) && !cfg.RPCUnsafeExternal {
return LocalRequestOnly(r, v)
}

return nil
}
}
40 changes: 26 additions & 14 deletions dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,36 @@ type HTTPServerConfig struct {
TransactionQueueAPI modules.TransactionStateAPI
RPCAPI modules.RPCAPI
SystemAPI modules.SystemAPI
External bool
RPC bool
RPCExternal bool
RPCUnsafe bool
RPCUnsafeExternal bool
Host string
RPCPort uint32
WS bool
WSExternal bool
WSUnsafe bool
WSUnsafeExternal bool
WSPort uint32
Modules []string
}

func (h *HTTPServerConfig) rpcUnsafeEnabled() bool {
return h.RPCUnsafe || h.RPCUnsafeExternal
}

func (h *HTTPServerConfig) wsUnsafeEnabled() bool {
return h.WSUnsafe || h.WSUnsafeExternal
}

func (h *HTTPServerConfig) exposeWS() bool {
return h.WSExternal || h.WSUnsafeExternal
}

func (h *HTTPServerConfig) exposeRPC() bool {
return h.RPCExternal || h.RPCUnsafeExternal
}

var logger log.Logger

// NewHTTPServer creates a new http server and registers an associated rpc server
Expand All @@ -78,10 +99,6 @@ func NewHTTPServer(cfg *HTTPServerConfig) *HTTPServer {
}

server.RegisterModules(cfg.Modules)
if !cfg.External {
server.rpcServer.RegisterValidateRequestFunc(LocalRequestOnly)
}

return server
}

Expand Down Expand Up @@ -138,15 +155,8 @@ func (h *HTTPServer) Start() error {
// Add custom validator for `common.Hash`
validate.RegisterCustomTypeFunc(common.HashValidator, common.Hash{})

validateHandler := func(r *rpc.RequestInfo, v interface{}) error {
err := validate.Struct(v)
if err != nil {
return err
}
return nil
}
h.rpcServer.RegisterValidateRequestFunc(rpcValidator(h.serverConfig, validate))

h.rpcServer.RegisterValidateRequestFunc(validateHandler)
go func() {
err := http.ListenAndServe(fmt.Sprintf(":%d", h.serverConfig.RPCPort), r)
if err != nil {
Expand Down Expand Up @@ -199,7 +209,7 @@ func (h *HTTPServer) Stop() error {
func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var upg = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
if !h.serverConfig.WSExternal {
if !h.serverConfig.exposeWS() {
ip, _, error := net.SplitHostPort(r.RemoteAddr)
if error != nil {
logger.Error("unable to parse IP", "error")
Expand All @@ -214,6 +224,7 @@ func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logger.Debug("external websocket request refused", "error")
return false
}

return true
},
}
Expand All @@ -233,6 +244,7 @@ func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// NewWSConn to create new WebSocket Connection struct
func NewWSConn(conn *websocket.Conn, cfg *HTTPServerConfig) *subscription.WSConn {
c := &subscription.WSConn{
UnsafeEnabled: cfg.wsUnsafeEnabled(),
Wsconn: conn,
Subscriptions: make(map[uint32]subscription.Listener),
StorageAPI: cfg.StorageAPI,
Expand Down
Loading

0 comments on commit 10ed3de

Please sign in to comment.