Add more logging to worker auth rotation and test flags

This makes it much easier to figure out when and why rotation is
happening and control it for tests/debugging.

Remove cert skew

Cherry-picked from release/0.12.x
This commit is contained in:
Jeff Mitchell committed 2023-05-19 17:14:33 -04:00
1 parent 253d191440
commit 77fd51fb12
10 files changed
+327 -69

No files matched your search

+49 -45
View File
@@ -65,45 +65,46 @@ type Command struct {
controller *controller.Controller
worker *worker.Worker
flagLogLevel string
flagLogFormat string
flagCombineLogs bool
flagLoginName string
flagPassword string
flagUnprivilegedLoginName string
flagUnprivilegedPassword string
flagIdSuffix string
flagSecondaryIdSuffix string
flagHostAddress string
flagTargetDefaultPort int
flagTargetSessionMaxSeconds int
flagTargetSessionConnectionLimit int
flagControllerAPIListenAddr string
flagControllerClusterListenAddr string
flagControllerPublicClusterAddr string
flagControllerOnly bool
flagWorkerAuthKey string
flagWorkerProxyListenAddr string
flagWorkerPublicAddr string
flagOpsListenAddr string
flagUiPassthroughDir string
flagRecoveryKey string
flagDatabaseUrl string
flagContainerImage string
flagDisableDatabaseDestruction bool
flagEventFormat string
flagAudit string
flagObservations string
flagSysEvents string
flagEveryEventAllowFilters []string
flagEveryEventDenyFilters []string
flagCreateLoopbackHostPlugin bool
flagPluginExecutionDir string
flagWorkerAuthMethod string
flagWorkerAuthStorageDir string
flagWorkerAuthStorageSkipCleanup bool
flagWorkerAuthRotationInterval time.Duration
flagWorkerAuthDebuggingEnabled bool
flagLogLevel string
flagLogFormat string
flagCombineLogs bool
flagLoginName string
flagPassword string
flagUnprivilegedLoginName string
flagUnprivilegedPassword string
flagIdSuffix string
flagSecondaryIdSuffix string
flagHostAddress string
flagTargetDefaultPort int
flagTargetSessionMaxSeconds int
flagTargetSessionConnectionLimit int
flagControllerAPIListenAddr string
flagControllerClusterListenAddr string
flagControllerPublicClusterAddr string
flagControllerOnly bool
flagWorkerAuthKey string
flagWorkerProxyListenAddr string
flagWorkerPublicAddr string
flagOpsListenAddr string
flagUiPassthroughDir string
flagRecoveryKey string
flagDatabaseUrl string
flagContainerImage string
flagDisableDatabaseDestruction bool
flagEventFormat string
flagAudit string
flagObservations string
flagSysEvents string
flagEveryEventAllowFilters []string
flagEveryEventDenyFilters []string
flagCreateLoopbackHostPlugin bool
flagPluginExecutionDir string
flagWorkerAuthMethod string
flagWorkerAuthStorageDir string
flagWorkerAuthStorageSkipCleanup bool
flagWorkerAuthWorkerRotationInterval time.Duration
flagWorkerAuthCaCertificateLifetime time.Duration
flagWorkerAuthDebuggingEnabled bool
}
func (c *Command) Synopsis() string {
@@ -385,8 +386,13 @@ func (c *Command) Flags() *base.FlagSets {
})
f.DurationVar(&base.DurationVar{
Name: "worker-auth-rotation-interval",
Target: &c.flagWorkerAuthRotationInterval,
Name: "worker-auth-worker-rotation-interval",
Target: &c.flagWorkerAuthWorkerRotationInterval,
Hidden: true,
})
f.DurationVar(&base.DurationVar{
Name: "worker-auth-ca-certificate-lifetime",
Target: &c.flagWorkerAuthCaCertificateLifetime,
Hidden: true,
})
@@ -708,6 +714,7 @@ func (c *Command) Run(args []string) int {
conf := &controller.Config{
RawConfig: c.Config,
Server: c.Server,
TestOverrideWorkerAuthCaCertificateLifetime: c.flagWorkerAuthCaCertificateLifetime,
}
var err error
@@ -742,10 +749,7 @@ func (c *Command) Run(args []string) int {
c.UI.Error(fmt.Errorf("Error initializing worker: %w", err).Error())
return base.CommandCliError
}
if c.flagWorkerAuthRotationInterval > 0 {
c.worker.TestOverrideAuthRotationPeriod = c.flagWorkerAuthRotationInterval
}
c.worker.TestOverrideAuthRotationPeriod = c.flagWorkerAuthWorkerRotationInterval
if c.flagWorkerAuthMethod == PkiRandomWorkerAuthMechanism {
// Flip a coin. Use one method or the other; it's transparent to
+18 -3
View File
@@ -62,9 +62,11 @@ type Command struct {
flagLogFormat string
flagCombineLogs bool
reloadedCh chan struct{} // for tests
startedCh chan struct{} // for tests
presetConfig *atomic.String // for tests
reloadedCh chan struct{} // for tests
startedCh chan struct{} // for tests
presetConfig *atomic.String // for tests
flagWorkerAuthWorkerRotationInterval time.Duration // for tests
flagWorkerAuthCaCertificateLifetime time.Duration // for tests
}
func (c *Command) Synopsis() string {
@@ -126,6 +128,17 @@ func (c *Command) Flags() *base.FlagSets {
Usage: `Log format, mostly as a fallback for events. Supported values are "standard" and "json".`,
})
f.DurationVar(&base.DurationVar{
Name: "worker-auth-worker-rotation-interval",
Target: &c.flagWorkerAuthWorkerRotationInterval,
Hidden: true,
})
f.DurationVar(&base.DurationVar{
Name: "worker-auth-ca-certificate-lifetime",
Target: &c.flagWorkerAuthCaCertificateLifetime,
Hidden: true,
})
return set
}
@@ -583,6 +596,7 @@ func (c *Command) StartController(ctx context.Context) error {
conf := &controller.Config{
RawConfig: c.Config,
Server: c.Server,
TestOverrideWorkerAuthCaCertificateLifetime: c.flagWorkerAuthCaCertificateLifetime,
}
var err error
@@ -614,6 +628,7 @@ func (c *Command) StartWorker() error {
if err != nil {
return fmt.Errorf("Error initializing worker: %w", err)
}
c.worker.TestOverrideAuthRotationPeriod = c.flagWorkerAuthWorkerRotationInterval
if err := c.worker.Start(); err != nil {
retErr := fmt.Errorf("Error starting worker: %w", err)
+4
View File
@@ -4,6 +4,8 @@
package controller
import (
"time"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
)
@@ -17,4 +19,6 @@ type Config struct {
RawConfig *config.Config
// If set, authorization checking occurrs but failures are ignored
DisableAuthorizationFailures bool
// Override worker auth CA certificate lifetime for testing
TestOverrideWorkerAuthCaCertificateLifetime time.Duration
}
+10 -2
View File
@@ -47,6 +47,7 @@ import (
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/mlock"
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
"github.com/hashicorp/nodeenrollment"
ua "go.uber.org/atomic"
"google.golang.org/grpc"
)
@@ -392,7 +393,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
if err != nil {
return nil, fmt.Errorf("unable to instantiate worker auth repository: %w", err)
}
_, err = server.RotateRoots(ctx, serversRepo)
_, err = server.RotateRoots(ctx, serversRepo, nodeenrollment.WithCertificateLifetime(conf.TestOverrideWorkerAuthCaCertificateLifetime))
if err != nil {
return nil, fmt.Errorf("unable to ensure worker auth roots exist: %w", err)
}
@@ -518,7 +519,14 @@ func (c *Controller) registerJobs() error {
if err := session.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms, c.workerStatusGracePeriod); err != nil {
return err
}
if err := serversjob.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms); err != nil {
var serverJobOpts []serversjob.Option
if c.conf.TestOverrideWorkerAuthCaCertificateLifetime > 0 {
serverJobOpts = append(serverJobOpts,
serversjob.WithCertificateLifetime(c.conf.TestOverrideWorkerAuthCaCertificateLifetime),
serversjob.WithRotationFrequency(c.conf.TestOverrideWorkerAuthCaCertificateLifetime/2),
)
}
if err := serversjob.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms, serverJobOpts...); err != nil {
return err
}
if err := kmsjob.RegisterJobs(c.baseContext, c.scheduler, c.kms); err != nil {
+111 -5
View File
@@ -5,7 +5,9 @@ package worker
import (
"context"
"crypto/x509"
"errors"
"fmt"
"time"
berrors "github.com/hashicorp/boundary/internal/errors"
@@ -70,24 +72,77 @@ func (w *Worker) startAuthRotationTicking(cancelCtx context.Context) {
// Check the certificates to see if it's time
if len(currentNodeCreds.CertificateBundles) != 2 {
// Likely this means we haven't been authorized yet, and
// unclear what to do in any other situation
// Likely this means we haven't been authorized yet, and unclear
// what to do in any other situation. However, we should try
// again fairly quickly because this may also be prior to the
// first rotation and we want to ensure the validity period is
// checked soon.
resetDuration = 5 * time.Second
continue
}
var earliestValid, latestValid time.Time
// Go through the bundles and find the full range of time that we
// have valid certificates
for _, bundle := range currentNodeCreds.CertificateBundles {
var args []any
for i, bundle := range currentNodeCreds.CertificateBundles {
if curr := bundle.CertificateNotBefore.AsTime(); earliestValid.IsZero() || curr.Before(earliestValid) {
earliestValid = curr
}
if curr := bundle.CertificateNotAfter.AsTime(); latestValid.IsZero() || curr.After(latestValid) {
latestValid = curr
}
cert, err := x509.ParseCertificate(bundle.CertificateDer)
if err != nil {
event.WriteError(cancelCtx, op, fmt.Errorf("error parsing leaf certificate in current worker auth bundle: %w", err))
continue
}
certPkixBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
event.WriteError(cancelCtx, op, fmt.Errorf("error marshaling leaf certificate public key in current worker auth bundle: %w", err))
continue
}
certKeyId, err := nodeenrollment.KeyIdFromPkix(certPkixBytes)
if err != nil {
event.WriteError(cancelCtx, op, fmt.Errorf("error deriving pkix string from leaf certificate public key in current worker auth bundle: %w", err))
continue
}
args = append(args,
fmt.Sprintf("leaf_cert_%d_id", i), certKeyId,
fmt.Sprintf("leaf_cert_%d_not_before", i), cert.NotBefore.Format(time.RFC3339),
fmt.Sprintf("leaf_cert_%d_not_after", i), cert.NotAfter.Format(time.RFC3339),
)
caCert, err := x509.ParseCertificate(bundle.CaCertificateDer)
if err != nil {
event.WriteError(cancelCtx, op, fmt.Errorf("error parsing CA certificate in current worker auth bundle: %w", err))
continue
}
caCertPkixBytes, err := x509.MarshalPKIXPublicKey(caCert.PublicKey)
if err != nil {
event.WriteError(cancelCtx, op, fmt.Errorf("error marshaling CA certificate public key in current worker auth bundle: %w", err))
continue
}
caCertKeyId, err := nodeenrollment.KeyIdFromPkix(caCertPkixBytes)
if err != nil {
event.WriteError(cancelCtx, op, fmt.Errorf("error deriving pkix string from CA certificate public key in current worker auth bundle: %w", err))
continue
}
args = append(args,
fmt.Sprintf("ca_cert_%d_id", i), caCertKeyId,
fmt.Sprintf("ca_cert_%d_not_before", i), caCert.NotBefore.Format(time.RFC3339),
fmt.Sprintf("ca_cert_%d_not_after", i), caCert.NotAfter.Format(time.RFC3339),
)
}
// Ensure that our time periods aren't somehow quite messed up
now := time.Now()
event.WriteSysEvent(cancelCtx, op, "checking if worker auth should rotate", append(
args,
"now", now.Format(time.RFC3339),
"earliest_valid", earliestValid.Format(time.RFC3339),
"latest_valid", latestValid.Format(time.RFC3339),
)...,
)
if earliestValid.After(now) || latestValid.Before(now) || earliestValid.After(latestValid) {
// We basically have no valid creds so we can't rotate.
// TODO (maybe): Have this trigger a new set of creds and request?
@@ -97,15 +152,21 @@ func (w *Worker) startAuthRotationTicking(cancelCtx context.Context) {
// Figure out the midpoint; if we're after it, try to rotate
var shouldRotate bool
delta := latestValid.Sub(earliestValid)
switch {
case w.TestOverrideAuthRotationPeriod != 0:
shouldRotate = true
default:
delta := latestValid.Sub(earliestValid)
resetDuration = latestValid.Sub(now) / 2
shouldRotate = now.After(earliestValid.Add(delta / 2))
}
if !shouldRotate {
event.WriteSysEvent(cancelCtx, op, "not time to rotate yet",
"now", now.Format(time.RFC3339),
"midpoint", earliestValid.Add(delta/2).Format(time.RFC3339),
"next_check", now.Add(resetDuration).Format(time.RFC3339),
)
continue
}
@@ -113,7 +174,7 @@ func (w *Worker) startAuthRotationTicking(cancelCtx context.Context) {
event.WriteError(cancelCtx, op, err)
continue
}
event.WriteSysEvent(cancelCtx, op, "worker credentials rotated")
event.WriteSysEvent(cancelCtx, op, "worker credentials rotated", "next_check", now.Add(resetDuration).Format(time.RFC3339))
// TODO (maybe): Calculate new delta and set a custom retry time on the timer?
}
@@ -207,5 +268,50 @@ func rotateWorkerAuth(ctx context.Context, w *Worker, currentNodeCreds *types.No
}
w.WorkerAuthCurrentKeyId.Store(newKeyId)
var args []any
for i, bundle := range newNodeCreds.GetCertificateBundles() {
cert, err := x509.ParseCertificate(bundle.CertificateDer)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error parsing leaf certificate in new worker auth bundle: %w", err))
continue
}
certPkixBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error marshaling leaf certificate public key in new worker auth bundle: %w", err))
continue
}
certKeyId, err := nodeenrollment.KeyIdFromPkix(certPkixBytes)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error deriving pkix string from leaf certificate public key in new worker auth bundle: %w", err))
continue
}
args = append(args,
fmt.Sprintf("leaf_cert_%d_id", i), certKeyId,
fmt.Sprintf("leaf_cert_%d_not_before", i), cert.NotBefore.Format(time.RFC3339),
fmt.Sprintf("leaf_cert_%d_not_after", i), cert.NotAfter.Format(time.RFC3339),
)
caCert, err := x509.ParseCertificate(bundle.CaCertificateDer)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error parsing CA certificate in new worker auth bundle: %w", err))
continue
}
caCertPkixBytes, err := x509.MarshalPKIXPublicKey(caCert.PublicKey)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error marshaling CA certificate public key in new worker auth bundle: %w", err))
continue
}
caCertKeyId, err := nodeenrollment.KeyIdFromPkix(caCertPkixBytes)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error deriving pkix string from CA certificate public key in new worker auth bundle: %w", err))
continue
}
args = append(args,
fmt.Sprintf("ca_cert_%d_id", i), caCertKeyId,
fmt.Sprintf("ca_cert_%d_not_before", i), caCert.NotBefore.Format(time.RFC3339),
fmt.Sprintf("ca_cert_%d_not_after", i), caCert.NotAfter.Format(time.RFC3339),
)
}
event.WriteSysEvent(ctx, op, "rotate worker auth job finished successfully", args...)
return nil
}
+2 -2
View File
@@ -14,7 +14,7 @@ import (
)
// RegisterJobs registers the rotate roots job with the provided scheduler.
func RegisterJobs(ctx context.Context, scheduler *scheduler.Scheduler, r db.Reader, w db.Writer, kms *kms.Kms) error {
func RegisterJobs(ctx context.Context, scheduler *scheduler.Scheduler, r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) error {
const op = "server.(Jobs).RegisterJobs"
if isNil(scheduler) {
@@ -30,7 +30,7 @@ func RegisterJobs(ctx context.Context, scheduler *scheduler.Scheduler, r db.Read
return errors.New(ctx, errors.InvalidParameter, op, "missing kms")
}
rotateRootsJob, err := newRotateRootsJob(ctx, r, w, kms)
rotateRootsJob, err := newRotateRootsJob(ctx, r, w, kms, opt...)
if err != nil {
return errors.Wrap(ctx, err, op)
}
+48
View File
@@ -0,0 +1,48 @@
package servers
import (
"time"
)
const defaultRotationFrequency = time.Hour
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
}
return opts
}
// Option - how Options are passed as arguments
type Option func(*options)
// options = how options are represented
type options struct {
withRotationFrequency time.Duration
withCertificateLifetime time.Duration
}
func getDefaultOptions() options {
return options{
withRotationFrequency: defaultRotationFrequency,
}
}
// WithRotationFrequency provides an frequency for running a job. If the passed-in
// frequency is zero, it will not update the value, so that the default is used
// instead.
func WithRotationFrequency(with time.Duration) Option {
return func(o *options) {
o.withRotationFrequency = with
}
}
// WithCertificateLifetime provides a way to specify the lifetime of generated
// roots
func WithCertificateLifetime(with time.Duration) Option {
return func(o *options) {
o.withCertificateLifetime = with
}
}
+24
View File
@@ -0,0 +1,24 @@
package servers
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_GetOpts(t *testing.T) {
t.Parallel()
t.Run("WithRotationFrequency", func(t *testing.T) {
opts := getOpts(WithRotationFrequency(time.Minute))
testOpts := getDefaultOptions()
assert.Equal(t, testOpts.withRotationFrequency, defaultRotationFrequency)
assert.Equal(t, time.Minute, opts.withRotationFrequency)
})
t.Run("WithCertificateLifetime", func(t *testing.T) {
opts := getOpts(WithCertificateLifetime(time.Minute))
testOpts := getDefaultOptions()
assert.Equal(t, testOpts.withCertificateLifetime, defaultRotationFrequency)
assert.Equal(t, time.Minute, opts.withCertificateLifetime)
})
}
+14 -7
View File
@@ -12,10 +12,9 @@ import (
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/nodeenrollment"
)
const rotateFrequency = time.Hour
// rotateRootsJob defines a periodic job that initiates root certificate rotation
// It runs every hour; the root rotation function in the library is designed to not
// do anything if it's not time to rotate (roots are within their valid ranges)
@@ -23,10 +22,14 @@ type rotateRootsJob struct {
workerAuthRepo *server.WorkerAuthRepositoryStorage
totalRotates int
rotateFrequency time.Duration
certificateLifetime time.Duration
}
// newRotateRootsJob instantiates the rotate roots job.
func newRotateRootsJob(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms) (*rotateRootsJob, error) {
func newRotateRootsJob(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*rotateRootsJob, error) {
const op = "server.newRotateRootsJob"
switch {
case isNil(r):
@@ -42,9 +45,13 @@ func newRotateRootsJob(ctx context.Context, r db.Reader, w db.Writer, kms *kms.K
return nil, errors.Wrap(ctx, err, op)
}
opts := getOpts(opt...)
return &rotateRootsJob{
workerAuthRepo: workerAuthRepo,
totalRotates: 0,
workerAuthRepo: workerAuthRepo,
totalRotates: 0,
rotateFrequency: opts.withRotationFrequency,
certificateLifetime: opts.withCertificateLifetime,
}, nil
}
@@ -59,7 +66,7 @@ func (r *rotateRootsJob) Description() string {
// NextRunIn returns the next run time after a job is completed.
// This is represented by RotateFrequency
func (r *rotateRootsJob) NextRunIn(_ context.Context) (time.Duration, error) {
return rotateFrequency, nil
return r.rotateFrequency, nil
}
// Status returns the status of the running job.
@@ -74,7 +81,7 @@ func (r *rotateRootsJob) Status() scheduler.JobStatus {
func (r *rotateRootsJob) Run(ctx context.Context) error {
const op = "server.(rotateRootsJob).Run"
_, err := server.RotateRoots(ctx, r.workerAuthRepo)
_, err := server.RotateRoots(ctx, r.workerAuthRepo, nodeenrollment.WithCertificateLifetime(r.certificateLifetime))
if err != nil {
return errors.Wrap(ctx, err, op)
}
+47 -5
View File
@@ -5,17 +5,21 @@ package server
import (
"context"
"fmt"
"time"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/nodeenrollment"
"github.com/hashicorp/nodeenrollment/rotation"
"github.com/hashicorp/nodeenrollment/types"
)
// RotateRoots is a domain service function that initiates a rotation of root certificates
// via a call to the nodenenrollment RotateRootCertificates function
// Accepts the nodeenrollment option, WithCertificateLifetime(time.Duration) to specify the lifetime
// of the generated cert(s)
// RotateRoots is a domain service function that initiates a rotation of root
// certificates via a call to the nodenenrollment RotateRootCertificates
// function. Accepts the nodeenrollment option
// WithCertificateLifetime(time.Duration) to specify the lifetime of the
// generated cert(s).
func RotateRoots(ctx context.Context, workerAuthRepo *WorkerAuthRepositoryStorage, opt ...nodeenrollment.Option) (*types.RootCertificates, error) {
const op = "server.RotateRoots"
if workerAuthRepo == nil {
@@ -28,12 +32,50 @@ func RotateRoots(ctx context.Context, workerAuthRepo *WorkerAuthRepositoryStorag
if err != nil && err != nodeenrollment.ErrNotFound {
return nil, errors.Wrap(ctx, err, op)
}
opt = append(opt, nodeenrollment.WithState(rootCerts.GetState()))
opt = append(opt,
nodeenrollment.WithState(rootCerts.GetState()),
nodeenrollment.WithNotAfterClockSkew(0),
nodeenrollment.WithNotBeforeClockSkew(0),
)
var current, next string
if rootCerts.Current != nil {
current = rootCerts.Current.GetId()
}
if rootCerts.Next != nil {
next = rootCerts.Next.GetId()
}
roots, err := rotation.RotateRootCertificates(ctx, workerAuthRepo, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if roots != nil && (roots.Current.GetId() != current || roots.Next.GetId() != next) {
var args []any
currentKeyId, err := nodeenrollment.KeyIdFromPkix(roots.Current.PublicKeyPkix)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error deriving pkix string from current ca certificate public key after rotation: %w", err))
} else {
args = append(args,
"current_ca_cert_id", currentKeyId,
"current_ca_cert_not_before", roots.Current.GetNotBefore().AsTime().Format(time.RFC3339),
"current_ca_cert_not_after", roots.Current.GetNotAfter().AsTime().Format(time.RFC3339),
)
}
nextKeyId, err := nodeenrollment.KeyIdFromPkix(roots.Next.PublicKeyPkix)
if err != nil {
event.WriteError(ctx, op, fmt.Errorf("error deriving pkix string from next ca certificate public key after rotation: %w", err))
} else {
args = append(args,
"next_ca_cert_id", nextKeyId,
"next_ca_cert_not_before", roots.Next.GetNotBefore().AsTime().Format(time.RFC3339),
"next_ca_cert_not_after", roots.Next.GetNotAfter().AsTime().Format(time.RFC3339),
)
}
event.WriteSysEvent(ctx, op, "worker auth root certificates were rotated", args...)
}
return roots, nil
}