diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ea7d97c9d..cd7783355 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -22,6 +22,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES: +- [rpc] [\#2582](https://github.com/tendermint/tendermint/issues/2582) Enable CORS on RPC API + ### IMPROVEMENTS: ### BUG FIXES: diff --git a/Gopkg.lock b/Gopkg.lock index 229ed3f9c..8695205e7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -128,6 +128,14 @@ revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" version = "v1.2.0" +[[projects]] + digest = "1:b0c25f00bad20d783d259af2af8666969e2fc343fa0dc9efe52936bbd67fb758" + name = "github.com/rs/cors" + packages = ["."] + pruneopts = "UT" + revision = "9a47f48565a795472d43519dd49aac781f3034fb" + version = "v1.6.0" + [[projects]] digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8" name = "github.com/hashicorp/hcl" diff --git a/Gopkg.toml b/Gopkg.toml index a0ffb9205..1ab6a18e9 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,6 +40,10 @@ name = "github.com/gorilla/websocket" version = "=1.2.0" +[[constraint]] + name = "github.com/rs/cors" + version = "1.6.0" + [[constraint]] name = "github.com/pkg/errors" version = "=0.8.0" diff --git a/config/config.go b/config/config.go index fa6182114..ea6582c09 100644 --- a/config/config.go +++ b/config/config.go @@ -242,6 +242,18 @@ type RPCConfig struct { // TCP or UNIX socket address for the RPC server to listen on ListenAddress string `mapstructure:"laddr"` + // A list of origins a cross-domain request can be executed from. + // If the special '*' value is present in the list, all origins will be allowed. + // An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). + // Only one wildcard can be used per origin. + CORSAllowedOrigins []string `mapstructure:"cors_allowed_origins"` + + // A list of methods the client is allowed to use with cross-domain requests. + CORSAllowedMethods []string `mapstructure:"cors_allowed_methods"` + + // A list of non simple headers the client is allowed to use with cross-domain requests. + CORSAllowedHeaders []string `mapstructure:"cors_allowed_headers"` + // TCP or UNIX socket address for the gRPC server to listen on // NOTE: This server only supports /broadcast_tx_commit GRPCListenAddress string `mapstructure:"grpc_laddr"` @@ -269,8 +281,10 @@ type RPCConfig struct { // DefaultRPCConfig returns a default configuration for the RPC server func DefaultRPCConfig() *RPCConfig { return &RPCConfig{ - ListenAddress: "tcp://0.0.0.0:26657", - + ListenAddress: "tcp://0.0.0.0:26657", + CORSAllowedOrigins: []string{}, + CORSAllowedMethods: []string{"HEAD", "GET", "POST"}, + CORSAllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"}, GRPCListenAddress: "", GRPCMaxOpenConnections: 900, @@ -300,6 +314,11 @@ func (cfg *RPCConfig) ValidateBasic() error { return nil } +// IsCorsEnabled returns true if cross-origin resource sharing is enabled. +func (cfg *RPCConfig) IsCorsEnabled() bool { + return len(cfg.CORSAllowedOrigins) != 0 +} + //----------------------------------------------------------------------------- // P2PConfig diff --git a/config/toml.go b/config/toml.go index d73b9c81d..89be3783d 100644 --- a/config/toml.go +++ b/config/toml.go @@ -119,6 +119,17 @@ filter_peers = {{ .BaseConfig.FilterPeers }} # TCP or UNIX socket address for the RPC server to listen on laddr = "{{ .RPC.ListenAddress }}" +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = "{{ .RPC.CORSAllowedOrigins }}" + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = "{{ .RPC.CORSAllowedMethods }}" + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = "{{ .RPC.CORSAllowedHeaders }}" + # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit grpc_laddr = "{{ .RPC.GRPCListenAddress }}" diff --git a/node/node.go b/node/node.go index 0652a392f..796bbc2a8 100644 --- a/node/node.go +++ b/node/node.go @@ -13,8 +13,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/cors" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" @@ -651,9 +652,20 @@ func (n *Node) startRPC() ([]net.Listener, error) { wm.SetLogger(rpcLogger.With("protocol", "websocket")) mux.HandleFunc("/websocket", wm.WebsocketHandler) rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger) + + var rootHandler http.Handler = mux + if n.config.RPC.IsCorsEnabled() { + corsMiddleware := cors.New(cors.Options{ + AllowedOrigins: n.config.RPC.CORSAllowedOrigins, + AllowedMethods: n.config.RPC.CORSAllowedMethods, + AllowedHeaders: n.config.RPC.CORSAllowedHeaders, + }) + rootHandler = corsMiddleware.Handler(mux) + } + listener, err := rpcserver.StartHTTPServer( listenAddr, - mux, + rootHandler, rpcLogger, rpcserver.Config{MaxOpenConnections: n.config.RPC.MaxOpenConnections}, ) diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 602525b51..217971fda 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -2,6 +2,7 @@ package client_test import ( "fmt" + "net/http" "strings" "testing" @@ -11,7 +12,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/rpc/client" - rpctest "github.com/tendermint/tendermint/rpc/test" + "github.com/tendermint/tendermint/rpc/test" "github.com/tendermint/tendermint/types" ) @@ -32,6 +33,21 @@ func GetClients() []client.Client { } } +func TestCorsEnabled(t *testing.T) { + origin := rpctest.GetConfig().RPC.CORSAllowedOrigins[0] + remote := strings.Replace(rpctest.GetConfig().RPC.ListenAddress, "tcp", "http", -1) + + req, err := http.NewRequest("GET", remote, nil) + require.Nil(t, err, "%+v", err) + req.Header.Set("Origin", origin) + c := &http.Client{} + resp, err := c.Do(req) + defer resp.Body.Close() + + require.Nil(t, err, "%+v", err) + assert.Equal(t, resp.Header.Get("Access-Control-Allow-Origin"), origin) +} + // Make sure status is correct (we connect properly) func TestStatus(t *testing.T) { for i, c := range GetClients() { diff --git a/rpc/core/doc.go b/rpc/core/doc.go index 5378dde24..ec79c8e17 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -12,7 +12,10 @@ See it here: https://github.com/tendermint/tendermint/tree/master/rpc/lib ## Configuration -Set the `laddr` config parameter under `[rpc]` table in the `$TMHOME/config/config.toml` file or the `--rpc.laddr` command-line flag to the desired protocol://host:port setting. Default: `tcp://0.0.0.0:26657`. +RPC can be configured by tuning parameters under `[rpc]` table in the `$TMHOME/config/config.toml` file or by using the `--rpc.X` command-line flags. + +Default rpc listen address is `tcp://0.0.0.0:26657`. To set another address, set the `laddr` config parameter to desired value. +CORS (Cross-Origin Resource Sharing) can be enabled by setting `cors_allowed_origins`, `cors_allowed_methods`, `cors_allowed_headers` config parameters. ## Arguments diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 6de376c29..8cacaeefb 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -151,11 +151,6 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler rww := &ResponseWriterWrapper{-1, w} begin := time.Now() - // Common headers - origin := r.Header.Get("Origin") - rww.Header().Set("Access-Control-Allow-Origin", origin) - rww.Header().Set("Access-Control-Allow-Credentials", "true") - rww.Header().Set("Access-Control-Expose-Headers", "X-Server-Time") rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix())) defer func() { diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 0a9cd9847..e68ec1490 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -84,6 +84,7 @@ func GetConfig() *cfg.Config { tm, rpc, grpc := makeAddrs() globalConfig.P2P.ListenAddress = tm globalConfig.RPC.ListenAddress = rpc + globalConfig.RPC.CORSAllowedOrigins = []string{"https://tendermint.com/"} globalConfig.RPC.GRPCListenAddress = grpc globalConfig.TxIndex.IndexTags = "app.creator,tx.height" // see kvstore application }