Skip to content

Commit

Permalink
security: clear client cert expiration cache on SIGHUP
Browse files Browse the repository at this point in the history
Release note (security update): The SIGHUP signal will now clear the
cached expiration times for client certificates that are reported by
the security.certificate.expiration.client metric.
  • Loading branch information
rafiss committed Sep 16, 2023
1 parent f2bbea9 commit 17e8e3c
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 8 deletions.
1 change: 1 addition & 0 deletions pkg/security/cert_expiry_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func NewClientCertExpirationCache(
},
OnEvictedEntry: func(entry *cache.Entry) {
gauge := entry.Value.(*aggmetric.Gauge)
gauge.Update(0)
gauge.Unlink()
c.mu.acc.Shrink(ctx, int64(unsafe.Sizeof(*gauge)))
},
Expand Down
3 changes: 3 additions & 0 deletions pkg/security/certificate_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ func (cm *CertificateManager) RegisterSignalHandler(
return
case sig := <-ch:
log.Ops.Infof(ctx, "received signal %q, triggering certificate reload", sig)
if cache := cm.clientCertExpirationCache; cache != nil {
cache.Clear()
}
if err := cm.LoadCertificates(); err != nil {
log.Ops.Warningf(ctx, "could not reload certificates: %v", err)
log.StructuredEvent(ctx, &eventpb.CertsReload{Success: false, ErrorMessage: err.Error()})
Expand Down
42 changes: 38 additions & 4 deletions pkg/security/certs_rotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"regexp"
"strings"
"testing"
"time"

"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities/tenantcapabilitiesauthorizer"
Expand All @@ -41,6 +42,8 @@ import (
"github.com/cockroachdb/cockroach/pkg/util/log/eventpb"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
"github.com/cockroachdb/errors"
prometheusgo "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)

Expand All @@ -56,7 +59,7 @@ func TestRotateCerts(t *testing.T) {
defer ResetTest()
certsDir := t.TempDir()

if err := generateBaseCerts(certsDir); err != nil {
if err := generateBaseCerts(certsDir, 48*time.Hour); err != nil {
t.Fatal(err)
}

Expand Down Expand Up @@ -103,6 +106,22 @@ func TestRotateCerts(t *testing.T) {
return goDB
}

checkCertExpirationMetric := func() int64 {
cm, err := s.RPCContext().SecurityContext.GetCertificateManager()
if err != nil {
t.Fatal(err)
}
ret := int64(0)
cm.Metrics().ClientExpiration.Each(nil, func(pm *prometheusgo.Metric) {
for _, l := range pm.GetLabel() {
if l.GetName() == "sql_user" && l.GetValue() == username.RootUser {
ret = int64(pm.GetGauge().GetValue())
}
}
})
return ret
}

// Some errors codes.
const kBadAuthority = "certificate signed by unknown authority"
const kBadCertificate = "tls: bad certificate"
Expand All @@ -120,6 +139,9 @@ func TestRotateCerts(t *testing.T) {
t.Fatalf("could not create http client: %v", err)
}

// Before any client has connected, the expiration metric should be zero.
require.Zero(t, checkCertExpirationMetric())

if err := clientTest(firstClient); err != nil {
t.Fatal(err)
}
Expand All @@ -131,12 +153,15 @@ func TestRotateCerts(t *testing.T) {
t.Fatal(err)
}

// Delete certs and re-generate them.
// Now the expiration metric should be set.
require.Greater(t, checkCertExpirationMetric(), timeutil.Now().Add(40*time.Hour).Unix())

// Delete certs and re-generate them with a longer client cert lifetime.
// New clients will fail with CA errors.
if err := os.RemoveAll(certsDir); err != nil {
t.Fatal(err)
}
if err := generateBaseCerts(certsDir); err != nil {
if err := generateBaseCerts(certsDir, 54*time.Hour); err != nil {
t.Fatal(err)
}

Expand Down Expand Up @@ -197,6 +222,9 @@ func TestRotateCerts(t *testing.T) {
return nil
})

// The expiration metric should be zero after the SIGHUP.
require.Zero(t, checkCertExpirationMetric())

// Check that the structured event was logged.
// We use SucceedsSoon here because there may be a delay between
// the moment SIGHUP is processed and certs are reloaded, and
Expand Down Expand Up @@ -244,13 +272,16 @@ func TestRotateCerts(t *testing.T) {
t.Fatal(err)
}

// The expiration metric should be set again, but should now show a higher value.
require.Greater(t, checkCertExpirationMetric(), timeutil.Now().Add(50*time.Hour).Unix())

// Now regenerate certs, but keep the CA cert around.
// We still need to delete the key.
// New clients with certs will fail with bad certificate (CA not yet loaded).
if err := os.Remove(filepath.Join(certsDir, certnames.EmbeddedCAKey)); err != nil {
t.Fatal(err)
}
if err := generateBaseCerts(certsDir); err != nil {
if err := generateBaseCerts(certsDir, 58*time.Hour); err != nil {
t.Fatal(err)
}

Expand Down Expand Up @@ -302,6 +333,9 @@ func TestRotateCerts(t *testing.T) {
}
return nil
})

// The expiration metric should be updated to be larger again.
require.Greater(t, checkCertExpirationMetric(), timeutil.Now().Add(57*time.Hour).Unix())
}

var cmLogRe = regexp.MustCompile(`event_log\.go`)
8 changes: 4 additions & 4 deletions pkg/security/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func TestGenerateNodeCerts(t *testing.T) {
// client.root.crt: client certificate for the root user.
// client-tenant.10.crt: tenant client certificate for tenant 10.
// tenant-signing.10.crt: tenant signing certificate for tenant 10.
func generateBaseCerts(certsDir string) error {
func generateBaseCerts(certsDir string, clientCertLifetime time.Duration) error {
{
caKey := filepath.Join(certsDir, certnames.EmbeddedCAKey)

Expand All @@ -286,7 +286,7 @@ func generateBaseCerts(certsDir string) error {
certsDir,
caKey,
testKeySize,
time.Hour*48,
clientCertLifetime,
true,
username.RootUserName(),
[]roachpb.TenantID{roachpb.SystemTenantID},
Expand Down Expand Up @@ -329,7 +329,7 @@ func generateBaseCerts(certsDir string) error {
// client.node.crt: node client cert: signed by ca-client.crt
// client.root.crt: root client cert: signed by ca-client.crt
func generateSplitCACerts(certsDir string) error {
if err := generateBaseCerts(certsDir); err != nil {
if err := generateBaseCerts(certsDir, 48*time.Hour); err != nil {
return err
}

Expand Down Expand Up @@ -384,7 +384,7 @@ func TestUseCerts(t *testing.T) {
defer ResetTest()
certsDir := t.TempDir()

if err := generateBaseCerts(certsDir); err != nil {
if err := generateBaseCerts(certsDir, 48*time.Hour); err != nil {
t.Fatal(err)
}

Expand Down

0 comments on commit 17e8e3c

Please sign in to comment.