Files
threshold/internal/daemon/worker/auth_rotation_test.go
T
Jeff Mitchell 163ce184b8 KMS-PKI Workers (#3101)
This adds a new mechanism for worker authentication that uses the
third-party arbitration of the original KMS flow by leveraging
recently-added capabilities in nodeenrollment to support a registration
wrapper. This allows for workers to be registered to the system with
just a name and a matching wrapper, while at the same time allowing
these workers to be used for private Vault access and multi-hop as they
contain per-worker encryption keys and support rotation.

Nodes using this mode will generate and rotate credentials in-memory
(using another recently added nodeenrollment capability) so will have a
new set of keys on every startup. This has the side effect of ensuring
that there is never a conflict or re-use of these credentials.

Note that the normal test worker flow was using the now-deprecated KMS
method; there is still a test that uses this (explicitly) but all other
tests using test workers that did not specify a specific PKI flow now
actually use this mechanism. This means there is fairly decent test
coverage in a general sense, plus the specific tests updated/added for
this feature.

This also adds a new KMS type that can be used to have
distinct KMSes for upstream authentication vs. accepting from
downstream.
2023-03-30 16:20:28 -04:00

152 lines
5.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package worker_test
import (
"testing"
"time"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/daemon/controller"
"github.com/hashicorp/boundary/internal/daemon/worker"
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/server/store"
"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) {
require, assert := require.New(t), assert.New(t)
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Trace,
})
conf, err := config.DevController()
require.NoError(err)
c := controller.NewTestController(t, &controller.TestControllerOpts{
Config: conf,
Logger: logger.Named("controller"),
})
t.Cleanup(c.Shutdown)
const rotationPeriod = 20 * time.Second
// names should not be set when using pki workers
wConf, err := config.DevWorker()
require.NoError(err)
wConf.Worker.Name = ""
wConf.Worker.InitialUpstreams = c.ClusterAddrs()
w := worker.NewTestWorker(t, &worker.TestWorkerOpts{
InitialUpstreams: c.ClusterAddrs(),
Logger: logger.Named("worker"),
AuthRotationPeriod: rotationPeriod,
Config: wConf,
})
t.Cleanup(w.Shutdown)
// Get a server repo and worker auth repo
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))
worker, err := serversRepo.CreateWorker(c.Context(), &server.Worker{
Worker: &store.Worker{
ScopeId: scope.Global.String(),
},
}, server.WithFetchNodeCredentialsRequest(req))
require.NoError(err)
// Verify we see one authorized set of credentials now
auths, err = workerAuthRepo.List(c.Context(), (*types.NodeInformation)(nil))
require.NoError(err)
assert.Len(auths, 1)
// Fetch creds and store current key
currNodeCreds, err := types.LoadNodeCredentials(
w.Context(),
w.Worker().WorkerAuthStorage,
nodeenrollment.CurrentId,
nodeenrollment.WithStorageWrapper(w.Config().WorkerAuthStorageKms),
)
require.NoError(err)
currKey := currNodeCreds.CertificatePublicKeyPkix
priorKeyId, err := nodeenrollment.KeyIdFromPkix(currKey)
require.NoError(err)
// Now we wait and check that we see new values in the DB and different
// creds on the worker after each rotation period
for i := 2; i < 5; i++ {
time.Sleep(rotationPeriod)
// Verify we see 2- after credentials have rotated, we should see current and previous
auths, err = workerAuthRepo.List(c.Context(), (*types.NodeInformation)(nil))
require.NoError(err)
assert.Len(auths, 2)
// Fetch creds and compare current key
currNodeCreds, err := types.LoadNodeCredentials(
w.Context(),
w.Worker().WorkerAuthStorage,
nodeenrollment.CurrentId,
nodeenrollment.WithStorageWrapper(w.Config().WorkerAuthStorageKms),
)
require.NoError(err)
assert.NotEqual(currKey, currNodeCreds.CertificatePublicKeyPkix)
currKey = currNodeCreds.CertificatePublicKeyPkix
currKeyId, err := nodeenrollment.KeyIdFromPkix(currNodeCreds.CertificatePublicKeyPkix)
require.NoError(err)
assert.Equal(currKeyId, w.Worker().WorkerAuthCurrentKeyId.Load())
// Check that we've got the correct prior encryption key
previousKeyId, _, err := currNodeCreds.PreviousX25519EncryptionKey()
require.NoError(err)
assert.Equal(priorKeyId, previousKeyId)
// Get workerAuthSet for this worker id and compare keys
workerAuthSet, err := workerAuthRepo.FindWorkerAuthByWorkerId(c.Context(), worker.PublicId)
require.NoError(err)
assert.NotNil(workerAuthSet)
assert.NotNil(workerAuthSet.Previous)
assert.NotNil(workerAuthSet.Current)
assert.Equal(workerAuthSet.Current.WorkerKeyIdentifier, currKeyId)
assert.Equal(workerAuthSet.Previous.WorkerKeyIdentifier, previousKeyId)
// Save priorKeyId
priorKeyId = currKeyId
// 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
require.NotNil(w.Worker().GrpcClientConn)
require.NoError(w.Worker().GrpcClientConn.Close())
require.NoError(w.Worker().StartControllerConnections())
}
}