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  }