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 }