Skip to content
79 changes: 75 additions & 4 deletions cmd/rpcdaemon/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package cli

import (
"context"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"

"github.com/golang-jwt/jwt/v4"
"github.com/ledgerwatch/erigon-lib/direct"
"github.com/ledgerwatch/erigon-lib/gointerfaces"
"github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil"
Expand All @@ -28,6 +32,7 @@ import (
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
"github.com/ledgerwatch/erigon/cmd/utils"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/paths"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/eth/ethconfig"
Expand All @@ -50,6 +55,9 @@ var rootCmd = &cobra.Command{
Short: "rpcdaemon is JSON RPC server that connects to Erigon node for remote DB access",
}

const JwtTokenExpiry = 1000 * time.Hour
const JwtDefaultFile = "jwt.hex"

func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
utils.CobraFlags(rootCmd, append(debug.Flags, utils.MetricFlags...))

Expand Down Expand Up @@ -84,6 +92,8 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
rootCmd.PersistentFlags().IntVar(&cfg.GRPCPort, "grpc.port", node.DefaultGRPCPort, "GRPC server listening port")
rootCmd.PersistentFlags().BoolVar(&cfg.GRPCHealthCheckEnabled, "grpc.healthcheck", false, "Enable GRPC health check")
rootCmd.PersistentFlags().StringVar(&cfg.StarknetGRPCAddress, "starknet.grpc.address", "127.0.0.1:6066", "Starknet GRPC address")
rootCmd.PersistentFlags().StringVar(&cfg.JWTSecretPath, "engine.authsecret", "", "Token to ensure safe connection beetwen CL and EL")
rootCmd.PersistentFlags().BoolVar(&cfg.EngineAuthentication, "engine.auth", false, "Enable Authentication accordingly to Klin Specs")

if err := rootCmd.MarkPersistentFlagFilename("rpc.accessList", "json"); err != nil {
panic(err)
Expand Down Expand Up @@ -426,7 +436,10 @@ func StartRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API)
wsHandler = srv.WebsocketHandler([]string{"*"}, cfg.WebsocketCompression)
}

apiHandler := createHandler(cfg, defaultAPIList, httpHandler, wsHandler)
apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, false)
if err != nil {
return err
}

listener, _, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, apiHandler)
if err != nil {
Expand Down Expand Up @@ -499,7 +512,37 @@ func isWebsocket(r *http.Request) bool {
strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
}

func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler) http.Handler {
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, isEngine bool) (http.Handler, error) {
var jwtVerificationKey []byte
var err error

if isEngine && cfg.EngineAuthentication {
// If no file is specified we generate a key in jwt.hex
if cfg.JWTSecretPath == "" {
jwtVerificationKey := make([]byte, 32)
rand.Read(jwtVerificationKey)
jwtVerificationKey = []byte(common.Bytes2Hex(jwtVerificationKey))
f, err := os.Create(JwtDefaultFile)
if err != nil {
return nil, err
}

_, err = f.Write(jwtVerificationKey)
if err != nil {
return nil, err
}
f.Close()
} else {
jwtVerificationKey, err = ioutil.ReadFile(cfg.JWTSecretPath)
if err != nil {
return nil, err
}
if len(jwtVerificationKey) != 64 {
return nil, fmt.Errorf("error: invalid size of verification key in %s", cfg.JWTSecretPath)
}
}
}

var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// adding a healthcheck here
if health.ProcessHealthcheckIfNeeded(w, r, apiList) {
Expand All @@ -509,10 +552,35 @@ func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Hand
wsHandler.ServeHTTP(w, r)
return
}

if isEngine && cfg.EngineAuthentication {
// Check if JWT signature is correct
tokenStr, ok := r.Header["Authorization"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
return
}

claims := jwt.StandardClaims{}
tkn, err := jwt.ParseWithClaims(strings.Replace(tokenStr[0], "Bearer ", "", 1), &claims, func(token *jwt.Token) (interface{}, error) {
return jwtVerificationKey, nil
})
if err != nil || !tkn.Valid {
w.WriteHeader(http.StatusUnauthorized)
return
}
// Validate time of iat
now := time.Now().Unix()
if claims.IssuedAt > now+JwtTokenExpiry.Nanoseconds() && claims.IssuedAt < now-JwtTokenExpiry.Nanoseconds() {
w.WriteHeader(http.StatusUnauthorized)
return
}
}

httpHandler.ServeHTTP(w, r)
})

return handler
return handler, nil
}

func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API, engineFlag []string) (*http.Server, *rpc.Server, string, error) {
Expand All @@ -531,7 +599,10 @@ func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API, engineFlag [
}

engineHttpHandler := node.NewHTTPHandlerStack(enginesrv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
engineApiHandler := createHandler(cfg, engineApi, engineHttpHandler, nil)
engineApiHandler, err := createHandler(cfg, engineApi, engineHttpHandler, nil, true)
if err != nil {
return nil, nil, "", err
}

engineListener, _, err := node.StartHTTPEndpoint(engineHttpEndpoint, rpc.DefaultHTTPTimeouts, engineApiHandler)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions cmd/rpcdaemon/cli/httpcfg/http_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ type HttpCfg struct {
GRPCPort int
GRPCHealthCheckEnabled bool
StarknetGRPCAddress string
JWTSecretPath string // Engine API Authentication
EngineAuthentication bool
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/goccy/go-json v0.7.4
github.com/gofrs/flock v0.8.1
github.com/golang-jwt/jwt/v4 v4.3.0
github.com/golang/snappy v0.0.4
github.com/google/btree v1.0.1
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down