mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 19:50:36 +00:00
Added Engine Authentication [JWT] (#3531)
* jwt * fuuuuture * added auth * merge * merge * Update jwt to the latest version * ops * comments * cleanup * bad * gut * maybe * mod sum Co-authored-by: yperbasis <andrey.ashikhmin@gmail.com>
This commit is contained in:
parent
0cac29d1d2
commit
cb8aacae87
@ -2,14 +2,18 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"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"
|
||||
@ -26,6 +30,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"
|
||||
@ -46,6 +51,9 @@ var rootCmd = &cobra.Command{
|
||||
Short: "rpcdaemon is JSON RPC server that connects to Erigon node for remote DB access",
|
||||
}
|
||||
|
||||
const JwtTokenExpiry = 5 * time.Second
|
||||
const JwtDefaultFile = "jwt.hex"
|
||||
|
||||
func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
|
||||
utils.CobraFlags(rootCmd, append(debug.Flags, utils.MetricFlags...))
|
||||
|
||||
@ -80,6 +88,7 @@ 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, "jwt-secret", "", "Token to ensure safe connection between CL and EL")
|
||||
|
||||
if err := rootCmd.MarkPersistentFlagFilename("rpc.accessList", "json"); err != nil {
|
||||
panic(err)
|
||||
@ -372,6 +381,7 @@ func RemoteServices(ctx context.Context, cfg httpcfg.HttpCfg, logger log.Logger,
|
||||
|
||||
func StartRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API) error {
|
||||
var engineListener *http.Server
|
||||
var engineListenerAuth *http.Server
|
||||
var enginesrv *rpc.Server
|
||||
var engineHttpEndpoint string
|
||||
|
||||
@ -418,7 +428,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 {
|
||||
@ -428,7 +441,7 @@ func StartRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API)
|
||||
"ws.compression", cfg.WebsocketCompression, "grpc", cfg.GRPCServerEnabled}
|
||||
|
||||
if len(engineAPI) > 0 {
|
||||
engineListener, enginesrv, engineHttpEndpoint, err = createEngineListener(cfg, engineAPI, engineFlag)
|
||||
engineListener, engineListenerAuth, enginesrv, engineHttpEndpoint, err = createEngineListener(cfg, engineAPI, engineFlag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start RPC api for engine: %w", err)
|
||||
}
|
||||
@ -471,6 +484,11 @@ func StartRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API)
|
||||
log.Info("Engine HTTP endpoint close", "url", engineHttpEndpoint)
|
||||
}
|
||||
|
||||
if engineListenerAuth != nil {
|
||||
_ = engineListenerAuth.Shutdown(shutdownCtx)
|
||||
log.Info("Engine HTTP endpoint close", "url", engineHttpEndpoint)
|
||||
}
|
||||
|
||||
if cfg.GRPCServerEnabled {
|
||||
if cfg.GRPCHealthCheckEnabled {
|
||||
healthServer.Shutdown()
|
||||
@ -491,7 +509,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, isAuth bool) (http.Handler, error) {
|
||||
var jwtVerificationKey []byte
|
||||
var err error
|
||||
|
||||
if isAuth {
|
||||
// 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
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(jwtVerificationKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} 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) {
|
||||
@ -501,37 +549,79 @@ func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Hand
|
||||
wsHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if isAuth {
|
||||
// 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) {
|
||||
func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API, engineFlag []string) (*http.Server, *http.Server, *rpc.Server, string, error) {
|
||||
engineHttpEndpoint := fmt.Sprintf("%s:%d", cfg.EngineHTTPListenAddress, cfg.EnginePort)
|
||||
engineHttpEndpointAuth := fmt.Sprintf("%s:%d", cfg.EngineHTTPListenAddress, cfg.EnginePort+1)
|
||||
|
||||
enginesrv := rpc.NewServer(cfg.RpcBatchConcurrency)
|
||||
|
||||
allowListForRPC, err := parseAllowListForRPC(cfg.RpcAllowListFilePath)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
enginesrv.SetAllowList(allowListForRPC)
|
||||
|
||||
if err := node.RegisterApisFromWhitelist(engineApi, engineFlag, enginesrv, false); err != nil {
|
||||
return nil, nil, "", fmt.Errorf("could not start register RPC engine api: %w", err)
|
||||
return nil, nil, nil, "", fmt.Errorf("could not start register RPC engine api: %w", err)
|
||||
}
|
||||
|
||||
engineHttpHandler := node.NewHTTPHandlerStack(enginesrv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
|
||||
engineApiHandler := createHandler(cfg, engineApi, engineHttpHandler, nil)
|
||||
engineApiHandler, err := createHandler(cfg, engineApi, engineHttpHandler, nil, false)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
|
||||
engineApiHandlerAuth, err := createHandler(cfg, engineApi, engineHttpHandler, nil, true)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
|
||||
engineListener, _, err := node.StartHTTPEndpoint(engineHttpEndpoint, rpc.DefaultHTTPTimeouts, engineApiHandler)
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
|
||||
return nil, nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
|
||||
}
|
||||
|
||||
engineListenerAuth, _, err := node.StartHTTPEndpoint(engineHttpEndpointAuth, rpc.DefaultHTTPTimeouts, engineApiHandlerAuth)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
|
||||
}
|
||||
|
||||
engineInfo := []interface{}{"url", engineHttpEndpoint}
|
||||
log.Info("HTTP endpoint opened for engine", engineInfo...)
|
||||
engineInfoAuth := []interface{}{"url", engineHttpEndpointAuth}
|
||||
log.Info("HTTP endpoint opened for auth engine", engineInfoAuth...)
|
||||
|
||||
return engineListener, enginesrv, engineHttpEndpoint, nil
|
||||
return engineListener, engineListenerAuth, enginesrv, engineHttpEndpoint, nil
|
||||
|
||||
}
|
||||
|
@ -38,4 +38,5 @@ type HttpCfg struct {
|
||||
GRPCPort int
|
||||
GRPCHealthCheckEnabled bool
|
||||
StarknetGRPCAddress string
|
||||
JWTSecretPath string // Engine API Authentication
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -26,6 +26,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
|
||||
@ -66,6 +67,7 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect
|
||||
google.golang.org/grpc v1.42.0
|
||||
|
4
go.sum
4
go.sum
@ -421,6 +421,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=
|
||||
@ -1339,6 +1341,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
Loading…
Reference in New Issue
Block a user