From b46dd295bc6e237aad50c5b88906eacff0d5211f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 1 Nov 2019 13:23:46 +0300 Subject: [PATCH] rpc: add CORS workaround for RPC This one enables our RPC to be called from the browser if there is a need. It's insecure and not standards-compliant, thus this behaviour is configurable is not enabled by default. It makes our node with this workaround enabled compatible with neo-mon monitoring. Originally debugged by @anatoly-bogatyrev in #464. --- cli/server/server.go | 2 +- config/config.go | 10 +++++++++- config/protocol.mainnet.yml | 5 ++++- config/protocol.privnet.docker.four.yml | 5 ++++- config/protocol.privnet.docker.one.yml | 5 ++++- config/protocol.privnet.docker.three.yml | 5 ++++- config/protocol.privnet.docker.two.yml | 5 ++++- config/protocol.privnet.yml | 5 ++++- config/protocol.testnet.yml | 5 ++++- config/protocol.unit_testnet.yml | 5 ++++- go.mod | 2 ++ pkg/rpc/request.go | 19 +++++++++++++------ pkg/rpc/server.go | 19 ++++++++++++++----- pkg/rpc/server_helper_test.go | 2 +- 14 files changed, 72 insertions(+), 22 deletions(-) diff --git a/cli/server/server.go b/cli/server/server.go index 95f167eac..9aacdca66 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -247,7 +247,7 @@ func startServer(ctx *cli.Context) error { } server := network.NewServer(serverConfig, chain) - rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server) + rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPC, server) errChan := make(chan error) monitoring := metrics.NewMetricsService(cfg.ApplicationConfiguration.Monitoring) diff --git a/config/config.go b/config/config.go index e348bcf10..2b3b7f1cc 100644 --- a/config/config.go +++ b/config/config.go @@ -67,7 +67,6 @@ type ( // ApplicationConfiguration config specific to the node. ApplicationConfiguration struct { DBConfiguration storage.DBConfiguration `yaml:"DBConfiguration"` - RPCPort uint16 `yaml:"RPCPort"` NodePort uint16 `yaml:"NodePort"` Relay bool `yaml:"Relay"` DialTimeout time.Duration `yaml:"DialTimeout"` @@ -75,6 +74,15 @@ type ( MaxPeers int `yaml:"MaxPeers"` MinPeers int `yaml:"MinPeers"` Monitoring metrics.PrometheusConfig `yaml:"Monitoring"` + RPC RPCConfig `yaml:"RPC"` + } + + // RPCConfig is an RPC service configuration information (to be moved to the rpc package, see #423). + RPCConfig struct { + Enabled bool `yaml:"Enabled"` + EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"` + Address string `yaml:"Address"` + Port uint16 `yaml:"Port"` } // NetMode describes the mode the blockchain will operate on. diff --git a/config/protocol.mainnet.yml b/config/protocol.mainnet.yml index b20b5a1b2..655b7f0ef 100644 --- a/config/protocol.mainnet.yml +++ b/config/protocol.mainnet.yml @@ -42,13 +42,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/mainnet.bolt" - RPCPort: 20332 NodePort: 20333 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 5 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20332 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.privnet.docker.four.yml b/config/protocol.privnet.docker.four.yml index c92117da0..18b05e8c8 100644 --- a/config/protocol.privnet.docker.four.yml +++ b/config/protocol.privnet.docker.four.yml @@ -30,13 +30,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - RPCPort: 20336 NodePort: 20337 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 3 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20336 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.privnet.docker.one.yml b/config/protocol.privnet.docker.one.yml index 7c8f94d1d..2d628984e 100644 --- a/config/protocol.privnet.docker.one.yml +++ b/config/protocol.privnet.docker.one.yml @@ -27,13 +27,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - RPCPort: 20333 NodePort: 20334 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 3 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20333 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.privnet.docker.three.yml b/config/protocol.privnet.docker.three.yml index db6ac5436..6894ba952 100644 --- a/config/protocol.privnet.docker.three.yml +++ b/config/protocol.privnet.docker.three.yml @@ -27,13 +27,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - RPCPort: 20335 NodePort: 20336 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 3 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20335 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.privnet.docker.two.yml b/config/protocol.privnet.docker.two.yml index 85d07045d..e58dc0c00 100644 --- a/config/protocol.privnet.docker.two.yml +++ b/config/protocol.privnet.docker.two.yml @@ -27,13 +27,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - RPCPort: 20334 NodePort: 20335 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 3 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20334 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.privnet.yml b/config/protocol.privnet.yml index ac804131b..9b039d375 100644 --- a/config/protocol.privnet.yml +++ b/config/protocol.privnet.yml @@ -33,13 +33,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - RPCPort: 20331 NodePort: 20332 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 3 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20331 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.testnet.yml b/config/protocol.testnet.yml index 3663565da..44fa60e20 100644 --- a/config/protocol.testnet.yml +++ b/config/protocol.testnet.yml @@ -42,13 +42,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/testnet.bolt" - RPCPort: 20332 NodePort: 20333 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 5 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20332 Monitoring: Enabled: true Port: 2112 diff --git a/config/protocol.unit_testnet.yml b/config/protocol.unit_testnet.yml index eff0453fe..18c50732f 100644 --- a/config/protocol.unit_testnet.yml +++ b/config/protocol.unit_testnet.yml @@ -32,13 +32,16 @@ ApplicationConfiguration: # DB: 0 # BoltDBOptions: # FilePath: "./chains/unit_testnet.bolt" - RPCPort: 20332 NodePort: 20333 Relay: true DialTimeout: 3 ProtoTickInterval: 2 MaxPeers: 50 MinPeers: 1 + RPC: + Enabled: true + EnableCORSWorkaround: false + Port: 20332 Monitoring: Enabled: false #since it's not useful for unit tests. Port: 2112 diff --git a/go.mod b/go.mod index 668cc6bc4..6b4ad7c72 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,8 @@ require ( github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-isatty v0.0.9 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/mr-tron/base58 v1.1.2 github.com/nspcc-dev/rfc6979 v0.1.0 github.com/onsi/gomega v1.4.2 // indirect diff --git a/pkg/rpc/request.go b/pkg/rpc/request.go index 342baf3ba..ddd2bb816 100644 --- a/pkg/rpc/request.go +++ b/pkg/rpc/request.go @@ -17,10 +17,11 @@ type ( // Request represents a standard JSON-RPC 2.0 // request: http://www.jsonrpc.org/specification#request_object. Request struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - RawParams json.RawMessage `json:"params,omitempty"` - RawID json.RawMessage `json:"id,omitempty"` + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + RawParams json.RawMessage `json:"params,omitempty"` + RawID json.RawMessage `json:"id,omitempty"` + enableCORSWorkaround bool } // Response represents a standard JSON-RPC 2.0 @@ -34,9 +35,10 @@ type ( ) // NewRequest creates a new Request struct. -func NewRequest() *Request { +func NewRequest(corsWorkaround bool) *Request { return &Request{ - JSONRPC: jsonRPCVersion, + JSONRPC: jsonRPCVersion, + enableCORSWorkaround: corsWorkaround, } } @@ -110,6 +112,11 @@ func (r Request) WriteResponse(w http.ResponseWriter, result interface{}) { func (r Request) writeServerResponse(w http.ResponseWriter, response Response) { w.Header().Set("Content-Type", "application/json; charset=utf-8") + if r.enableCORSWorkaround { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") + } + encoder := json.NewEncoder(w) err := encoder.Encode(response) diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 1ea607d6e..a767663f5 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -7,6 +7,7 @@ import ( "net/http" "strconv" + "github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto" @@ -24,6 +25,7 @@ type ( Server struct { *http.Server chain core.Blockchainer + config config.RPCConfig coreServer *network.Server } ) @@ -35,12 +37,15 @@ var ( ) // NewServer creates a new Server struct. -func NewServer(chain core.Blockchainer, port uint16, coreServer *network.Server) Server { +func NewServer(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server) Server { + httpServer := &http.Server{ + Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10), + } + return Server{ - Server: &http.Server{ - Addr: ":" + strconv.FormatUint(uint64(port), 10), - }, + Server: httpServer, chain: chain, + config: conf, coreServer: coreServer, } } @@ -48,6 +53,10 @@ func NewServer(chain core.Blockchainer, port uint16, coreServer *network.Server) // Start creates a new JSON-RPC server // listening on the configured port. func (s *Server) Start(errChan chan error) { + if !s.config.Enabled { + log.Info("RPC server is not enabled") + return + } s.Handler = http.HandlerFunc(s.requestHandler) log.WithFields(log.Fields{ "endpoint": s.Addr, @@ -66,7 +75,7 @@ func (s *Server) Shutdown() error { } func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) { - req := NewRequest() + req := NewRequest(s.config.EnableCORSWorkaround) if httpRequest.Method != "POST" { req.WriteErrorResponse( diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index 603564d78..42d02d544 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -167,7 +167,7 @@ func initServerWithInMemoryChain(ctx context.Context, t *testing.T) (*core.Block serverConfig := network.NewServerConfig(cfg) server := network.NewServer(serverConfig, chain) - rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server) + rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPC, server) handler := http.HandlerFunc(rpcServer.requestHandler) return chain, handler