github.com/authcall/reference-optimistic-geth@v0.0.0-20220816224302-06313bfeb8d2/node/node_auth_test.go (about) 1 // Copyright 2022 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 "context" 21 crand "crypto/rand" 22 "fmt" 23 "net/http" 24 "os" 25 "path" 26 "testing" 27 "time" 28 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 "github.com/ethereum/go-ethereum/rpc" 31 "github.com/golang-jwt/jwt/v4" 32 ) 33 34 type helloRPC string 35 36 func (ta helloRPC) HelloWorld() (string, error) { 37 return string(ta), nil 38 } 39 40 type TestAuthProvider func(header *http.Header) error 41 42 func (fn TestAuthProvider) AddAuthHeader(header *http.Header) error { 43 return fn(header) 44 } 45 46 type authTest struct { 47 name string 48 endpoint string 49 prov rpc.HeaderAuthProvider 50 expectDialFail bool 51 expectCall1Fail bool 52 expectCall2Fail bool 53 } 54 55 func (at *authTest) Run(t *testing.T) { 56 ctx := context.Background() 57 cl, err := rpc.DialWithAuth(ctx, at.endpoint, at.prov) 58 if at.expectDialFail { 59 if err == nil { 60 t.Fatal("expected initial dial to fail") 61 } else { 62 return 63 } 64 } 65 if err != nil { 66 t.Fatalf("failed to dial rpc endpoint: %v", err) 67 } 68 var x string 69 err = cl.CallContext(ctx, &x, "engine_helloWorld") 70 if at.expectCall1Fail { 71 if err == nil { 72 t.Fatal("expected call 1 to fail") 73 } else { 74 return 75 } 76 } 77 if err != nil { 78 t.Fatalf("failed to call rpc endpoint: %v", err) 79 } 80 if x != "hello engine" { 81 t.Fatalf("method was silent but did not return expected value: %q", x) 82 } 83 err = cl.CallContext(ctx, &x, "eth_helloWorld") 84 if at.expectCall2Fail { 85 if err == nil { 86 t.Fatal("expected call 2 to fail") 87 } else { 88 return 89 } 90 } 91 if err != nil { 92 t.Fatalf("failed to call rpc endpoint: %v", err) 93 } 94 if x != "hello eth" { 95 t.Fatalf("method was silent but did not return expected value: %q", x) 96 } 97 } 98 99 func TestAuthEndpoints(t *testing.T) { 100 var secret [32]byte 101 if _, err := crand.Read(secret[:]); err != nil { 102 t.Fatalf("failed to create jwt secret: %v", err) 103 } 104 // Geth must read it from a file, and does not support in-memory JWT secrets, so we create a temporary file. 105 jwtPath := path.Join(t.TempDir(), "jwt_secret") 106 if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil { 107 t.Fatalf("failed to prepare jwt secret file: %v", err) 108 } 109 // We get ports assigned by the node automatically 110 conf := &Config{ 111 HTTPHost: "127.0.0.1", 112 HTTPPort: 0, 113 WSHost: "127.0.0.1", 114 WSPort: 0, 115 AuthAddr: "127.0.0.1", 116 AuthPort: 0, 117 JWTSecret: jwtPath, 118 119 WSModules: []string{"eth", "engine"}, 120 HTTPModules: []string{"eth", "engine"}, 121 } 122 node, err := New(conf) 123 if err != nil { 124 t.Fatalf("could not create a new node: %v", err) 125 } 126 // register dummy apis so we can test the modules are available and reachable with authentication 127 node.RegisterAPIs([]rpc.API{ 128 { 129 Namespace: "engine", 130 Version: "1.0", 131 Service: helloRPC("hello engine"), 132 Public: true, 133 Authenticated: true, 134 }, 135 { 136 Namespace: "eth", 137 Version: "1.0", 138 Service: helloRPC("hello eth"), 139 Public: true, 140 Authenticated: true, 141 }, 142 }) 143 if err := node.Start(); err != nil { 144 t.Fatalf("failed to start test node: %v", err) 145 } 146 defer node.Close() 147 148 // sanity check we are running different endpoints 149 if a, b := node.WSEndpoint(), node.WSAuthEndpoint(); a == b { 150 t.Fatalf("expected ws and auth-ws endpoints to be different, got: %q and %q", a, b) 151 } 152 if a, b := node.HTTPEndpoint(), node.HTTPAuthEndpoint(); a == b { 153 t.Fatalf("expected http and auth-http endpoints to be different, got: %q and %q", a, b) 154 } 155 156 goodAuth := rpc.NewJWTAuthProvider(secret) 157 var otherSecret [32]byte 158 if _, err := crand.Read(otherSecret[:]); err != nil { 159 t.Fatalf("failed to create jwt secret: %v", err) 160 } 161 badAuth := rpc.NewJWTAuthProvider(otherSecret) 162 noneAuth := TestAuthProvider(func(header *http.Header) error { 163 token := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{ 164 "iat": &jwt.NumericDate{Time: time.Now()}, 165 }) 166 s, err := token.SignedString(secret[:]) 167 if err != nil { 168 return fmt.Errorf("failed to create JWT token: %w", err) 169 } 170 header.Add("Authorization", "Bearer "+s) 171 return nil 172 }) 173 offsetTimeAuth := func(offset time.Duration) TestAuthProvider { 174 return func(header *http.Header) error { 175 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 176 "iat": &jwt.NumericDate{Time: time.Now().Add(offset)}, 177 }) 178 s, err := token.SignedString(secret[:]) 179 if err != nil { 180 return fmt.Errorf("failed to create JWT token: %w", err) 181 } 182 header.Add("Authorization", "Bearer "+s) 183 return nil 184 } 185 } 186 changingAuth := func(provs ...rpc.HeaderAuthProvider) TestAuthProvider { 187 i := 0 188 return func(header *http.Header) error { 189 i += 1 190 if i > len(provs) { 191 i = len(provs) 192 } 193 return provs[i-1].AddAuthHeader(header) 194 } 195 } 196 197 notTooLong := time.Second * 57 198 tooLong := time.Second * 60 199 requestDelay := time.Second 200 201 testCases := []authTest{ 202 // Auth works 203 {name: "ws good", endpoint: node.WSAuthEndpoint(), prov: goodAuth, expectCall1Fail: false}, 204 {name: "http good", endpoint: node.HTTPAuthEndpoint(), prov: goodAuth, expectCall1Fail: false}, 205 206 // Try nil auth 207 {name: "ws nil auth provider", endpoint: node.WSAuthEndpoint(), prov: nil, expectDialFail: true}, 208 {name: "http nil auth provider", endpoint: node.HTTPAuthEndpoint(), prov: nil, expectDialFail: true}, 209 210 // Try a bad auth 211 {name: "ws bad", endpoint: node.WSAuthEndpoint(), prov: badAuth, expectDialFail: true}, // ws auth is immediate 212 {name: "http bad", endpoint: node.HTTPAuthEndpoint(), prov: badAuth, expectCall1Fail: true}, // http auth is on first call 213 214 // A common mistake with JWT is to allow the "none" algorithm, which is a valid JWT but not secure. 215 {name: "ws none", endpoint: node.WSAuthEndpoint(), prov: noneAuth, expectDialFail: true}, 216 {name: "http none", endpoint: node.HTTPAuthEndpoint(), prov: noneAuth, expectCall1Fail: true}, 217 218 // claims of 5 seconds or more, older or newer, are not allowed 219 {name: "ws too old", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(-tooLong), expectDialFail: true}, 220 {name: "http too old", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(-tooLong), expectCall1Fail: true}, 221 // note: for it to be too long we need to add a delay, so that once we receive the request, the difference has not dipped below the "tooLong" 222 {name: "ws too new", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(tooLong + requestDelay), expectDialFail: true}, 223 {name: "http too new", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(tooLong + requestDelay), expectCall1Fail: true}, 224 225 // Try offset the time, but stay just within bounds 226 {name: "ws old", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(-notTooLong)}, 227 {name: "http old", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(-notTooLong)}, 228 {name: "ws new", endpoint: node.WSAuthEndpoint(), prov: offsetTimeAuth(notTooLong)}, 229 {name: "http new", endpoint: node.HTTPAuthEndpoint(), prov: offsetTimeAuth(notTooLong)}, 230 231 // ws only authenticates on initial dial, then continues communication 232 {name: "ws single auth", endpoint: node.WSAuthEndpoint(), prov: changingAuth(goodAuth, badAuth)}, 233 {name: "http call fail auth", endpoint: node.HTTPAuthEndpoint(), prov: changingAuth(goodAuth, badAuth), expectCall2Fail: true}, 234 {name: "http call fail time", endpoint: node.HTTPAuthEndpoint(), prov: changingAuth(goodAuth, offsetTimeAuth(tooLong+requestDelay)), expectCall2Fail: true}, 235 } 236 237 for _, testCase := range testCases { 238 t.Run(testCase.name, testCase.Run) 239 } 240 }