From 05d9b290a79518dca3d47f5ffee73618e0b80acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= <31522760+fedekunze@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:33:54 +0200 Subject: [PATCH] rpc, server: add TLS certificate for websocket (#600) * rpc, server: add TLS certificate for websocket * changelog --- CHANGELOG.md | 1 + ethereum/rpc/websockets.go | 34 +++++++---- server/config/config.go | 117 +++++++++++++++++++++++++------------ server/config/toml.go | 12 ++++ server/flags/flags.go | 6 ++ server/json_rpc.go | 4 +- server/start.go | 3 + 7 files changed, 128 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb1d672..569ebd22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes +* (rpc, server) [tharsis#600](https://github.com/tharsis/ethermint/pull/600) Add TLS configuration for websocket API * (rpc) [tharsis#598](https://github.com/tharsis/ethermint/pull/598) Check truncation when creating a `BlockNumber` from `big.Int` * (evm) [tharsis#597](https://github.com/tharsis/ethermint/pull/597) Check for `uint64` -> `int64` block height overflow on `GetHashFn` * (evm) [tharsis#579](https://github.com/tharsis/ethermint/pull/579) Update `DeriveChainID` function to handle `v` signature values `< 35`. diff --git a/ethereum/rpc/websockets.go b/ethereum/rpc/websockets.go index 694afc0a..488a33cf 100644 --- a/ethereum/rpc/websockets.go +++ b/ethereum/rpc/websockets.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "math/big" + "net" "net/http" "sync" @@ -25,6 +26,7 @@ import ( rpcfilters "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth/filters" "github.com/tharsis/ethermint/ethereum/rpc/types" + "github.com/tharsis/ethermint/server/config" evmtypes "github.com/tharsis/ethermint/x/evm/types" ) @@ -61,19 +63,25 @@ type ErrorMessageJSON struct { } type websocketsServer struct { - rpcAddr string // listen address of rest-server - wsAddr string // listen address of ws server - api *pubSubAPI - logger log.Logger + rpcAddr string // listen address of rest-server + wsAddr string // listen address of ws server + certFile string + keyFile string + api *pubSubAPI + logger log.Logger } -func NewWebsocketsServer(logger log.Logger, tmWSClient *rpcclient.WSClient, rpcAddr, wsAddr string) WebsocketsServer { +func NewWebsocketsServer(logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer { logger = logger.With("api", "websocket-server") + _, port, _ := net.SplitHostPort(cfg.JSONRPC.Address) + return &websocketsServer{ - rpcAddr: rpcAddr, - wsAddr: wsAddr, - api: newPubSubAPI(logger, tmWSClient), - logger: logger, + rpcAddr: "localhost:" + port, // FIXME: this shouldn't be hardcoded to localhost + wsAddr: cfg.JSONRPC.WsAddress, + certFile: cfg.TLS.CertificatePath, + keyFile: cfg.TLS.KeyPath, + api: newPubSubAPI(logger, tmWSClient), + logger: logger, } } @@ -82,7 +90,13 @@ func (s *websocketsServer) Start() { ws.Handle("/", s) go func() { - err := http.ListenAndServe(s.wsAddr, ws) + var err error + if s.certFile == "" || s.keyFile == "" { + err = http.ListenAndServe(s.wsAddr, ws) + } else { + err = http.ListenAndServeTLS(s.wsAddr, s.certFile, s.keyFile, ws) + } + if err != nil { if err == http.ErrServerClosed { return diff --git a/server/config/config.go b/server/config/config.go index e453f9b1..110b2452 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "fmt" + "path" "github.com/spf13/viper" @@ -31,9 +32,43 @@ const ( var evmTracers = []string{DefaultEVMTracer, "markdown", "struct", "access_list"} -// GetDefaultAPINamespaces returns the default list of JSON-RPC namespaces that should be enabled -func GetDefaultAPINamespaces() []string { - return []string{"eth", "net", "web3"} +// Config defines the server's top level configuration. It includes the default app config +// from the SDK as well as the EVM configuration to enable the JSON-RPC APIs. +type Config struct { + config.Config + + EVM EVMConfig `mapstructure:"evm"` + JSONRPC JSONRPCConfig `mapstructure:"json-rpc"` + TLS TLSConfig `mapstructure:"tls"` +} + +// EVMConfig defines the application configuration values for the EVM. +type EVMConfig struct { + // Tracer defines vm.Tracer type that the EVM will use if the node is run in + // trace mode. Default: 'json'. + Tracer string `mapstructure:"tracer"` +} + +// JSONRPCConfig defines configuration for the EVM RPC server. +type JSONRPCConfig struct { + // Address defines the HTTP server to listen on + Address string `mapstructure:"address"` + // WsAddress defines the WebSocket server to listen on + WsAddress string `mapstructure:"ws-address"` + // API defines a list of JSON-RPC namespaces that should be enabled + API []string `mapstructure:"api"` + // Enable defines if the EVM RPC server should be enabled. + Enable bool `mapstructure:"enable"` + // GasCap is the global gas cap for eth-call variants. + GasCap uint64 `mapstructure:"gas-cap"` +} + +// TLSConfig defines the certificate and matching private key for the server. +type TLSConfig struct { + // CertificatePath the file path for the certificate .pem file + CertificatePath string `mapstructure:"certificate-path"` + // KeyPath the file path for the key .pem file + KeyPath string `mapstructure:"key-path"` } // AppConfig helps to override default appConfig template and configs. @@ -63,6 +98,7 @@ func AppConfig(denom string) (string, interface{}) { Config: *srvCfg, EVM: *DefaultEVMConfig(), JSONRPC: *DefaultJSONRPCConfig(), + TLS: *DefaultTLSConfig(), } customAppTemplate := config.DefaultConfigTemplate + DefaultConfigTemplate @@ -76,16 +112,10 @@ func DefaultConfig() *Config { Config: *config.DefaultConfig(), EVM: *DefaultEVMConfig(), JSONRPC: *DefaultJSONRPCConfig(), + TLS: *DefaultTLSConfig(), } } -// EVMConfig defines the application configuration values for the EVM. -type EVMConfig struct { - // Tracer defines vm.Tracer type that the EVM will use if the node is run in - // trace mode. Default: 'json'. - Tracer string `mapstructure:"tracer"` -} - // DefaultEVMConfig returns the default EVM configuration func DefaultEVMConfig() *EVMConfig { return &EVMConfig{ @@ -102,18 +132,20 @@ func (c EVMConfig) Validate() error { return nil } -// JSONRPCConfig defines configuration for the EVM RPC server. -type JSONRPCConfig struct { - // Address defines the HTTP server to listen on - Address string `mapstructure:"address"` - // WsAddress defines the WebSocket server to listen on - WsAddress string `mapstructure:"ws-address"` - // API defines a list of JSON-RPC namespaces that should be enabled - API []string `mapstructure:"api"` - // Enable defines if the EVM RPC server should be enabled. - Enable bool `mapstructure:"enable"` - // GasCap is the global gas cap for eth-call variants. - GasCap uint64 `mapstructure:"gas-cap"` +// GetDefaultAPINamespaces returns the default list of JSON-RPC namespaces that should be enabled +func GetDefaultAPINamespaces() []string { + return []string{"eth", "net", "web3"} +} + +// DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default +func DefaultJSONRPCConfig() *JSONRPCConfig { + return &JSONRPCConfig{ + Enable: true, + API: GetDefaultAPINamespaces(), + Address: DefaultJSONRPCAddress, + WsAddress: DefaultJSONRPCWsAddress, + GasCap: DefaultGasCap, + } } // Validate returns an error if the JSON-RPC configuration fields are invalid. @@ -135,24 +167,29 @@ func (c JSONRPCConfig) Validate() error { return nil } -// DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default -func DefaultJSONRPCConfig() *JSONRPCConfig { - return &JSONRPCConfig{ - Enable: true, - API: GetDefaultAPINamespaces(), - Address: DefaultJSONRPCAddress, - WsAddress: DefaultJSONRPCWsAddress, - GasCap: DefaultGasCap, +// DefaultTLSConfig returns the default TLS configuration +func DefaultTLSConfig() *TLSConfig { + return &TLSConfig{ + CertificatePath: "", + KeyPath: "", } } -// Config defines the server's top level configuration. It includes the default app config -// from the SDK as well as the EVM configuration to enable the JSON-RPC APIs. -type Config struct { - config.Config +// Validate returns an error if the TLS certificate and key file extensions are invalid. +func (c TLSConfig) Validate() error { + certExt := path.Ext(c.CertificatePath) - EVM EVMConfig `mapstructure:"evm"` - JSONRPC JSONRPCConfig `mapstructure:"json-rpc"` + if c.CertificatePath != "" && certExt != ".pem" { + return fmt.Errorf("invalid extension %s for certificate path %s, expected '.pem'", certExt, c.CertificatePath) + } + + keyExt := path.Ext(c.KeyPath) + + if c.KeyPath != "" && keyExt != ".pem" { + return fmt.Errorf("invalid extension %s for key path %s, expected '.pem'", keyExt, c.KeyPath) + } + + return nil } // GetConfig returns a fully parsed Config object. @@ -171,6 +208,10 @@ func GetConfig(v *viper.Viper) Config { WsAddress: v.GetString("json-rpc.ws-address"), GasCap: v.GetUint64("json-rpc.gas-cap"), }, + TLS: TLSConfig{ + CertificatePath: v.GetString("tls.certificate-path"), + KeyPath: v.GetString("tls.key-path"), + }, } } @@ -193,5 +234,9 @@ func (c Config) ValidateBasic() error { return sdkerrors.Wrapf(sdkerrors.ErrAppConfig, "invalid json-rpc config value: %s", err.Error()) } + if err := c.TLS.Validate(); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrAppConfig, "invalid tls config value: %s", err.Error()) + } + return c.Config.ValidateBasic() } diff --git a/server/config/toml.go b/server/config/toml.go index 6b0e3f0a..6793e1da 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -34,4 +34,16 @@ api = "{{range $index, $elmt := .JSONRPC.API}}{{if $index}},{{$elmt}}{{else}}{{$ # GasCap sets a cap on gas that can be used in eth_call/estimateGas (0=infinite). Default: 25,000,000. gas-cap = {{ .JSONRPC.GasCap }} + +############################################################################### +### TLS Configuration ### +############################################################################### + +[tls] + +# Certificate path defines the cert.pem file path for the TLS configuration. +certificate-path = "{{ .TLS.CertificatePath }}" + +# Key path defines the key.pem file path for the TLS configuration. +key-path = "{{ .TLS.KeyPath }}" ` diff --git a/server/flags/flags.go b/server/flags/flags.go index 89205f60..fb8eaad0 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -38,6 +38,12 @@ const ( EVMTracer = "evm.tracer" ) +// TLS flags +const ( + TLSCertPath = "tls.certificate-path" + TLSKeyPath = "tls.key-path" +) + // AddTxFlags adds common flags for commands to post tx func AddTxFlags(cmd *cobra.Command) *cobra.Command { cmd.PersistentFlags().String(flags.FlagChainID, "testnet", "Specify Chain ID for sending Tx") diff --git a/server/json_rpc.go b/server/json_rpc.go index 16bbd738..9090de24 100644 --- a/server/json_rpc.go +++ b/server/json_rpc.go @@ -1,7 +1,6 @@ package server import ( - "net" "net/http" "time" @@ -73,11 +72,10 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn } ctx.Logger.Info("Starting JSON WebSocket server", "address", config.JSONRPC.WsAddress) - _, port, _ := net.SplitHostPort(config.JSONRPC.Address) // allocate separate WS connection to Tendermint tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) - wsSrv := rpc.NewWebsocketsServer(ctx.Logger, tmWsClient, "localhost:"+port, config.JSONRPC.WsAddress) + wsSrv := rpc.NewWebsocketsServer(ctx.Logger, tmWsClient, config) wsSrv.Start() return httpSrv, httpSrvDone, nil } diff --git a/server/start.go b/server/start.go index 627f2c55..245b0c7c 100644 --- a/server/start.go +++ b/server/start.go @@ -147,6 +147,9 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") + cmd.Flags().String(srvflags.TLSCertPath, "", "the cert.pem file path for the server TLS configuration") + cmd.Flags().String(srvflags.TLSKeyPath, "", "the key.pem file path for the server TLS configuration") + cmd.Flags().Uint64(server.FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") cmd.Flags().Uint32(server.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")