get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/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  }