diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 1503c0581b..42266d5bf4 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -195,6 +195,7 @@ var ( utils.SuaveConfidentialStorePebbleDbPathFlag, utils.SuaveEthBundleSigningKeyFlag, utils.SuaveEthBlockSigningKeyFlag, + utils.SuaveExternalWhitelistFlag, utils.SuaveDevModeFlag, } ) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 07bac21398..de6813e5ff 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -562,6 +562,13 @@ var ( Category: flags.SuaveCategory, } + SuaveExternalWhitelistFlag = &cli.StringSliceFlag{ + Name: "suave.eth.external-whitelist", + EnvVars: []string{"SUAVE_EXTERNAL_WHITELIST"}, + Usage: "List of external whitelisted addresses", + Category: flags.SuaveCategory, + } + SuaveDevModeFlag = &cli.BoolFlag{ Name: "suave.dev", Usage: "Dev mode for suave", @@ -1736,6 +1743,18 @@ func SetSuaveConfig(ctx *cli.Context, stack *node.Node, cfg *suave.Config) { if ctx.IsSet(SuaveEthBlockSigningKeyFlag.Name) { cfg.EthBlockSigningKeyHex = ctx.String(SuaveEthBlockSigningKeyFlag.Name) } + + if ctx.IsSet(SuaveEthBundleSigningKeyFlag.Name) { + cfg.EthBundleSigningKeyHex = ctx.String(SuaveEthBundleSigningKeyFlag.Name) + } + + if ctx.IsSet(SuaveExternalWhitelistFlag.Name) { + cfg.ExternalWhitelist = ctx.StringSlice(SuaveEthBundleSigningKeyFlag.Name) + if len(cfg.ExternalWhitelist) == 0 { + // As of now, default to wildcard + cfg.ExternalWhitelist = []string{"*"} + } + } } // SetEthConfig applies eth-related command line flags to the config. diff --git a/core/vm/contracts_suave.go b/core/vm/contracts_suave.go index 669cf9c4da..8f4c94b55b 100644 --- a/core/vm/contracts_suave.go +++ b/core/vm/contracts_suave.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strings" "time" @@ -146,17 +147,34 @@ func (s *suaveRuntime) doHTTPRequest(request types.HttpRequest) ([]byte, error) body = bytes.NewReader(request.Body) } + // decode the url and check if the domain is allowed + parsedURL, err := url.Parse(request.Url) + if err != nil { + panic(err) + } + + var allowed bool + for _, allowedDomain := range s.suaveContext.Backend.ExternalWhitelist { + if allowedDomain == "*" || allowedDomain == parsedURL.Hostname() { + allowed = true + break + } + } + if !allowed { + return nil, fmt.Errorf("domain %s is not allowed", parsedURL.Hostname()) + } + req, err := http.NewRequest(request.Method, request.Url, body) if err != nil { return nil, err } for _, header := range request.Headers { - prts := strings.Split(header, ":") - if len(prts) != 2 { - return nil, fmt.Errorf("incorrect header format") + indx := strings.Index(header, ":") + if indx == -1 { + return nil, fmt.Errorf("incorrect header format '%s', no ':' present", header) } - req.Header.Add(prts[0], prts[1]) + req.Header.Add(header[:indx], header[indx+1:]) } client := &http.Client{ diff --git a/core/vm/contracts_suave_eth.go b/core/vm/contracts_suave_eth.go index bf513bc6b0..02c826f170 100644 --- a/core/vm/contracts_suave_eth.go +++ b/core/vm/contracts_suave_eth.go @@ -285,8 +285,8 @@ func (b *suaveRuntime) submitEthBlockBidToRelay(relayUrl string, builderBidJson Url: endpoint, Body: builderBidJson, Headers: []string{ - "Content-Type: application/json", - "Accept: application/json", + "Content-Type:application/json", + "Accept:application/json", }, } if _, err := b.doHTTPRequest(httpReq); err != nil { @@ -363,9 +363,9 @@ func (c *suaveRuntime) submitBundleJsonRPC(url string, method string, params []b Url: url, Body: body, Headers: []string{ - "Content-Type: application/json", - "Accept: application/json", - "X-Flashbots-Signature: " + signature, + "Content-Type:application/json", + "Accept:application/json", + "X-Flashbots-Signature:" + signature, }, } if _, err := c.doHTTPRequest(httpReq); err != nil { diff --git a/core/vm/contracts_suave_test.go b/core/vm/contracts_suave_test.go index 35c631f020..72653b346e 100644 --- a/core/vm/contracts_suave_test.go +++ b/core/vm/contracts_suave_test.go @@ -279,8 +279,14 @@ func (h *httpTestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok1")) } -func TestSuave_HttpRequest(t *testing.T) { - s := &suaveRuntime{} +func TestSuave_HttpRequest_Basic(t *testing.T) { + s := &suaveRuntime{ + suaveContext: &SuaveContext{ + Backend: &SuaveExecutionBackend{ + ExternalWhitelist: []string{"127.0.0.1"}, + }, + }, + } srv := httptest.NewServer(&httpTestHandler{}) defer srv.Close() @@ -300,6 +306,11 @@ func TestSuave_HttpRequest(t *testing.T) { req: types.HttpRequest{Url: srv.URL}, err: true, }, + { + // url not allowed + req: types.HttpRequest{Url: "http://example.com", Method: "GET"}, + err: true, + }, { // incorrect header format req: types.HttpRequest{Url: srv.URL, Method: "GET", Headers: []string{"a"}}, @@ -325,6 +336,11 @@ func TestSuave_HttpRequest(t *testing.T) { req: types.HttpRequest{Url: srv.URL, Method: "POST", Headers: []string{"a:c"}}, resp: []byte("c"), }, + { + // POST request with headers with multiple : + req: types.HttpRequest{Url: srv.URL, Method: "POST", Headers: []string{"a:c:d"}}, + resp: []byte("c:d"), + }, { // POST with error req: types.HttpRequest{Url: srv.URL, Method: "POST", Headers: []string{"fail:1"}}, diff --git a/core/vm/suave.go b/core/vm/suave.go index bafe109714..95dd473b60 100644 --- a/core/vm/suave.go +++ b/core/vm/suave.go @@ -35,6 +35,7 @@ type SuaveContext struct { type SuaveExecutionBackend struct { EthBundleSigningKey *ecdsa.PrivateKey EthBlockSigningKey *bls.SecretKey + ExternalWhitelist []string ConfidentialStore ConfidentialStore ConfidentialEthBackend suave.ConfidentialEthBackend } diff --git a/eth/api_backend.go b/eth/api_backend.go index 38349927bf..b5d20d1f0f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -58,6 +58,7 @@ type EthAPIBackend struct { suaveEthBlockSigningKey *bls.SecretKey suaveEngine *cstore.ConfidentialStoreEngine suaveEthBackend suave.ConfidentialEthBackend + suaveExternalWhitelist []string } // For testing purposes @@ -293,6 +294,7 @@ func (b *EthAPIBackend) GetMEVM(ctx context.Context, msg *core.Message, state *s suaveCtxCopy.Backend = &vm.SuaveExecutionBackend{ EthBundleSigningKey: suaveCtx.Backend.EthBundleSigningKey, EthBlockSigningKey: suaveCtx.Backend.EthBlockSigningKey, + ExternalWhitelist: suaveCtx.Backend.ExternalWhitelist, ConfidentialStore: storeTransaction, ConfidentialEthBackend: b.suaveEthBackend, } @@ -449,6 +451,7 @@ func (b *EthAPIBackend) SuaveContext(requestTx *types.Transaction, ccr *types.Co Backend: &vm.SuaveExecutionBackend{ EthBundleSigningKey: b.suaveEthBundleSigningKey, EthBlockSigningKey: b.suaveEthBlockSigningKey, + ExternalWhitelist: b.suaveExternalWhitelist, ConfidentialStore: storeTransaction, ConfidentialEthBackend: b.suaveEthBackend, }, diff --git a/eth/backend.go b/eth/backend.go index 06601a29d7..8c374a4b97 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -287,7 +287,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { confidentialStoreEngine := cstore.NewConfidentialStoreEngine(confidentialStoreBackend, confidentialStoreTransport, suaveDaSigner, types.LatestSigner(chainConfig)) - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil, suaveEthBundleSigningKey, suaveEthBlockSigningKey, confidentialStoreEngine, suaveEthBackend} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil, suaveEthBundleSigningKey, suaveEthBlockSigningKey, confidentialStoreEngine, suaveEthBackend, config.Suave.ExternalWhitelist} if eth.APIBackend.allowUnprotectedTxs { log.Info("Unprotected transactions allowed") } diff --git a/suave/core/config.go b/suave/core/config.go index 0e75897ac6..035d26c6af 100644 --- a/suave/core/config.go +++ b/suave/core/config.go @@ -7,6 +7,7 @@ type Config struct { PebbleDbPath string EthBundleSigningKeyHex string EthBlockSigningKeyHex string + ExternalWhitelist []string } var DefaultConfig = Config{} diff --git a/suave/e2e/workflow_test.go b/suave/e2e/workflow_test.go index 9f23b977d9..6ad6218f86 100644 --- a/suave/e2e/workflow_test.go +++ b/suave/e2e/workflow_test.go @@ -1120,7 +1120,7 @@ func TestE2EOnChainStateTransition(t *testing.T) { } func TestE2ERemoteCalls(t *testing.T) { - fr := newFramework(t) + fr := newFramework(t, WithWhitelist([]string{"127.0.0.1"})) defer fr.Close() clt := fr.NewSDKClient() @@ -1164,6 +1164,16 @@ func TestE2ERemoteCalls(t *testing.T) { } contract.SendTransaction("remoteCall", []interface{}{req}, nil) }) + + t.Run("Not whitelisted", func(t *testing.T) { + req := &types.HttpRequest{ + Method: "POST", + Url: "http://example.com", + Headers: []string{"b:c"}, + } + _, err := contract.SendTransaction("remoteCall", []interface{}{req}, nil) + require.Error(t, err) + }) } type clientWrapper struct { @@ -1221,7 +1231,9 @@ type frameworkConfig struct { var defaultFrameworkConfig = frameworkConfig{ kettleAddress: false, redisStoreBackend: false, - suaveConfig: suave.Config{}, + suaveConfig: suave.Config{ + ExternalWhitelist: []string{"*"}, + }, } type frameworkOpt func(*frameworkConfig) @@ -1261,6 +1273,12 @@ func WithBlockSigningKeyOpt(t *testing.T) (frameworkOpt, *bls.PublicKey) { }, pk } +func WithWhitelist(whitelist []string) frameworkOpt { + return func(c *frameworkConfig) { + c.suaveConfig.ExternalWhitelist = whitelist + } +} + func newFramework(t *testing.T, opts ...frameworkOpt) *framework { cfg := defaultFrameworkConfig for _, opt := range opts {