github.com/Cleverse/go-ethereum@v0.0.0-20220927095127-45113064e7f2/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.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() + 6})) 360 }, 361 // stale 362 func() string { 363 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 6})) 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 }