rpc, server: add TLS certificate for websocket (#600)

* rpc, server: add TLS certificate for websocket

* changelog
This commit is contained in:
Federico Kunze Küllmer 2021-09-28 13:33:54 +02:00 committed by GitHub
parent 9164eb329d
commit 05d9b290a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 49 deletions

View File

@ -56,6 +56,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes ### 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` * (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#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`. * (evm) [tharsis#579](https://github.com/tharsis/ethermint/pull/579) Update `DeriveChainID` function to handle `v` signature values `< 35`.

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"net"
"net/http" "net/http"
"sync" "sync"
@ -25,6 +26,7 @@ import (
rpcfilters "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth/filters" rpcfilters "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth/filters"
"github.com/tharsis/ethermint/ethereum/rpc/types" "github.com/tharsis/ethermint/ethereum/rpc/types"
"github.com/tharsis/ethermint/server/config"
evmtypes "github.com/tharsis/ethermint/x/evm/types" evmtypes "github.com/tharsis/ethermint/x/evm/types"
) )
@ -63,15 +65,21 @@ type ErrorMessageJSON struct {
type websocketsServer struct { type websocketsServer struct {
rpcAddr string // listen address of rest-server rpcAddr string // listen address of rest-server
wsAddr string // listen address of ws server wsAddr string // listen address of ws server
certFile string
keyFile string
api *pubSubAPI api *pubSubAPI
logger log.Logger 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") logger = logger.With("api", "websocket-server")
_, port, _ := net.SplitHostPort(cfg.JSONRPC.Address)
return &websocketsServer{ return &websocketsServer{
rpcAddr: rpcAddr, rpcAddr: "localhost:" + port, // FIXME: this shouldn't be hardcoded to localhost
wsAddr: wsAddr, wsAddr: cfg.JSONRPC.WsAddress,
certFile: cfg.TLS.CertificatePath,
keyFile: cfg.TLS.KeyPath,
api: newPubSubAPI(logger, tmWSClient), api: newPubSubAPI(logger, tmWSClient),
logger: logger, logger: logger,
} }
@ -82,7 +90,13 @@ func (s *websocketsServer) Start() {
ws.Handle("/", s) ws.Handle("/", s)
go func() { 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 != nil {
if err == http.ErrServerClosed { if err == http.ErrServerClosed {
return return

View File

@ -3,6 +3,7 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"path"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -31,9 +32,43 @@ const (
var evmTracers = []string{DefaultEVMTracer, "markdown", "struct", "access_list"} var evmTracers = []string{DefaultEVMTracer, "markdown", "struct", "access_list"}
// GetDefaultAPINamespaces returns the default list of JSON-RPC namespaces that should be enabled // Config defines the server's top level configuration. It includes the default app config
func GetDefaultAPINamespaces() []string { // from the SDK as well as the EVM configuration to enable the JSON-RPC APIs.
return []string{"eth", "net", "web3"} 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. // AppConfig helps to override default appConfig template and configs.
@ -63,6 +98,7 @@ func AppConfig(denom string) (string, interface{}) {
Config: *srvCfg, Config: *srvCfg,
EVM: *DefaultEVMConfig(), EVM: *DefaultEVMConfig(),
JSONRPC: *DefaultJSONRPCConfig(), JSONRPC: *DefaultJSONRPCConfig(),
TLS: *DefaultTLSConfig(),
} }
customAppTemplate := config.DefaultConfigTemplate + DefaultConfigTemplate customAppTemplate := config.DefaultConfigTemplate + DefaultConfigTemplate
@ -76,16 +112,10 @@ func DefaultConfig() *Config {
Config: *config.DefaultConfig(), Config: *config.DefaultConfig(),
EVM: *DefaultEVMConfig(), EVM: *DefaultEVMConfig(),
JSONRPC: *DefaultJSONRPCConfig(), 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 // DefaultEVMConfig returns the default EVM configuration
func DefaultEVMConfig() *EVMConfig { func DefaultEVMConfig() *EVMConfig {
return &EVMConfig{ return &EVMConfig{
@ -102,18 +132,20 @@ func (c EVMConfig) Validate() error {
return nil return nil
} }
// JSONRPCConfig defines configuration for the EVM RPC server. // GetDefaultAPINamespaces returns the default list of JSON-RPC namespaces that should be enabled
type JSONRPCConfig struct { func GetDefaultAPINamespaces() []string {
// Address defines the HTTP server to listen on return []string{"eth", "net", "web3"}
Address string `mapstructure:"address"` }
// WsAddress defines the WebSocket server to listen on
WsAddress string `mapstructure:"ws-address"` // DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default
// API defines a list of JSON-RPC namespaces that should be enabled func DefaultJSONRPCConfig() *JSONRPCConfig {
API []string `mapstructure:"api"` return &JSONRPCConfig{
// Enable defines if the EVM RPC server should be enabled. Enable: true,
Enable bool `mapstructure:"enable"` API: GetDefaultAPINamespaces(),
// GasCap is the global gas cap for eth-call variants. Address: DefaultJSONRPCAddress,
GasCap uint64 `mapstructure:"gas-cap"` WsAddress: DefaultJSONRPCWsAddress,
GasCap: DefaultGasCap,
}
} }
// Validate returns an error if the JSON-RPC configuration fields are invalid. // Validate returns an error if the JSON-RPC configuration fields are invalid.
@ -135,24 +167,29 @@ func (c JSONRPCConfig) Validate() error {
return nil return nil
} }
// DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default // DefaultTLSConfig returns the default TLS configuration
func DefaultJSONRPCConfig() *JSONRPCConfig { func DefaultTLSConfig() *TLSConfig {
return &JSONRPCConfig{ return &TLSConfig{
Enable: true, CertificatePath: "",
API: GetDefaultAPINamespaces(), KeyPath: "",
Address: DefaultJSONRPCAddress,
WsAddress: DefaultJSONRPCWsAddress,
GasCap: DefaultGasCap,
} }
} }
// Config defines the server's top level configuration. It includes the default app config // Validate returns an error if the TLS certificate and key file extensions are invalid.
// from the SDK as well as the EVM configuration to enable the JSON-RPC APIs. func (c TLSConfig) Validate() error {
type Config struct { certExt := path.Ext(c.CertificatePath)
config.Config
EVM EVMConfig `mapstructure:"evm"` if c.CertificatePath != "" && certExt != ".pem" {
JSONRPC JSONRPCConfig `mapstructure:"json-rpc"` 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. // GetConfig returns a fully parsed Config object.
@ -171,6 +208,10 @@ func GetConfig(v *viper.Viper) Config {
WsAddress: v.GetString("json-rpc.ws-address"), WsAddress: v.GetString("json-rpc.ws-address"),
GasCap: v.GetUint64("json-rpc.gas-cap"), 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()) 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() return c.Config.ValidateBasic()
} }

View File

@ -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. # GasCap sets a cap on gas that can be used in eth_call/estimateGas (0=infinite). Default: 25,000,000.
gas-cap = {{ .JSONRPC.GasCap }} 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 }}"
` `

View File

@ -38,6 +38,12 @@ const (
EVMTracer = "evm.tracer" EVMTracer = "evm.tracer"
) )
// TLS flags
const (
TLSCertPath = "tls.certificate-path"
TLSKeyPath = "tls.key-path"
)
// AddTxFlags adds common flags for commands to post tx // AddTxFlags adds common flags for commands to post tx
func AddTxFlags(cmd *cobra.Command) *cobra.Command { func AddTxFlags(cmd *cobra.Command) *cobra.Command {
cmd.PersistentFlags().String(flags.FlagChainID, "testnet", "Specify Chain ID for sending Tx") cmd.PersistentFlags().String(flags.FlagChainID, "testnet", "Specify Chain ID for sending Tx")

View File

@ -1,7 +1,6 @@
package server package server
import ( import (
"net"
"net/http" "net/http"
"time" "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) ctx.Logger.Info("Starting JSON WebSocket server", "address", config.JSONRPC.WsAddress)
_, port, _ := net.SplitHostPort(config.JSONRPC.Address)
// allocate separate WS connection to Tendermint // allocate separate WS connection to Tendermint
tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) 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() wsSrv.Start()
return httpSrv, httpSrvDone, nil return httpSrv, httpSrvDone, nil
} }

View File

@ -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.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().Uint64(server.FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval")
cmd.Flags().Uint32(server.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") cmd.Flags().Uint32(server.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")