github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/user_test.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package sql_test
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"net/url"
    17  	"os"
    18  	"sync/atomic"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/base"
    23  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
    24  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    25  	"github.com/cockroachdb/cockroach/pkg/security"
    26  	"github.com/cockroachdb/cockroach/pkg/testutils"
    27  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    29  	"github.com/cockroachdb/cockroach/pkg/util"
    30  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    31  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    32  	"github.com/jackc/pgx"
    33  	"github.com/jackc/pgx/pgtype"
    34  )
    35  
    36  // TestGetUserHashedPasswordTimeout verifies that user login attempts
    37  // fail with a suitable timeout when some system range(s) are
    38  // unavailable.
    39  //
    40  // To achieve this it creates a 2-node cluster, moves all ranges
    41  // from node 1 to node 2, then stops node 2, then attempts
    42  // to connect to node 1.
    43  func TestGetUserHashedPasswordTimeout(t *testing.T) {
    44  	defer leaktest.AfterTest(t)()
    45  
    46  	if util.RaceEnabled {
    47  		// We want to use a low timeout below to prevent
    48  		// this test from taking forever, however
    49  		// race builds are so slow as to trigger this timeout spuriously.
    50  		t.Skip("not running under race")
    51  	}
    52  
    53  	ctx := context.Background()
    54  
    55  	// unavailableCh is used by the replica command filter
    56  	// to conditionally block requests and simulate unavailability.
    57  	var unavailableCh atomic.Value
    58  	closedCh := make(chan struct{})
    59  	close(closedCh)
    60  	unavailableCh.Store(closedCh)
    61  	knobs := &kvserver.StoreTestingKnobs{
    62  		TestingRequestFilter: func(ctx context.Context, _ roachpb.BatchRequest) *roachpb.Error {
    63  			select {
    64  			case <-unavailableCh.Load().(chan struct{}):
    65  			case <-ctx.Done():
    66  			}
    67  			return nil
    68  		},
    69  	}
    70  	params := base.TestServerArgs{Knobs: base.TestingKnobs{Store: knobs}}
    71  	s, db, _ := serverutils.StartServer(t, params)
    72  	defer s.Stopper().Stop(ctx)
    73  
    74  	// Make a user that must use a password to authenticate.
    75  	// Default privileges on defaultdb are needed to run simple queries.
    76  	if _, err := db.Exec(`
    77  CREATE USER foo WITH PASSWORD 'testabc';
    78  GRANT ALL ON DATABASE defaultdb TO foo`); err != nil {
    79  		t.Fatal(err)
    80  	}
    81  
    82  	// We'll attempt connections on gateway node 0.
    83  	userURL, cleanupFn := sqlutils.PGUrlWithOptionalClientCerts(t,
    84  		s.ServingSQLAddr(), t.Name(), url.UserPassword("foo", "testabc"), false /* withClientCerts */)
    85  	defer cleanupFn()
    86  	rootURL, rootCleanupFn := sqlutils.PGUrl(t,
    87  		s.ServingSQLAddr(), t.Name(), url.User(security.RootUser))
    88  	defer rootCleanupFn()
    89  
    90  	// Override the timeout built into pgx so we are only subject to
    91  	// what the server thinks.
    92  	userURL.RawQuery += "&connect_timeout=0"
    93  	rootURL.RawQuery += "&connect_timeout=0"
    94  
    95  	fmt.Fprintln(os.Stderr, "-- sanity checks --")
    96  
    97  	// We use a closure here and below to ensure the defers are run
    98  	// before the rest of the test.
    99  
   100  	func() {
   101  		// Sanity check: verify that secure mode is enabled: password is
   102  		// required. If this part fails, this means the test cluster is
   103  		// not properly configured, and the remainder of the test below
   104  		// would report false positives.
   105  		unauthURL := userURL
   106  		unauthURL.User = url.User("foo")
   107  		dbSQL, err := pgxConn(t, unauthURL)
   108  		if err == nil {
   109  			defer func() { _ = dbSQL.Close() }()
   110  		}
   111  		if !testutils.IsError(err, "password authentication failed for user foo") {
   112  			t.Fatalf("expected password error, got %v", err)
   113  		}
   114  	}()
   115  
   116  	func() {
   117  		// Sanity check: verify that the new user is able to log in with password.
   118  		dbSQL, err := pgxConn(t, userURL)
   119  		if err != nil {
   120  			t.Fatal(err)
   121  		}
   122  		defer func() { _ = dbSQL.Close() }()
   123  		row := dbSQL.QueryRow("SELECT current_user")
   124  		var username string
   125  		if err := row.Scan(&username); err != nil {
   126  			t.Fatal(err)
   127  		}
   128  		if username != "foo" {
   129  			t.Fatalf("invalid username: expected foo, got %q", username)
   130  		}
   131  	}()
   132  
   133  	// Configure the login timeout to just 1s.
   134  	if _, err := db.Exec(`SET CLUSTER SETTING server.user_login.timeout = '200ms'`); err != nil {
   135  		t.Fatal(err)
   136  	}
   137  
   138  	fmt.Fprintln(os.Stderr, "-- make ranges unavailable --")
   139  
   140  	ch := make(chan struct{})
   141  	unavailableCh.Store(ch)
   142  	defer close(ch)
   143  
   144  	fmt.Fprintln(os.Stderr, "-- expect timeout --")
   145  
   146  	func() {
   147  		// Now attempt to connect again. We're expecting a timeout within 5 seconds.
   148  		start := timeutil.Now()
   149  		dbSQL, err := pgxConn(t, userURL)
   150  		if err == nil {
   151  			defer func() { _ = dbSQL.Close() }()
   152  		}
   153  		if !testutils.IsError(err, "internal error while retrieving user account") {
   154  			t.Fatalf("expected error during connection, got %v", err)
   155  		}
   156  		timeoutDur := timeutil.Now().Sub(start)
   157  		if timeoutDur > 5*time.Second {
   158  			t.Fatalf("timeout lasted for more than 5 second (%s)", timeoutDur)
   159  		}
   160  	}()
   161  
   162  	fmt.Fprintln(os.Stderr, "-- no timeout for root --")
   163  
   164  	func() {
   165  		dbSQL, err := pgxConn(t, rootURL)
   166  		if err != nil {
   167  			t.Fatal(err)
   168  		}
   169  		defer func() { _ = dbSQL.Close() }()
   170  		// A simple query must work for 'root' even without a system range available.
   171  		if _, err := dbSQL.Exec("SELECT 1"); err != nil {
   172  			t.Fatal(err)
   173  		}
   174  	}()
   175  }
   176  
   177  func pgxConn(t *testing.T, connURL url.URL) (*pgx.Conn, error) {
   178  	pgxConfig, err := pgx.ParseConnectionString(connURL.String())
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  
   183  	// Override the conninfo to avoid a bunch of pg_catalog
   184  	// queries when the connection is being set up.
   185  	pgxConfig.CustomConnInfo = func(c *pgx.Conn) (*pgtype.ConnInfo, error) {
   186  		return c.ConnInfo, nil
   187  	}
   188  
   189  	return pgx.Connect(pgxConfig)
   190  }