github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/security/certs_rotation_test.go (about) 1 // Copyright 2017 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 // +build !windows 12 13 package security_test 14 15 import ( 16 "context" 17 gosql "database/sql" 18 "io/ioutil" 19 "net/http" 20 "os" 21 "path/filepath" 22 "testing" 23 24 "github.com/cockroachdb/cockroach/pkg/base" 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/util/leaktest" 29 "github.com/cockroachdb/errors" 30 "golang.org/x/sys/unix" 31 ) 32 33 // TestRotateCerts tests certs rotation in the server. 34 // TODO(marc): enable this test on windows once we support a non-signal method 35 // of triggering a certificate refresh. 36 func TestRotateCerts(t *testing.T) { 37 defer leaktest.AfterTest(t)() 38 // Do not mock cert access for this test. 39 security.ResetAssetLoader() 40 defer ResetTest() 41 certsDir, err := ioutil.TempDir("", "certs_test") 42 if err != nil { 43 t.Fatal(err) 44 } 45 defer func() { 46 if err := os.RemoveAll(certsDir); err != nil { 47 t.Fatal(err) 48 } 49 }() 50 51 if err := generateBaseCerts(certsDir); err != nil { 52 t.Fatal(err) 53 } 54 55 // Start a test server with first set of certs. 56 // Web session authentication is disabled in order to avoid the need to 57 // authenticate the individual clients being instantiated (session auth has 58 // no effect on what is being tested here). 59 params := base.TestServerArgs{ 60 SSLCertsDir: certsDir, 61 DisableWebSessionAuthentication: true, 62 } 63 s, _, _ := serverutils.StartServer(t, params) 64 defer s.Stopper().Stop(context.Background()) 65 66 // Client test function. 67 clientTest := func(httpClient http.Client) error { 68 req, err := http.NewRequest("GET", s.AdminURL()+"/_status/metrics/local", nil) 69 if err != nil { 70 return errors.Errorf("could not create request: %v", err) 71 } 72 resp, err := httpClient.Do(req) 73 if err != nil { 74 return errors.Errorf("http request failed: %v", err) 75 } 76 defer resp.Body.Close() 77 if resp.StatusCode != http.StatusOK { 78 body, _ := ioutil.ReadAll(resp.Body) 79 return errors.Errorf("Expected OK, got %q with body: %s", resp.Status, body) 80 } 81 return nil 82 } 83 84 // Create a client by calling sql.Open which loads the certificates but do not use it yet. 85 createTestClient := func() *gosql.DB { 86 pgUrl := makeSecurePGUrl(s.ServingSQLAddr(), security.RootUser, certsDir, security.EmbeddedCACert, security.EmbeddedRootCert, security.EmbeddedRootKey) 87 goDB, err := gosql.Open("postgres", pgUrl) 88 if err != nil { 89 t.Fatal(err) 90 } 91 return goDB 92 } 93 94 // Some errors codes. 95 const kBadAuthority = "certificate signed by unknown authority" 96 const kBadCertificate = "tls: bad certificate" 97 98 // Test client with the same certs. 99 clientContext := testutils.NewNodeTestBaseContext() 100 clientContext.SSLCertsDir = certsDir 101 firstClient, err := clientContext.GetHTTPClient() 102 if err != nil { 103 t.Fatalf("could not create http client: %v", err) 104 } 105 106 if err := clientTest(firstClient); err != nil { 107 t.Fatal(err) 108 } 109 110 firstSQLClient := createTestClient() 111 defer firstSQLClient.Close() 112 113 if _, err := firstSQLClient.Exec("SELECT 1"); err != nil { 114 t.Fatal(err) 115 } 116 117 // Delete certs and re-generate them. 118 // New clients will fail with CA errors. 119 if err := os.RemoveAll(certsDir); err != nil { 120 t.Fatal(err) 121 } 122 if err := generateBaseCerts(certsDir); err != nil { 123 t.Fatal(err) 124 } 125 126 // Setup a second http client. It will load the new certs. 127 // We need to use a new context as it keeps the certificate manager around. 128 // Fails on crypto errors. 129 clientContext = testutils.NewNodeTestBaseContext() 130 clientContext.SSLCertsDir = certsDir 131 secondClient, err := clientContext.GetHTTPClient() 132 if err != nil { 133 t.Fatalf("could not create http client: %v", err) 134 } 135 136 if err := clientTest(secondClient); !testutils.IsError(err, kBadAuthority) { 137 t.Fatalf("expected error %q, got: %q", kBadAuthority, err) 138 } 139 140 secondSQLClient := createTestClient() 141 defer secondSQLClient.Close() 142 143 if _, err := secondSQLClient.Exec("SELECT 1"); !testutils.IsError(err, kBadAuthority) { 144 t.Fatalf("expected error %q, got: %q", kBadAuthority, err) 145 } 146 147 // We haven't triggered the reload, first client should still work. 148 if err := clientTest(firstClient); err != nil { 149 t.Fatal(err) 150 } 151 152 if _, err := firstSQLClient.Exec("SELECT 1"); err != nil { 153 t.Fatal(err) 154 } 155 156 t.Log("issuing SIGHUP") 157 if err := unix.Kill(unix.Getpid(), unix.SIGHUP); err != nil { 158 t.Fatal(err) 159 } 160 161 // Try again, the first HTTP client should now fail, the second should succeed. 162 testutils.SucceedsSoon(t, 163 func() error { 164 if err := clientTest(firstClient); !testutils.IsError(err, "unknown authority") { 165 return errors.Errorf("expected unknown authority, got %v", err) 166 } 167 168 if err := clientTest(secondClient); err != nil { 169 return err 170 } 171 return nil 172 }) 173 174 // Nothing changed in the first SQL client: the connection is already established. 175 if _, err := firstSQLClient.Exec("SELECT 1"); err != nil { 176 t.Fatal(err) 177 } 178 179 // However, the second SQL client now succeeds. 180 if _, err := secondSQLClient.Exec("SELECT 1"); err != nil { 181 t.Fatal(err) 182 } 183 184 // Now regenerate certs, but keep the CA cert around. 185 // We still need to delete the key. 186 // New clients with certs will fail with bad certificate (CA not yet loaded). 187 if err := os.Remove(filepath.Join(certsDir, security.EmbeddedCAKey)); err != nil { 188 t.Fatal(err) 189 } 190 if err := generateBaseCerts(certsDir); err != nil { 191 t.Fatal(err) 192 } 193 194 // Setup a third http client. It will load the new certs. 195 // We need to use a new context as it keeps the certificate manager around. 196 // This is HTTP and succeeds because we do not ask for or verify client certificates. 197 clientContext = testutils.NewNodeTestBaseContext() 198 clientContext.SSLCertsDir = certsDir 199 thirdClient, err := clientContext.GetHTTPClient() 200 if err != nil { 201 t.Fatalf("could not create http client: %v", err) 202 } 203 204 if err := clientTest(thirdClient); err != nil { 205 t.Fatal(err) 206 } 207 208 // However, a SQL client uses client certificates. The node does not have the new CA yet. 209 thirdSQLClient := createTestClient() 210 defer thirdSQLClient.Close() 211 212 if _, err := thirdSQLClient.Exec("SELECT 1"); !testutils.IsError(err, kBadCertificate) { 213 t.Fatalf("expected error %q, got: %q", kBadCertificate, err) 214 } 215 216 // We haven't triggered the reload, second client should still work. 217 if err := clientTest(secondClient); err != nil { 218 t.Fatal(err) 219 } 220 221 t.Log("issuing SIGHUP") 222 if err := unix.Kill(unix.Getpid(), unix.SIGHUP); err != nil { 223 t.Fatal(err) 224 } 225 226 // Wait until client3 succeeds (both http and sql). 227 testutils.SucceedsSoon(t, 228 func() error { 229 if err := clientTest(thirdClient); err != nil { 230 return errors.Errorf("third HTTP client failed: %v", err) 231 } 232 if _, err := thirdSQLClient.Exec("SELECT 1"); err != nil { 233 return errors.Errorf("third SQL client failed: %v", err) 234 } 235 return nil 236 }) 237 }