github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/auth_test.go (about) 1 // Copyright 2012-2024 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package server 15 16 import ( 17 "context" 18 "encoding/json" 19 "fmt" 20 "net" 21 "net/url" 22 "os" 23 "reflect" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/nats-io/jwt/v2" 29 "github.com/nats-io/nats.go" 30 ) 31 32 func TestUserCloneNilPermissions(t *testing.T) { 33 user := &User{ 34 Username: "foo", 35 Password: "bar", 36 } 37 38 clone := user.clone() 39 40 if !reflect.DeepEqual(user, clone) { 41 t.Fatalf("Cloned Users are incorrect.\nexpected: %+v\ngot: %+v", 42 user, clone) 43 } 44 45 clone.Password = "baz" 46 if reflect.DeepEqual(user, clone) { 47 t.Fatal("Expected Users to be different") 48 } 49 } 50 51 func TestUserClone(t *testing.T) { 52 user := &User{ 53 Username: "foo", 54 Password: "bar", 55 Permissions: &Permissions{ 56 Publish: &SubjectPermission{ 57 Allow: []string{"foo"}, 58 }, 59 Subscribe: &SubjectPermission{ 60 Allow: []string{"bar"}, 61 }, 62 }, 63 } 64 65 clone := user.clone() 66 67 if !reflect.DeepEqual(user, clone) { 68 t.Fatalf("Cloned Users are incorrect.\nexpected: %+v\ngot: %+v", 69 user, clone) 70 } 71 72 clone.Permissions.Subscribe.Allow = []string{"baz"} 73 if reflect.DeepEqual(user, clone) { 74 t.Fatal("Expected Users to be different") 75 } 76 } 77 78 func TestUserClonePermissionsNoLists(t *testing.T) { 79 user := &User{ 80 Username: "foo", 81 Password: "bar", 82 Permissions: &Permissions{}, 83 } 84 85 clone := user.clone() 86 87 if clone.Permissions.Publish != nil { 88 t.Fatalf("Expected Publish to be nil, got: %v", clone.Permissions.Publish) 89 } 90 if clone.Permissions.Subscribe != nil { 91 t.Fatalf("Expected Subscribe to be nil, got: %v", clone.Permissions.Subscribe) 92 } 93 } 94 95 func TestUserCloneNoPermissions(t *testing.T) { 96 user := &User{ 97 Username: "foo", 98 Password: "bar", 99 } 100 101 clone := user.clone() 102 103 if clone.Permissions != nil { 104 t.Fatalf("Expected Permissions to be nil, got: %v", clone.Permissions) 105 } 106 } 107 108 func TestUserCloneNil(t *testing.T) { 109 user := (*User)(nil) 110 clone := user.clone() 111 if clone != nil { 112 t.Fatalf("Expected nil, got: %+v", clone) 113 } 114 } 115 116 func TestUserUnknownAllowedConnectionType(t *testing.T) { 117 o := DefaultOptions() 118 o.Users = []*User{{ 119 Username: "user", 120 Password: "pwd", 121 AllowedConnectionTypes: testCreateAllowedConnectionTypes([]string{jwt.ConnectionTypeStandard, "someNewType"}), 122 }} 123 _, err := NewServer(o) 124 if err == nil || !strings.Contains(err.Error(), "connection type") { 125 t.Fatalf("Expected error about unknown connection type, got %v", err) 126 } 127 128 o.Users[0].AllowedConnectionTypes = testCreateAllowedConnectionTypes([]string{"websocket"}) 129 s, err := NewServer(o) 130 if err != nil { 131 t.Fatalf("Unexpected error: %v", err) 132 } 133 s.mu.Lock() 134 user := s.opts.Users[0] 135 s.mu.Unlock() 136 for act := range user.AllowedConnectionTypes { 137 if act != jwt.ConnectionTypeWebsocket { 138 t.Fatalf("Expected map to have been updated with proper case, got %v", act) 139 } 140 } 141 // Same with NKey user now. 142 o.Users = nil 143 o.Nkeys = []*NkeyUser{{ 144 Nkey: "somekey", 145 AllowedConnectionTypes: testCreateAllowedConnectionTypes([]string{jwt.ConnectionTypeStandard, "someNewType"}), 146 }} 147 _, err = NewServer(o) 148 if err == nil || !strings.Contains(err.Error(), "connection type") { 149 t.Fatalf("Expected error about unknown connection type, got %v", err) 150 } 151 o.Nkeys[0].AllowedConnectionTypes = testCreateAllowedConnectionTypes([]string{"websocket"}) 152 s, err = NewServer(o) 153 if err != nil { 154 t.Fatalf("Unexpected error: %v", err) 155 } 156 s.mu.Lock() 157 nkey := s.opts.Nkeys[0] 158 s.mu.Unlock() 159 for act := range nkey.AllowedConnectionTypes { 160 if act != jwt.ConnectionTypeWebsocket { 161 t.Fatalf("Expected map to have been updated with proper case, got %v", act) 162 } 163 } 164 } 165 166 func TestDNSAltNameMatching(t *testing.T) { 167 for idx, test := range []struct { 168 altName string 169 urls []string 170 match bool 171 }{ 172 {"foo", []string{"FOO"}, true}, 173 {"foo", []string{".."}, false}, 174 {"foo", []string{"."}, false}, 175 {"Foo", []string{"foO"}, true}, 176 {"FOO", []string{"foo"}, true}, 177 {"foo1", []string{"bar"}, false}, 178 {"multi", []string{"m", "mu", "mul", "multi"}, true}, 179 {"multi", []string{"multi", "m", "mu", "mul"}, true}, 180 {"foo.bar", []string{"foo", "foo.bar.bar", "foo.baz"}, false}, 181 {"foo.Bar", []string{"foo", "bar.foo", "Foo.Bar"}, true}, 182 {"foo.*", []string{"foo", "bar.foo", "Foo.Bar"}, false}, // only match left most 183 {"f*.bar", []string{"foo", "bar.foo", "Foo.Bar"}, false}, 184 {"*.bar", []string{"foo.bar"}, true}, 185 {"*", []string{"baz.bar", "bar", "z.y"}, true}, 186 {"*", []string{"bar"}, true}, 187 {"*", []string{"."}, false}, 188 {"*", []string{""}, true}, 189 {"*", []string{"*"}, true}, 190 {"bar.*", []string{"bar.*"}, true}, 191 {"*.Y-X-red-mgmt.default.svc", []string{"A.Y-X-red-mgmt.default.svc"}, true}, 192 {"*.Y-X-green-mgmt.default.svc", []string{"A.Y-X-green-mgmt.default.svc"}, true}, 193 {"*.Y-X-blue-mgmt.default.svc", []string{"A.Y-X-blue-mgmt.default.svc"}, true}, 194 {"Y-X-red-mgmt", []string{"Y-X-red-mgmt"}, true}, 195 {"Y-X-red-mgmt", []string{"X-X-red-mgmt"}, false}, 196 {"Y-X-red-mgmt", []string{"Y-X-green-mgmt"}, false}, 197 {"Y-X-red-mgmt", []string{"Y"}, false}, 198 {"Y-X-red-mgmt", []string{"Y-X"}, false}, 199 {"Y-X-red-mgmt", []string{"Y-X-red"}, false}, 200 {"Y-X-red-mgmt", []string{"X-red-mgmt"}, false}, 201 {"Y-X-green-mgmt", []string{"Y-X-green-mgmt"}, true}, 202 {"Y-X-blue-mgmt", []string{"Y-X-blue-mgmt"}, true}, 203 {"connect.Y.local", []string{"connect.Y.local"}, true}, 204 {"connect.Y.local", []string{".Y.local"}, false}, 205 {"connect.Y.local", []string{"..local"}, false}, 206 {"gcp.Y.local", []string{"gcp.Y.local"}, true}, 207 {"uswest1.gcp.Y.local", []string{"uswest1.gcp.Y.local"}, true}, 208 } { 209 urlSet := make([]*url.URL, len(test.urls)) 210 for i, u := range test.urls { 211 var err error 212 urlSet[i], err = url.Parse("nats://" + u) 213 if err != nil { 214 t.Fatal(err) 215 } 216 } 217 if dnsAltNameMatches(dnsAltNameLabels(test.altName), urlSet) != test.match { 218 t.Fatal("Test", idx, "Match miss match, expected:", test.match) 219 } 220 } 221 } 222 223 func TestNoAuthUser(t *testing.T) { 224 conf := createConfFile(t, []byte(` 225 listen: "127.0.0.1:-1" 226 accounts { 227 FOO { users [{user: "foo", password: "pwd1"}] } 228 BAR { users [{user: "bar", password: "pwd2"}] } 229 } 230 no_auth_user: "foo" 231 `)) 232 defer os.Remove(conf) 233 s, o := RunServerWithConfig(conf) 234 defer s.Shutdown() 235 236 for _, test := range []struct { 237 name string 238 usrInfo string 239 ok bool 240 account string 241 }{ 242 {"valid user/pwd", "bar:pwd2@", true, "BAR"}, 243 {"invalid pwd", "bar:wrong@", false, _EMPTY_}, 244 {"some token", "sometoken@", false, _EMPTY_}, 245 {"user used without pwd", "bar@", false, _EMPTY_}, // will be treated as a token 246 {"user with empty password", "bar:@", false, _EMPTY_}, 247 {"no user", _EMPTY_, true, "FOO"}, 248 } { 249 t.Run(test.name, func(t *testing.T) { 250 url := fmt.Sprintf("nats://%s127.0.0.1:%d", test.usrInfo, o.Port) 251 nc, err := nats.Connect(url) 252 if err != nil { 253 if test.ok { 254 t.Fatalf("Unexpected error: %v", err) 255 } 256 return 257 } else if !test.ok { 258 nc.Close() 259 t.Fatalf("Should have failed, did not") 260 } 261 var accName string 262 s.mu.Lock() 263 for _, c := range s.clients { 264 c.mu.Lock() 265 if c.acc != nil { 266 accName = c.acc.Name 267 } 268 c.mu.Unlock() 269 break 270 } 271 s.mu.Unlock() 272 nc.Close() 273 checkClientsCount(t, s, 0) 274 if accName != test.account { 275 t.Fatalf("The account should have been %q, got %q", test.account, accName) 276 } 277 }) 278 } 279 } 280 281 func TestNoAuthUserNkey(t *testing.T) { 282 conf := createConfFile(t, []byte(` 283 listen: "127.0.0.1:-1" 284 accounts { 285 FOO { users [{user: "foo", password: "pwd1"}] } 286 BAR { users [{nkey: "UBO2MQV67TQTVIRV3XFTEZOACM4WLOCMCDMAWN5QVN5PI2N6JHTVDRON"}] } 287 } 288 no_auth_user: "UBO2MQV67TQTVIRV3XFTEZOACM4WLOCMCDMAWN5QVN5PI2N6JHTVDRON" 289 `)) 290 s, _ := RunServerWithConfig(conf) 291 defer s.Shutdown() 292 293 // Make sure we connect ok and to the correct account. 294 nc := natsConnect(t, s.ClientURL()) 295 resp, err := nc.Request(userDirectInfoSubj, nil, time.Second) 296 require_NoError(t, err) 297 response := ServerAPIResponse{Data: &UserInfo{}} 298 err = json.Unmarshal(resp.Data, &response) 299 require_NoError(t, err) 300 userInfo := response.Data.(*UserInfo) 301 require_Equal(t, userInfo.UserID, "UBO2MQV67TQTVIRV3XFTEZOACM4WLOCMCDMAWN5QVN5PI2N6JHTVDRON") 302 require_Equal(t, userInfo.Account, "BAR") 303 } 304 305 func TestUserConnectionDeadline(t *testing.T) { 306 clientAuth := &DummyAuth{ 307 t: t, 308 register: true, 309 deadline: time.Now().Add(50 * time.Millisecond), 310 } 311 312 opts := DefaultOptions() 313 opts.CustomClientAuthentication = clientAuth 314 315 s := RunServer(opts) 316 defer s.Shutdown() 317 318 var dcerr error 319 320 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 321 322 nc, err := nats.Connect( 323 s.ClientURL(), 324 nats.UserInfo("valid", _EMPTY_), 325 nats.NoReconnect(), 326 nats.ErrorHandler(func(nc *nats.Conn, _ *nats.Subscription, err error) { 327 dcerr = err 328 cancel() 329 })) 330 if err != nil { 331 t.Fatalf("Expected client to connect, got: %s", err) 332 } 333 334 <-ctx.Done() 335 336 checkFor(t, 2*time.Second, 100*time.Millisecond, func() error { 337 if nc.IsConnected() { 338 return fmt.Errorf("Expected to be disconnected") 339 } 340 return nil 341 }) 342 343 if dcerr == nil || dcerr.Error() != "nats: authentication expired" { 344 t.Fatalf("Expected a auth expired error: got: %v", dcerr) 345 } 346 } 347 348 func TestNoAuthUserNoConnectProto(t *testing.T) { 349 conf := createConfFile(t, []byte(` 350 listen: "127.0.0.1:-1" 351 accounts { 352 A { users [{user: "foo", password: "pwd"}] } 353 } 354 authorization { timeout: 1 } 355 no_auth_user: "foo" 356 `)) 357 defer os.Remove(conf) 358 s, o := RunServerWithConfig(conf) 359 defer s.Shutdown() 360 361 checkClients := func(n int) { 362 t.Helper() 363 time.Sleep(100 * time.Millisecond) 364 if nc := s.NumClients(); nc != n { 365 t.Fatalf("Expected %d clients, got %d", n, nc) 366 } 367 } 368 369 conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", o.Host, o.Port)) 370 require_NoError(t, err) 371 defer conn.Close() 372 checkClientsCount(t, s, 1) 373 374 // With no auth user we should not require a CONNECT. 375 // Make sure we are good on not sending CONN first. 376 _, err = conn.Write([]byte("PUB foo 2\r\nok\r\n")) 377 require_NoError(t, err) 378 checkClients(1) 379 conn.Close() 380 381 // Now make sure we still do get timed out though. 382 conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", o.Host, o.Port)) 383 require_NoError(t, err) 384 defer conn.Close() 385 checkClientsCount(t, s, 1) 386 387 time.Sleep(1200 * time.Millisecond) 388 checkClientsCount(t, s, 0) 389 }