* 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. * Update auth rotation logic Because we were looking at validity period of client certs, which may not have matched the actual rotation interval, it was possible (mostly in test scenarios) for the rotation to happen multiple times on the roots before we actually hit the worker rotation interval. This changes how the interval is calculated by looking at the difference between not-after values on the certs to understand the rotation interval being used by the controller. We then use that as the starting point for calculating a next-rotation time.
80 lines
2.7 KiB
Go
80 lines
2.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
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).
|
|
func RotateRoots(ctx context.Context, workerAuthRepo *WorkerAuthRepositoryStorage, opt ...nodeenrollment.Option) (*types.RootCertificates, error) {
|
|
const op = "server.RotateRoots"
|
|
if workerAuthRepo == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing workerAuthRepo")
|
|
}
|
|
|
|
// Get current root certs so we can pass along the version
|
|
rootCerts := &types.RootCertificates{Id: CaId}
|
|
err := workerAuthRepo.Load(ctx, rootCerts)
|
|
if err != nil && err != nodeenrollment.ErrNotFound {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
opt = append(opt,
|
|
nodeenrollment.WithState(rootCerts.GetState()),
|
|
)
|
|
|
|
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
|
|
}
|