github.com/bcnmy/go-ethereum@v1.10.27/node/rpcstack_test.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package node
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"net/http"
    23  	"net/url"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/internal/testlog"
    30  	"github.com/ethereum/go-ethereum/log"
    31  	"github.com/ethereum/go-ethereum/rpc"
    32  	"github.com/golang-jwt/jwt/v4"
    33  	"github.com/gorilla/websocket"
    34  	"github.com/stretchr/testify/assert"
    35  )
    36  
    37  // TestCorsHandler makes sure CORS are properly handled on the http server.
    38  func TestCorsHandler(t *testing.T) {
    39  	srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{})
    40  	defer srv.stop()
    41  	url := "http://" + srv.listenAddr()
    42  
    43  	resp := rpcRequest(t, url, "origin", "test.com")
    44  	assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
    45  
    46  	resp2 := rpcRequest(t, url, "origin", "bad")
    47  	assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
    48  }
    49  
    50  // TestVhosts makes sure vhosts are properly handled on the http server.
    51  func TestVhosts(t *testing.T) {
    52  	srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{})
    53  	defer srv.stop()
    54  	url := "http://" + srv.listenAddr()
    55  
    56  	resp := rpcRequest(t, url, "host", "test")
    57  	assert.Equal(t, resp.StatusCode, http.StatusOK)
    58  
    59  	resp2 := rpcRequest(t, url, "host", "bad")
    60  	assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
    61  }
    62  
    63  type originTest struct {
    64  	spec    string
    65  	expOk   []string
    66  	expFail []string
    67  }
    68  
    69  // splitAndTrim splits input separated by a comma
    70  // and trims excessive white space from the substrings.
    71  // Copied over from flags.go
    72  func splitAndTrim(input string) (ret []string) {
    73  	l := strings.Split(input, ",")
    74  	for _, r := range l {
    75  		r = strings.TrimSpace(r)
    76  		if len(r) > 0 {
    77  			ret = append(ret, r)
    78  		}
    79  	}
    80  	return ret
    81  }
    82  
    83  // TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
    84  func TestWebsocketOrigins(t *testing.T) {
    85  	tests := []originTest{
    86  		{
    87  			spec: "*", // allow all
    88  			expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540",
    89  				"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
    90  		},
    91  		{
    92  			spec:    "test",
    93  			expOk:   []string{"http://test", "https://test", "http://test:8540", "https://test:8540"},
    94  			expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
    95  		},
    96  		// scheme tests
    97  		{
    98  			spec:  "https://test",
    99  			expOk: []string{"https://test", "https://test:9999"},
   100  			expFail: []string{
   101  				"test",                                // no scheme, required by spec
   102  				"http://test",                         // wrong scheme
   103  				"http://test.foo", "https://a.test.x", // subdomain variations
   104  				"http://testx:8540", "https://xtest:8540"},
   105  		},
   106  		// ip tests
   107  		{
   108  			spec:  "https://12.34.56.78",
   109  			expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"},
   110  			expFail: []string{
   111  				"http://12.34.56.78",     // wrong scheme
   112  				"http://12.34.56.78:443", // wrong scheme
   113  				"http://1.12.34.56.78",   // wrong 'domain name'
   114  				"http://12.34.56.78.a",   // wrong 'domain name'
   115  				"https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"},
   116  		},
   117  		// port tests
   118  		{
   119  			spec:  "test:8540",
   120  			expOk: []string{"http://test:8540", "https://test:8540"},
   121  			expFail: []string{
   122  				"http://test", "https://test", // spec says port required
   123  				"http://test:8541", "https://test:8541", // wrong port
   124  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   125  		},
   126  		// scheme and port
   127  		{
   128  			spec:  "https://test:8540",
   129  			expOk: []string{"https://test:8540"},
   130  			expFail: []string{
   131  				"https://test",                          // missing port
   132  				"http://test",                           // missing port, + wrong scheme
   133  				"http://test:8540",                      // wrong scheme
   134  				"http://test:8541", "https://test:8541", // wrong port
   135  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   136  		},
   137  		// several allowed origins
   138  		{
   139  			spec: "localhost,http://127.0.0.1",
   140  			expOk: []string{"localhost", "http://localhost", "https://localhost:8443",
   141  				"http://127.0.0.1", "http://127.0.0.1:8080"},
   142  			expFail: []string{
   143  				"https://127.0.0.1", // wrong scheme
   144  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   145  		},
   146  	}
   147  	for _, tc := range tests {
   148  		srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)})
   149  		url := fmt.Sprintf("ws://%v", srv.listenAddr())
   150  		for _, origin := range tc.expOk {
   151  			if err := wsRequest(t, url, "Origin", origin); err != nil {
   152  				t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err)
   153  			}
   154  		}
   155  		for _, origin := range tc.expFail {
   156  			if err := wsRequest(t, url, "Origin", origin); err == nil {
   157  				t.Errorf("spec '%v', origin '%v': expected not to allow,  got ok", tc.spec, origin)
   158  			}
   159  		}
   160  		srv.stop()
   161  	}
   162  }
   163  
   164  // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly.
   165  func TestIsWebsocket(t *testing.T) {
   166  	r, _ := http.NewRequest("GET", "/", nil)
   167  
   168  	assert.False(t, isWebsocket(r))
   169  	r.Header.Set("upgrade", "websocket")
   170  	assert.False(t, isWebsocket(r))
   171  	r.Header.Set("connection", "upgrade")
   172  	assert.True(t, isWebsocket(r))
   173  	r.Header.Set("connection", "upgrade,keep-alive")
   174  	assert.True(t, isWebsocket(r))
   175  	r.Header.Set("connection", " UPGRADE,keep-alive")
   176  	assert.True(t, isWebsocket(r))
   177  }
   178  
   179  func Test_checkPath(t *testing.T) {
   180  	tests := []struct {
   181  		req      *http.Request
   182  		prefix   string
   183  		expected bool
   184  	}{
   185  		{
   186  			req:      &http.Request{URL: &url.URL{Path: "/test"}},
   187  			prefix:   "/test",
   188  			expected: true,
   189  		},
   190  		{
   191  			req:      &http.Request{URL: &url.URL{Path: "/testing"}},
   192  			prefix:   "/test",
   193  			expected: true,
   194  		},
   195  		{
   196  			req:      &http.Request{URL: &url.URL{Path: "/"}},
   197  			prefix:   "/test",
   198  			expected: false,
   199  		},
   200  		{
   201  			req:      &http.Request{URL: &url.URL{Path: "/fail"}},
   202  			prefix:   "/test",
   203  			expected: false,
   204  		},
   205  		{
   206  			req:      &http.Request{URL: &url.URL{Path: "/"}},
   207  			prefix:   "",
   208  			expected: true,
   209  		},
   210  		{
   211  			req:      &http.Request{URL: &url.URL{Path: "/fail"}},
   212  			prefix:   "",
   213  			expected: false,
   214  		},
   215  		{
   216  			req:      &http.Request{URL: &url.URL{Path: "/"}},
   217  			prefix:   "/",
   218  			expected: true,
   219  		},
   220  		{
   221  			req:      &http.Request{URL: &url.URL{Path: "/testing"}},
   222  			prefix:   "/",
   223  			expected: true,
   224  		},
   225  	}
   226  
   227  	for i, tt := range tests {
   228  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   229  			assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix))
   230  		})
   231  	}
   232  }
   233  
   234  func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer {
   235  	t.Helper()
   236  
   237  	srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts)
   238  	assert.NoError(t, srv.enableRPC(nil, *conf))
   239  	if ws {
   240  		assert.NoError(t, srv.enableWS(nil, *wsConf))
   241  	}
   242  	assert.NoError(t, srv.setListenAddr("localhost", 0))
   243  	assert.NoError(t, srv.start())
   244  	return srv
   245  }
   246  
   247  // wsRequest attempts to open a WebSocket connection to the given URL.
   248  func wsRequest(t *testing.T, url string, extraHeaders ...string) error {
   249  	t.Helper()
   250  	//t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin)
   251  
   252  	headers := make(http.Header)
   253  	// Apply extra headers.
   254  	if len(extraHeaders)%2 != 0 {
   255  		panic("odd extraHeaders length")
   256  	}
   257  	for i := 0; i < len(extraHeaders); i += 2 {
   258  		key, value := extraHeaders[i], extraHeaders[i+1]
   259  		headers.Set(key, value)
   260  	}
   261  	conn, _, err := websocket.DefaultDialer.Dial(url, headers)
   262  	if conn != nil {
   263  		conn.Close()
   264  	}
   265  	return err
   266  }
   267  
   268  // rpcRequest performs a JSON-RPC request to the given URL.
   269  func rpcRequest(t *testing.T, url string, extraHeaders ...string) *http.Response {
   270  	t.Helper()
   271  
   272  	// Create the request.
   273  	body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`))
   274  	req, err := http.NewRequest("POST", url, body)
   275  	if err != nil {
   276  		t.Fatal("could not create http request:", err)
   277  	}
   278  	req.Header.Set("content-type", "application/json")
   279  
   280  	// Apply extra headers.
   281  	if len(extraHeaders)%2 != 0 {
   282  		panic("odd extraHeaders length")
   283  	}
   284  	for i := 0; i < len(extraHeaders); i += 2 {
   285  		key, value := extraHeaders[i], extraHeaders[i+1]
   286  		if strings.EqualFold(key, "host") {
   287  			req.Host = value
   288  		} else {
   289  			req.Header.Set(key, value)
   290  		}
   291  	}
   292  
   293  	// Perform the request.
   294  	t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders)
   295  	resp, err := http.DefaultClient.Do(req)
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	return resp
   300  }
   301  
   302  type testClaim map[string]interface{}
   303  
   304  func (testClaim) Valid() error {
   305  	return nil
   306  }
   307  
   308  func TestJWT(t *testing.T) {
   309  	var secret = []byte("secret")
   310  	issueToken := func(secret []byte, method jwt.SigningMethod, input map[string]interface{}) string {
   311  		if method == nil {
   312  			method = jwt.SigningMethodHS256
   313  		}
   314  		ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret)
   315  		return ss
   316  	}
   317  	srv := createAndStartServer(t, &httpConfig{jwtSecret: []byte("secret")},
   318  		true, &wsConfig{Origins: []string{"*"}, jwtSecret: []byte("secret")})
   319  	wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr())
   320  	htUrl := fmt.Sprintf("http://%v", srv.listenAddr())
   321  
   322  	expOk := []func() string{
   323  		func() string {
   324  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   325  		},
   326  		func() string {
   327  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 4}))
   328  		},
   329  		func() string {
   330  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 4}))
   331  		},
   332  		func() string {
   333  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{
   334  				"iat": time.Now().Unix(),
   335  				"exp": time.Now().Unix() + 2,
   336  			}))
   337  		},
   338  		func() string {
   339  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{
   340  				"iat": time.Now().Unix(),
   341  				"bar": "baz",
   342  			}))
   343  		},
   344  	}
   345  	for i, tokenFn := range expOk {
   346  		token := tokenFn()
   347  		if err := wsRequest(t, wsUrl, "Authorization", token); err != nil {
   348  			t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err)
   349  		}
   350  		token = tokenFn()
   351  		if resp := rpcRequest(t, htUrl, "Authorization", token); resp.StatusCode != 200 {
   352  			t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode)
   353  		}
   354  	}
   355  
   356  	expFail := []func() string{
   357  		// future
   358  		func() string {
   359  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + int64(jwtExpiryTimeout.Seconds()) + 1}))
   360  		},
   361  		// stale
   362  		func() string {
   363  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - int64(jwtExpiryTimeout.Seconds()) - 1}))
   364  		},
   365  		// wrong algo
   366  		func() string {
   367  			return fmt.Sprintf("Bearer %v", issueToken(secret, jwt.SigningMethodHS512, testClaim{"iat": time.Now().Unix() + 4}))
   368  		},
   369  		// expired
   370  		func() string {
   371  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix(), "exp": time.Now().Unix()}))
   372  		},
   373  		// missing mandatory iat
   374  		func() string {
   375  			return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{}))
   376  		},
   377  		//  wrong secret
   378  		func() string {
   379  			return fmt.Sprintf("Bearer %v", issueToken([]byte("wrong"), nil, testClaim{"iat": time.Now().Unix()}))
   380  		},
   381  		func() string {
   382  			return fmt.Sprintf("Bearer %v", issueToken([]byte{}, nil, testClaim{"iat": time.Now().Unix()}))
   383  		},
   384  		func() string {
   385  			return fmt.Sprintf("Bearer %v", issueToken(nil, nil, testClaim{"iat": time.Now().Unix()}))
   386  		},
   387  		// Various malformed syntax
   388  		func() string {
   389  			return fmt.Sprintf("%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   390  		},
   391  		func() string {
   392  			return fmt.Sprintf("Bearer  %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   393  		},
   394  		func() string {
   395  			return fmt.Sprintf("bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   396  		},
   397  		func() string {
   398  			return fmt.Sprintf("Bearer: %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   399  		},
   400  		func() string {
   401  			return fmt.Sprintf("Bearer:%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   402  		},
   403  		func() string {
   404  			return fmt.Sprintf("Bearer\t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   405  		},
   406  		func() string {
   407  			return fmt.Sprintf("Bearer \t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()}))
   408  		},
   409  	}
   410  	for i, tokenFn := range expFail {
   411  		token := tokenFn()
   412  		if err := wsRequest(t, wsUrl, "Authorization", token); err == nil {
   413  			t.Errorf("tc %d-ws, token '%v': expected not to allow,  got ok", i, token)
   414  		}
   415  		token = tokenFn()
   416  		if resp := rpcRequest(t, htUrl, "Authorization", token); resp.StatusCode != 403 {
   417  			t.Errorf("tc %d-http, token '%v': expected not to allow,  got %v", i, token, resp.StatusCode)
   418  		}
   419  	}
   420  	srv.stop()
   421  }