github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/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 variatoins
   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.ToLower(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  	expOk := []string{
   318  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   319  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 4})),
   320  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 4})),
   321  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{
   322  			"iat": time.Now().Unix(),
   323  			"exp": time.Now().Unix() + 2,
   324  		})),
   325  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{
   326  			"iat": time.Now().Unix(),
   327  			"bar": "baz",
   328  		})),
   329  	}
   330  	expFail := []string{
   331  		// future
   332  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 6})),
   333  		// stale
   334  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 6})),
   335  		// wrong algo
   336  		fmt.Sprintf("Bearer %v", issueToken(secret, jwt.SigningMethodHS512, testClaim{"iat": time.Now().Unix() + 4})),
   337  		// expired
   338  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix(), "exp": time.Now().Unix()})),
   339  		// missing mandatory iat
   340  		fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{})),
   341  		// wrong secret
   342  		fmt.Sprintf("Bearer %v", issueToken([]byte("wrong"), nil, testClaim{"iat": time.Now().Unix()})),
   343  		fmt.Sprintf("Bearer %v", issueToken([]byte{}, nil, testClaim{"iat": time.Now().Unix()})),
   344  		fmt.Sprintf("Bearer %v", issueToken(nil, nil, testClaim{"iat": time.Now().Unix()})),
   345  		// Various malformed syntax
   346  		fmt.Sprintf("%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   347  		fmt.Sprintf("Bearer  %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   348  		fmt.Sprintf("bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   349  		fmt.Sprintf("Bearer: %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   350  		fmt.Sprintf("Bearer:%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   351  		fmt.Sprintf("Bearer\t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   352  		fmt.Sprintf("Bearer \t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})),
   353  	}
   354  	srv := createAndStartServer(t, &httpConfig{jwtSecret: []byte("secret")},
   355  		true, &wsConfig{Origins: []string{"*"}, jwtSecret: []byte("secret")})
   356  	wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr())
   357  	htUrl := fmt.Sprintf("http://%v", srv.listenAddr())
   358  
   359  	for i, token := range expOk {
   360  		if err := wsRequest(t, wsUrl, "Authorization", token); err != nil {
   361  			t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err)
   362  		}
   363  		if resp := rpcRequest(t, htUrl, "Authorization", token); resp.StatusCode != 200 {
   364  			t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode)
   365  		}
   366  	}
   367  	for i, token := range expFail {
   368  		if err := wsRequest(t, wsUrl, "Authorization", token); err == nil {
   369  			t.Errorf("tc %d-ws, token '%v': expected not to allow,  got ok", i, token)
   370  		}
   371  		if resp := rpcRequest(t, htUrl, "Authorization", token); resp.StatusCode != 403 {
   372  			t.Errorf("tc %d-http, token '%v': expected not to allow,  got %v", i, token, resp.StatusCode)
   373  		}
   374  	}
   375  	srv.stop()
   376  }