Files
threshold/internal/daemon/worker/auth_rotation_test.go
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

176 lines
6.1 KiB
Go
Raw Normal View History

2022-06-07 14:26:33 -04:00
// Copyright (c) HashiCorp, Inc.
2022-06-07 16:43:07 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2022-06-07 14:26:33 -04:00
package worker_test
import (
2023-11-06 16:03:25 -05:00
"bytes"
"context"
2023-05-24 23:33:51 -04:00
"sync/atomic"
2022-06-07 14:26:33 -04:00
"testing"
"time"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/daemon/controller"
"github.com/hashicorp/boundary/internal/daemon/worker"
2022-06-20 18:20:17 -07:00
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/server/store"
2022-06-07 14:26:33 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nodeenrollment"
"github.com/hashicorp/nodeenrollment/types"
"github.com/mr-tron/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// TestRotationTicking ensures that we see new credential information for a
// worker on both the controller side and worker side in a periodic fashion
func TestRotationTicking(t *testing.T) {
t.Parallel()
2022-06-07 14:26:33 -04:00
require, assert := require.New(t), assert.New(t)
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Trace,
})
const rotationPeriod = 20 * time.Second
2022-06-07 14:26:33 -04:00
conf, err := config.DevController()
require.NoError(err)
2023-05-24 23:33:51 -04:00
workerAuthDebugEnabled := new(atomic.Bool)
workerAuthDebugEnabled.Store(true)
2022-06-07 14:26:33 -04:00
c := controller.NewTestController(t, &controller.TestControllerOpts{
Config: conf,
Logger: logger.Named("controller"),
WorkerAuthCaCertificateLifetime: rotationPeriod,
2023-05-24 23:33:51 -04:00
WorkerAuthDebuggingEnabled: workerAuthDebugEnabled,
2022-06-07 14:26:33 -04:00
})
// names should not be set when using pki workers
wConf, err := config.DevWorker()
require.NoError(err)
wConf.Worker.Name = ""
wConf.Worker.InitialUpstreams = c.ClusterAddrs()
2022-06-07 14:26:33 -04:00
w := worker.NewTestWorker(t, &worker.TestWorkerOpts{
2023-05-24 23:33:51 -04:00
InitialUpstreams: c.ClusterAddrs(),
Logger: logger.Named("worker"),
Config: wConf,
WorkerAuthDebuggingEnabled: workerAuthDebugEnabled,
2022-06-07 14:26:33 -04:00
})
2022-06-20 18:20:17 -07:00
// Get a server repo and worker auth repo
2022-06-07 14:26:33 -04:00
serversRepo, err := c.Controller().ServersRepoFn()
require.NoError(err)
workerAuthRepo, err := c.Controller().WorkerAuthRepoStorageFn()
require.NoError(err)
// Give time for the job to ensure that roots are created and offset us from
// the rotation period
time.Sleep(rotationPeriod / 2)
// Verify that there are no nodes authorized yet
auths, err := workerAuthRepo.List(c.Context(), (*types.NodeInformation)(nil))
require.NoError(err)
assert.Len(auths, 0)
// Perform initial authentication of worker to controller
reqBytes, err := base58.FastBase58Decoding(w.Worker().WorkerAuthRegistrationRequest)
require.NoError(err)
// Decode the proto into the request and create the worker
req := new(types.FetchNodeCredentialsRequest)
require.NoError(proto.Unmarshal(reqBytes, req))
newWorker, err := serversRepo.CreateWorker(c.Context(), &server.Worker{
2022-06-07 14:26:33 -04:00
Worker: &store.Worker{
ScopeId: scope.Global.String(),
},
2022-06-20 18:20:17 -07:00
}, server.WithFetchNodeCredentialsRequest(req))
2022-06-07 14:26:33 -04:00
require.NoError(err)
// Wait for a short while; there will be an initial rotation of credentials
// after authentication
time.Sleep(rotationPeriod / 2)
// Verify we see authorized credentials now
2022-06-07 14:26:33 -04:00
auths, err = workerAuthRepo.List(c.Context(), (*types.NodeInformation)(nil))
require.NoError(err)
require.Len(auths, 2)
2022-06-07 14:26:33 -04:00
// Fetch creds and store current key
2023-03-30 16:20:28 -04:00
currNodeCreds, err := types.LoadNodeCredentials(
w.Context(),
w.Worker().WorkerAuthStorage,
nodeenrollment.CurrentId,
nodeenrollment.WithStorageWrapper(w.Config().WorkerAuthStorageKms),
)
2022-06-07 14:26:33 -04:00
require.NoError(err)
currKey := currNodeCreds.CertificatePublicKeyPkix
// Now we wait and check that we see new values in the DB and different
// creds on the worker after each rotation period
2023-11-06 16:03:25 -05:00
// Make sure we have a failsafe in case the below loop never finds what it's
// looking for; at expiration the List will fail and we'll hit the Require,
// exiting
testTimeout := 10 * time.Minute
testDeadline, ok := t.Deadline()
if ok && time.Until(testDeadline) < testTimeout {
// If the test deadline is less than the default timeout, use the deadline
// Give a minute buffer for shutdowns and other test cleanup
testTimeout = time.Until(testDeadline) - time.Minute
}
deadlineCtx, deadlineCtxCancel := context.WithTimeout(c.Context(), testTimeout)
2023-11-06 16:03:25 -05:00
defer deadlineCtxCancel()
rotationCount := 2
for {
if rotationCount > 3 {
2023-11-06 16:03:25 -05:00
break
}
2023-05-24 23:33:51 -04:00
nextRotation := w.Worker().AuthRotationNextRotation.Load()
// Sleep until 5 seconds after next rotation
time.Sleep(time.Until(*nextRotation) + 5*time.Second)
2022-06-07 14:26:33 -04:00
// Verify we see 2- after credentials have rotated, we should see current and previous
2023-11-06 16:03:25 -05:00
auths, err = workerAuthRepo.List(deadlineCtx, (*types.NodeInformation)(nil))
2022-06-07 14:26:33 -04:00
require.NoError(err)
require.Len(auths, 2)
2022-06-07 14:26:33 -04:00
// Fetch creds and compare current key
2023-03-30 16:20:28 -04:00
currNodeCreds, err := types.LoadNodeCredentials(
w.Context(),
w.Worker().WorkerAuthStorage,
nodeenrollment.CurrentId,
nodeenrollment.WithStorageWrapper(w.Config().WorkerAuthStorageKms),
)
2022-06-07 14:26:33 -04:00
require.NoError(err)
2023-11-06 16:03:25 -05:00
if bytes.Equal(currKey, currNodeCreds.CertificatePublicKeyPkix) {
// Load due to parallel tests may mean that we didn't hit rotation
// yet, loop back to evaluate again
continue
}
2022-06-07 14:26:33 -04:00
assert.NotEqual(currKey, currNodeCreds.CertificatePublicKeyPkix)
currKey = currNodeCreds.CertificatePublicKeyPkix
2022-06-07 16:43:07 -04:00
currKeyId, err := nodeenrollment.KeyIdFromPkix(currNodeCreds.CertificatePublicKeyPkix)
require.NoError(err)
assert.Equal(currKeyId, w.Worker().WorkerAuthCurrentKeyId.Load())
2022-06-07 14:26:33 -04:00
// Get workerAuthSet for this worker id and compare keys
workerAuthSet, err := workerAuthRepo.FindWorkerAuthByWorkerId(c.Context(), newWorker.PublicId)
require.NoError(err)
assert.NotNil(workerAuthSet)
assert.NotNil(workerAuthSet.Previous)
assert.NotNil(workerAuthSet.Current)
assert.Equal(workerAuthSet.Current.WorkerKeyIdentifier, currKeyId)
2022-06-07 14:26:33 -04:00
// Stop and start the client connections to ensure the new credentials
// are valid; if not, we won't establish a new connection and rotation
// will fail
2024-02-21 16:32:26 -05:00
require.NotNil(w.Worker().GrpcClientConn.Load())
require.NoError(w.Worker().GrpcClientConn.Load().Close())
2022-06-07 14:26:33 -04:00
require.NoError(w.Worker().StartControllerConnections())
2023-11-06 16:03:25 -05:00
rotationCount++
2022-06-07 14:26:33 -04:00
}
}