Add controller-led worker auth flow (#2413)
This adds an action to the API that allows pulling out an activation token that can be given to a worker in its config (directly or via env or file). The worker can pass this along with its normal request in place of its normal nonce and the server will automatically accept it. Co-authored-by: Irena Rindos <irenarindos@users.noreply.github.com>
This commit is contained in:
1 parent
a1be125f5b
commit
01fb949d0b
55 files changed
+2728
-625
No files matched your search
+30
-22
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// getOpts - iterate the inbound Options and return a struct
|
||||
func getOpts(opt ...Option) options {
|
||||
func GetOpts(opt ...Option) options {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
@@ -21,26 +21,27 @@ type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withName string
|
||||
withPublicId string
|
||||
withDescription string
|
||||
withAddress string
|
||||
withLimit int
|
||||
withLiveness time.Duration
|
||||
withUpdateTags bool
|
||||
withWorkerTags []*Tag
|
||||
withWorkerKeyIdentifier string
|
||||
withWorkerKeys WorkerKeys
|
||||
withControllerEncryptionPrivateKey []byte
|
||||
withKeyId string
|
||||
withNonce []byte
|
||||
withNewIdFunc func(context.Context) (string, error)
|
||||
withFetchNodeCredentialsRequest *types.FetchNodeCredentialsRequest
|
||||
withTestPkiWorkerAuthorized bool
|
||||
withTestPkiWorkerKeyId *string
|
||||
withWorkerType WorkerType
|
||||
withRoot string
|
||||
withStopAfter uint
|
||||
withName string
|
||||
withPublicId string
|
||||
withDescription string
|
||||
withAddress string
|
||||
withLimit int
|
||||
withLiveness time.Duration
|
||||
withUpdateTags bool
|
||||
withWorkerTags []*Tag
|
||||
withWorkerKeyIdentifier string
|
||||
withWorkerKeys WorkerKeys
|
||||
withControllerEncryptionPrivateKey []byte
|
||||
withKeyId string
|
||||
withNonce []byte
|
||||
withNewIdFunc func(context.Context) (string, error)
|
||||
WithFetchNodeCredentialsRequest *types.FetchNodeCredentialsRequest
|
||||
withTestPkiWorkerAuthorized bool
|
||||
withTestPkiWorkerKeyId *string
|
||||
withWorkerType WorkerType
|
||||
withRoot string
|
||||
withStopAfter uint
|
||||
WithCreateControllerLedActivationToken bool
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -153,7 +154,7 @@ func WithNewIdFunc(fn func(context.Context) (string, error)) Option {
|
||||
// FetchNodeCredentialsRequest to be specified.
|
||||
func WithFetchNodeCredentialsRequest(req *types.FetchNodeCredentialsRequest) Option {
|
||||
return func(o *options) {
|
||||
o.withFetchNodeCredentialsRequest = req
|
||||
o.WithFetchNodeCredentialsRequest = req
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,3 +189,10 @@ func WithStopAfter(stopAfter uint) Option {
|
||||
o.withStopAfter = stopAfter
|
||||
}
|
||||
}
|
||||
|
||||
// WithCreateControllerLedActivationToken provides an optional stop after count
|
||||
func WithCreateControllerLedActivationToken(with bool) Option {
|
||||
return func(o *options) {
|
||||
o.WithCreateControllerLedActivationToken = with
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func Test_GetOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("WithName", func(t *testing.T) {
|
||||
opts := getOpts(WithName("test"))
|
||||
opts := GetOpts(WithName("test"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withName = "test"
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -22,7 +22,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithPublicId", func(t *testing.T) {
|
||||
opts := getOpts(WithPublicId("test"))
|
||||
opts := GetOpts(WithPublicId("test"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withPublicId = "test"
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -30,7 +30,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithDescription", func(t *testing.T) {
|
||||
opts := getOpts(WithDescription("test desc"))
|
||||
opts := GetOpts(WithDescription("test desc"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withDescription = "test desc"
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -38,7 +38,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithLimit", func(t *testing.T) {
|
||||
opts := getOpts(WithLimit(5))
|
||||
opts := GetOpts(WithLimit(5))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withLimit = 5
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -46,7 +46,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithAddress", func(t *testing.T) {
|
||||
opts := getOpts(WithAddress("test"))
|
||||
opts := GetOpts(WithAddress("test"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withAddress = "test"
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -54,7 +54,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithLiveness", func(t *testing.T) {
|
||||
opts := getOpts(WithLiveness(time.Hour))
|
||||
opts := GetOpts(WithLiveness(time.Hour))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withLiveness = time.Hour
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -62,7 +62,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithUpdateTags", func(t *testing.T) {
|
||||
opts := getOpts(WithUpdateTags(true))
|
||||
opts := GetOpts(WithUpdateTags(true))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withUpdateTags = true
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -74,7 +74,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key2", Value: "val2"},
|
||||
}
|
||||
opts := getOpts(WithWorkerTags(tags...))
|
||||
opts := GetOpts(WithWorkerTags(tags...))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withWorkerTags = tags
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -90,7 +90,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
workerSigningPubKey: populateBytes(20),
|
||||
}
|
||||
|
||||
opts := getOpts(WithWorkerKeys(keys))
|
||||
opts := GetOpts(WithWorkerKeys(keys))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withWorkerKeys = keys
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -103,7 +103,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
|
||||
key := populateBytes(20)
|
||||
|
||||
opts := getOpts(WithControllerEncryptionPrivateKey(key))
|
||||
opts := GetOpts(WithControllerEncryptionPrivateKey(key))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withControllerEncryptionPrivateKey = key
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -114,7 +114,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testOpts := getDefaultOptions()
|
||||
|
||||
opts := getOpts(WithKeyId("hi i'm another key id"))
|
||||
opts := GetOpts(WithKeyId("hi i'm another key id"))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withKeyId = "hi i'm another key id"
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -127,7 +127,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
|
||||
nonce := populateBytes(20)
|
||||
|
||||
opts := getOpts(WithNonce(nonce))
|
||||
opts := GetOpts(WithNonce(nonce))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withNonce = nonce
|
||||
opts.withNewIdFunc = nil
|
||||
@@ -137,7 +137,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
t.Run("WithNewIdFunc", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testFn := func(context.Context) (string, error) { return "", nil }
|
||||
opts := getOpts(WithNewIdFunc(testFn))
|
||||
opts := GetOpts(WithNewIdFunc(testFn))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withNewIdFunc = testFn
|
||||
assert.Equal(
|
||||
@@ -150,7 +150,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts := getDefaultOptions()
|
||||
|
||||
var keyId string
|
||||
opts := getOpts(WithTestPkiWorkerAuthorizedKeyId(&keyId))
|
||||
opts := GetOpts(WithTestPkiWorkerAuthorizedKeyId(&keyId))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withTestPkiWorkerAuthorized = true
|
||||
testOpts.withTestPkiWorkerKeyId = &keyId
|
||||
@@ -161,19 +161,25 @@ func Test_GetOpts(t *testing.T) {
|
||||
t.Run("WithWorkerType", func(t *testing.T) {
|
||||
opts := getDefaultOptions()
|
||||
assert.Empty(t, opts.withWorkerType)
|
||||
opts = getOpts(WithWorkerType(KmsWorkerType))
|
||||
opts = GetOpts(WithWorkerType(KmsWorkerType))
|
||||
assert.Equal(t, KmsWorkerType, opts.withWorkerType)
|
||||
})
|
||||
t.Run("WithRoot", func(t *testing.T) {
|
||||
opts := getDefaultOptions()
|
||||
assert.Empty(t, opts.withRoot)
|
||||
opts = getOpts(WithRoot("a"))
|
||||
opts = GetOpts(WithRoot("a"))
|
||||
assert.Equal(t, "a", opts.withRoot)
|
||||
})
|
||||
t.Run("WithStopAfter", func(t *testing.T) {
|
||||
opts := getDefaultOptions()
|
||||
assert.Empty(t, opts.withStopAfter)
|
||||
opts = getOpts(WithStopAfter(10))
|
||||
opts = GetOpts(WithStopAfter(10))
|
||||
assert.Equal(t, uint(10), opts.withStopAfter)
|
||||
})
|
||||
t.Run("WithCreateControllerLedActivationToken", func(t *testing.T) {
|
||||
opts := getDefaultOptions()
|
||||
assert.False(t, opts.WithCreateControllerLedActivationToken)
|
||||
opts = GetOpts(WithCreateControllerLedActivationToken(true))
|
||||
assert.True(t, opts.WithCreateControllerLedActivationToken)
|
||||
})
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo
|
||||
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil kms")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
if opts.withLimit == 0 {
|
||||
// zero signals the boundary defaults should be used.
|
||||
opts.withLimit = db.DefaultLimit
|
||||
|
||||
@@ -17,7 +17,7 @@ func (r *Repository) ListControllers(ctx context.Context, opt ...Option) ([]*sto
|
||||
// WithLimit option or the repo defaultLimit. It accepts a reader, allowing it
|
||||
// to be used within a transaction or without.
|
||||
func (r *Repository) listControllersWithReader(ctx context.Context, reader db.Reader, opt ...Option) ([]*store.Controller, error) {
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
liveness := opts.withLiveness
|
||||
if liveness == 0 {
|
||||
liveness = DefaultLiveness
|
||||
|
||||
@@ -11,10 +11,12 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/server/store"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/nodeenrollment"
|
||||
"github.com/hashicorp/nodeenrollment/registration"
|
||||
"github.com/hashicorp/nodeenrollment/types"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// DeleteWorker will delete a worker from the repository.
|
||||
@@ -170,7 +172,7 @@ func (r *Repository) ListWorkers(ctx context.Context, scopeIds []string, opt ...
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope ids set")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
liveness := opts.withLiveness
|
||||
if liveness == 0 {
|
||||
liveness = DefaultLiveness
|
||||
@@ -233,7 +235,7 @@ func (r *Repository) ListWorkers(ctx context.Context, scopeIds []string, opt ...
|
||||
func (r *Repository) UpsertWorkerStatus(ctx context.Context, worker *Worker, opt ...Option) (*Worker, error) {
|
||||
const op = "server.UpsertWorkerStatus"
|
||||
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
switch {
|
||||
case worker == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "worker is nil")
|
||||
@@ -492,10 +494,15 @@ func (r *Repository) UpdateWorker(ctx context.Context, worker *Worker, version u
|
||||
// ReportedStatus and Tags are intentionally ignored when creating a worker (not
|
||||
// included). Currently, a worker can only be created in the global scope
|
||||
//
|
||||
// Options supported: WithFetchNodeCredentialsRequest and WithNewIdFunc (this
|
||||
// option is likely only useful for tests)
|
||||
// Options supported: WithNewIdFunc (this option is likely only useful for
|
||||
// tests), WithFetchNodeCredentialsRequest,
|
||||
// WithCreateControllerLedActivationToken. The latter two are mutually
|
||||
// exclusive.
|
||||
func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Option) (*Worker, error) {
|
||||
const op = "server.CreateWorker"
|
||||
|
||||
opts := GetOpts(opt...)
|
||||
|
||||
switch {
|
||||
case worker == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing worker")
|
||||
@@ -509,9 +516,10 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "address is not empty")
|
||||
case worker.LastStatusTime != nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "last status time is not nil")
|
||||
case opts.WithFetchNodeCredentialsRequest != nil && opts.WithCreateControllerLedActivationToken:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "fetch node credentials request and controller led activation token option cannot both be set")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
var err error
|
||||
if worker.PublicId, err = opts.withNewIdFunc(ctx); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to generate worker id"))
|
||||
@@ -523,14 +531,11 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating state struct", err))
|
||||
}
|
||||
|
||||
var databaseWrapper wrapping.Wrapper
|
||||
var workerAuthRepo *WorkerAuthRepositoryStorage
|
||||
if opts.withFetchNodeCredentialsRequest != nil {
|
||||
// used to encrypt the privKey within the NodeInformation
|
||||
databaseWrapper, err = r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
databaseWrapper, err := r.kms.GetWrapper(context.Background(), worker.ScopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
|
||||
}
|
||||
|
||||
var returnedWorker *Worker
|
||||
@@ -547,12 +552,14 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op
|
||||
); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker"))
|
||||
}
|
||||
if opts.withFetchNodeCredentialsRequest != nil {
|
||||
|
||||
switch {
|
||||
case opts.WithFetchNodeCredentialsRequest != nil:
|
||||
workerAuthRepo, err = NewRepositoryStorage(ctx, read, w, r.kms)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker auth repository"))
|
||||
}
|
||||
nodeInfo, err := registration.AuthorizeNode(ctx, workerAuthRepo, opts.withFetchNodeCredentialsRequest, nodeenrollment.WithSkipStorage(true))
|
||||
nodeInfo, err := registration.AuthorizeNode(ctx, workerAuthRepo, opts.WithFetchNodeCredentialsRequest, nodeenrollment.WithSkipStorage(true))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to authorize node"))
|
||||
}
|
||||
@@ -560,7 +567,35 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op
|
||||
if err := StoreNodeInformationTx(ctx, w, databaseWrapper, nodeInfo); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to store node information"))
|
||||
}
|
||||
|
||||
case opts.WithCreateControllerLedActivationToken:
|
||||
tokenId, activationToken, err := registration.CreateServerLedActivationToken(ctx, nil, &types.ServerLedRegistrationRequest{},
|
||||
nodeenrollment.WithSkipStorage(true),
|
||||
nodeenrollment.WithState(state))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create controller-led activation token"))
|
||||
}
|
||||
creationTime := timestamppb.Now()
|
||||
creationTimeBytes, err := proto.Marshal(creationTime)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to marshal timestamp for activation token"))
|
||||
}
|
||||
activationTokenEntry, err := newWorkerAuthServerLedActivationToken(ctx, worker.PublicId, tokenId, creationTimeBytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in-memory activation token"))
|
||||
}
|
||||
if err := activationTokenEntry.encrypt(ctx, databaseWrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if err := w.Create(
|
||||
ctx,
|
||||
activationTokenEntry,
|
||||
); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker activation token in storage"))
|
||||
}
|
||||
returnedWorker.ControllerGeneratedActivationToken = activationToken
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
@@ -590,10 +625,7 @@ func (r *Repository) AddWorkerTags(ctx context.Context, workerId string, workerV
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("no worker found with public id %s", workerId))
|
||||
}
|
||||
|
||||
newTags := worker.apiTags
|
||||
for _, t := range tags {
|
||||
newTags = append(newTags, t)
|
||||
}
|
||||
newTags := append(worker.apiTags, tags...)
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error {
|
||||
worker := worker.clone()
|
||||
worker.PublicId = workerId
|
||||
|
||||
@@ -2,6 +2,7 @@ package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -143,6 +144,7 @@ func TestLookupWorkerIdByKeyId(t *testing.T) {
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
repo, err := server.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
@@ -252,6 +254,7 @@ func TestUpsertWorkerStatus(t *testing.T) {
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
repo, err := server.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -496,9 +499,11 @@ func TestListWorkers(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
|
||||
const testLimit = 10
|
||||
repo, err := server.NewRepository(rw, rw, kms, server.WithLimit(testLimit))
|
||||
repo, err := server.NewRepository(rw, rw, kmsCache, server.WithLimit(testLimit))
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -696,7 +701,6 @@ func TestRepository_CreateWorker(t *testing.T) {
|
||||
name string
|
||||
setup func() *server.Worker
|
||||
repo *server.Repository
|
||||
fetchReq *types.FetchNodeCredentialsRequest
|
||||
reader db.Reader
|
||||
opt []server.Option
|
||||
wantErr bool
|
||||
@@ -812,19 +816,21 @@ func TestRepository_CreateWorker(t *testing.T) {
|
||||
return w
|
||||
},
|
||||
reader: rw,
|
||||
fetchReq: func() *types.FetchNodeCredentialsRequest {
|
||||
// This happens on the worker
|
||||
fileStorage, err := file.New(testCtx)
|
||||
require.NoError(t, err)
|
||||
defer fileStorage.Cleanup()
|
||||
opt: []server.Option{server.WithFetchNodeCredentialsRequest(
|
||||
func() *types.FetchNodeCredentialsRequest {
|
||||
// This happens on the worker
|
||||
fileStorage, err := file.New(testCtx)
|
||||
require.NoError(t, err)
|
||||
defer fileStorage.Cleanup()
|
||||
|
||||
nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage)
|
||||
require.NoError(t, err)
|
||||
// Create request using worker id
|
||||
fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx)
|
||||
require.NoError(t, err)
|
||||
return fetchReq
|
||||
}(),
|
||||
nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage)
|
||||
require.NoError(t, err)
|
||||
// Create request using worker id
|
||||
fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx)
|
||||
require.NoError(t, err)
|
||||
return fetchReq
|
||||
}(),
|
||||
)},
|
||||
repo: func() *server.Repository {
|
||||
mockConn, mock := db.TestSetupWithMock(t)
|
||||
mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
@@ -844,7 +850,7 @@ func TestRepository_CreateWorker(t *testing.T) {
|
||||
return w
|
||||
},
|
||||
reader: rw,
|
||||
fetchReq: &types.FetchNodeCredentialsRequest{},
|
||||
opt: []server.Option{server.WithFetchNodeCredentialsRequest(&types.FetchNodeCredentialsRequest{})},
|
||||
repo: testRepo,
|
||||
wantErr: true,
|
||||
wantErrContains: "unable to authorize node",
|
||||
@@ -862,7 +868,7 @@ func TestRepository_CreateWorker(t *testing.T) {
|
||||
wantErrIs: errors.NotUnique,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
name: "success-no-options",
|
||||
setup: func() *server.Worker {
|
||||
w := server.NewWorker(scope.Global.String())
|
||||
w.Name = "success"
|
||||
@@ -892,29 +898,40 @@ func TestRepository_CreateWorker(t *testing.T) {
|
||||
return w
|
||||
},
|
||||
reader: rw,
|
||||
fetchReq: func() *types.FetchNodeCredentialsRequest {
|
||||
// This happens on the worker
|
||||
fileStorage, err := file.New(testCtx)
|
||||
require.NoError(t, err)
|
||||
defer fileStorage.Cleanup()
|
||||
opt: []server.Option{server.WithFetchNodeCredentialsRequest(
|
||||
func() *types.FetchNodeCredentialsRequest {
|
||||
// This happens on the worker
|
||||
fileStorage, err := file.New(testCtx)
|
||||
require.NoError(t, err)
|
||||
defer fileStorage.Cleanup()
|
||||
|
||||
nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage)
|
||||
require.NoError(t, err)
|
||||
// Create request using worker id
|
||||
fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx)
|
||||
require.NoError(t, err)
|
||||
return fetchReq
|
||||
}(),
|
||||
nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage)
|
||||
require.NoError(t, err)
|
||||
// Create request using worker id
|
||||
fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx)
|
||||
require.NoError(t, err)
|
||||
return fetchReq
|
||||
}(),
|
||||
)},
|
||||
repo: testRepo,
|
||||
},
|
||||
{
|
||||
name: "success-with-controller-activation-token",
|
||||
setup: func() *server.Worker {
|
||||
w := server.NewWorker(scope.Global.String())
|
||||
w.Name = "success-with-controller-activation-token"
|
||||
return w
|
||||
},
|
||||
reader: rw,
|
||||
opt: []server.Option{server.WithCreateControllerLedActivationToken(true)},
|
||||
repo: testRepo,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
testWorker := tc.setup()
|
||||
|
||||
tc.opt = append(tc.opt, server.WithFetchNodeCredentialsRequest(tc.fetchReq))
|
||||
|
||||
got, err := tc.repo.CreateWorker(testCtx, testWorker, tc.opt...)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
@@ -942,12 +959,19 @@ func TestRepository_CreateWorker(t *testing.T) {
|
||||
require.NoError(err)
|
||||
assert.Empty(cmp.Diff(got, found, protocmp.Transform()))
|
||||
|
||||
if tc.fetchReq != nil {
|
||||
opts := server.GetOpts(tc.opt...)
|
||||
if opts.WithFetchNodeCredentialsRequest != nil {
|
||||
worker := &server.WorkerAuth{
|
||||
WorkerAuth: &store.WorkerAuth{},
|
||||
}
|
||||
require.NoError(tc.reader.LookupWhere(testCtx, worker, "worker_id = ?", []any{found.PublicId}))
|
||||
}
|
||||
if opts.WithCreateControllerLedActivationToken {
|
||||
activationToken := &server.WorkerAuthServerLedActivationToken{
|
||||
WorkerAuthServerLedActivationToken: &store.WorkerAuthServerLedActivationToken{},
|
||||
}
|
||||
require.NoError(tc.reader.LookupWhere(testCtx, activationToken, "worker_id = ?", []any{found.PublicId}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -959,6 +983,7 @@ func TestRepository_UpdateWorker(t *testing.T) {
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
|
||||
repo, err := server.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
@@ -85,6 +86,7 @@ func (r *WorkerAuthRepositoryStorage) Store(ctx context.Context, msg nodee.Messa
|
||||
}); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
case *types.RootCertificates:
|
||||
err := r.storeRootCertificates(ctx, t)
|
||||
if err != nil {
|
||||
@@ -343,6 +345,9 @@ func (r *WorkerAuthRepositoryStorage) Load(ctx context.Context, msg nodee.Messag
|
||||
case *types.NodeInformation:
|
||||
err = r.loadNodeInformation(ctx, t)
|
||||
|
||||
case *types.ServerLedActivationToken:
|
||||
err = r.loadServerLedActivationToken(ctx, t)
|
||||
|
||||
case *types.RootCertificates:
|
||||
err = r.loadRootCertificates(ctx, t)
|
||||
|
||||
@@ -416,6 +421,48 @@ func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, n
|
||||
return nil
|
||||
}
|
||||
|
||||
// Node information is loaded in two parts:
|
||||
// * the workerAuth record
|
||||
// * its certificate bundles
|
||||
func (r *WorkerAuthRepositoryStorage) loadServerLedActivationToken(ctx context.Context, token *types.ServerLedActivationToken) error {
|
||||
const op = "server.(WorkerAuthRepositoryStorage).loadServerLedActivationToken"
|
||||
if token == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing ServerLedActivationToken")
|
||||
}
|
||||
|
||||
activationTokenEntry := allocWorkerAuthServerLedActivationToken()
|
||||
|
||||
err := r.reader.LookupWhere(ctx, activationTokenEntry, "token_id = ?", []any{token.Id})
|
||||
if err != nil {
|
||||
if errors.Is(err, dbw.ErrRecordNotFound) {
|
||||
return nodee.ErrNotFound
|
||||
}
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
token.State, err = AttachWorkerIdToState(ctx, activationTokenEntry.WorkerId)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// Decrypt marshaled creation time
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(activationTokenEntry.KeyId))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if err := activationTokenEntry.decrypt(ctx, databaseWrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
token.CreationTimeMarshaled = activationTokenEntry.CreationTime
|
||||
|
||||
token.CreationTime = new(timestamppb.Timestamp)
|
||||
if err := proto.Unmarshal(token.CreationTimeMarshaled, token.CreationTime); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkerAuthRepositoryStorage) findCertBundles(ctx context.Context, workerKeyId string) ([]*types.CertificateBundle, error) {
|
||||
const op = "server.(WorkerAuthRepositoryStorage).findCertBundles"
|
||||
if workerKeyId == "" {
|
||||
@@ -537,9 +584,14 @@ func (r *WorkerAuthRepositoryStorage) Remove(ctx context.Context, msg nodee.Mess
|
||||
}
|
||||
|
||||
// Determine type of message to remove
|
||||
switch msg.(type) {
|
||||
switch t := msg.(type) {
|
||||
case *types.NodeInformation:
|
||||
err := r.removeNodeInformation(ctx, msg.GetId())
|
||||
err := r.removeNodeInformation(ctx, t)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
case *types.ServerLedActivationToken:
|
||||
err := r.removeServerLedActivationToken(ctx, t)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
@@ -555,9 +607,12 @@ func (r *WorkerAuthRepositoryStorage) Remove(ctx context.Context, msg nodee.Mess
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, id string) error {
|
||||
func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, msg *types.NodeInformation) error {
|
||||
const op = "server.(WorkerAuthRepositoryStorage).removeNodeInformation"
|
||||
if id == "" {
|
||||
switch {
|
||||
case msg == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "nil node information")
|
||||
case msg.Id == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "empty id")
|
||||
}
|
||||
|
||||
@@ -567,11 +622,11 @@ func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
var err error
|
||||
_, err = w.Exec(ctx, deleteWorkerAuthQuery, []interface{}{sql.Named("worker_key_identifier", id)})
|
||||
_, err = w.Exec(ctx, deleteWorkerAuthQuery, []interface{}{sql.Named("worker_key_identifier", msg.Id)})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
_, err = w.Exec(ctx, deleteWorkerCertBundlesQuery, []interface{}{sql.Named("worker_key_identifier", id)})
|
||||
_, err = w.Exec(ctx, deleteWorkerCertBundlesQuery, []interface{}{sql.Named("worker_key_identifier", msg.Id)})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete workerAuth"))
|
||||
}
|
||||
@@ -585,6 +640,49 @@ func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkerAuthRepositoryStorage) removeServerLedActivationToken(ctx context.Context, msg *types.ServerLedActivationToken) error {
|
||||
const op = "server.(WorkerAuthRepositoryStorage).removeServerLedActivationToken"
|
||||
switch {
|
||||
case msg == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "nil node information")
|
||||
case msg.Id == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "empty id")
|
||||
case msg.State == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing state")
|
||||
}
|
||||
|
||||
var workerInfo workerAuthWorkerId
|
||||
if err := mapstructure.Decode(msg.State.AsMap(), &workerInfo); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if workerInfo.WorkerId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "state missing worker id")
|
||||
}
|
||||
|
||||
actToken := allocWorkerAuthServerLedActivationToken()
|
||||
actToken.WorkerId = workerInfo.WorkerId
|
||||
_, err := r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
rowsDeleted, err := w.Delete(ctx, actToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if rowsDeleted != 1 {
|
||||
return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to delete one activation token, deleted %d", rowsDeleted))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkerAuthRepositoryStorage) removeCertificateAuthority(ctx context.Context, cert *types.RootCertificates) error {
|
||||
const op = "server.(WorkerAuthRepositoryStorage).removeCertificateAuthority"
|
||||
|
||||
|
||||
@@ -2,13 +2,18 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/server/store"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/nodeenrollment"
|
||||
@@ -17,9 +22,11 @@ import (
|
||||
"github.com/hashicorp/nodeenrollment/storage/file"
|
||||
"github.com/hashicorp/nodeenrollment/types"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
@@ -135,18 +142,14 @@ func TestStoreCertAuthorityVersioning(t *testing.T) {
|
||||
func TestStoreWorkerAuth(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
rootWrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
kmsCache := kms.TestKms(t, conn, rootWrapper)
|
||||
|
||||
// Ensures the global scope contains a valid root key
|
||||
err := kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))
|
||||
require.NoError(err)
|
||||
wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
require.NotNil(wrapper)
|
||||
require.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
|
||||
worker := TestPkiWorker(t, conn, wrapper)
|
||||
worker := TestPkiWorker(t, conn, rootWrapper)
|
||||
|
||||
rw := db.New(conn)
|
||||
rootStorage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
@@ -216,6 +219,49 @@ func TestStoreWorkerAuth(t *testing.T) {
|
||||
require.Error(err)
|
||||
}
|
||||
|
||||
func TestStoreServerLedActivationToken(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
ctx := context.Background()
|
||||
rootWrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, rootWrapper)
|
||||
|
||||
// Ensure the global scope contains a valid root key
|
||||
require.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
|
||||
rw := db.New(conn)
|
||||
rootStorage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
_, err = rotation.RotateRootCertificates(ctx, rootStorage)
|
||||
require.NoError(err)
|
||||
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
worker, err := repo.CreateWorker(ctx, &Worker{Worker: &store.Worker{ScopeId: scope.Global.String()}}, WithCreateControllerLedActivationToken(true))
|
||||
require.NoError(err)
|
||||
require.NotEmpty(worker.ControllerGeneratedActivationToken)
|
||||
|
||||
// We should now look for an activation token value in storage using the
|
||||
// lookup function and validate that it's populated
|
||||
tokenNonce := new(types.ServerLedActivationTokenNonce)
|
||||
marshaledNonce, err := base58.FastBase58Decoding(strings.TrimPrefix(worker.ControllerGeneratedActivationToken, nodeenrollment.ServerLedActivationTokenPrefix))
|
||||
require.NoError(err)
|
||||
require.NoError(proto.Unmarshal(marshaledNonce, tokenNonce))
|
||||
hm := hmac.New(sha256.New, tokenNonce.HmacKeyBytes)
|
||||
idBytes := hm.Sum(tokenNonce.Nonce)
|
||||
actToken := &types.ServerLedActivationToken{
|
||||
Id: base58.FastBase58Encoding(idBytes),
|
||||
}
|
||||
require.NoError(rootStorage.Load(ctx, actToken))
|
||||
require.NotNil(actToken.CreationTime)
|
||||
assert.False(actToken.CreationTime.AsTime().IsZero())
|
||||
assert.Less(time.Until(actToken.CreationTime.AsTime()), time.Duration(0))
|
||||
|
||||
require.NoError(rootStorage.Remove(ctx, actToken))
|
||||
require.Error(rootStorage.Load(ctx, actToken))
|
||||
}
|
||||
|
||||
func TestUnsupportedMessages(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
@@ -253,8 +299,7 @@ func TestStoreNodeInformationTx(t *testing.T) {
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, testWrapper)
|
||||
// Ensures the global scope contains a valid root key
|
||||
err = kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package store
|
||||
|
||||
// TableName overrides the table name used by Controller to `server_controller`
|
||||
func (Controller) TableName() string {
|
||||
func (*Controller) TableName() string {
|
||||
return "server_controller"
|
||||
}
|
||||
|
||||
// TableName overrides the table name used by WorkerTag to `worker_tag`
|
||||
func (WorkerTag) TableName() string {
|
||||
func (*WorkerTag) TableName() string {
|
||||
return "server_worker_tag"
|
||||
}
|
||||
@@ -203,6 +203,99 @@ func (x *WorkerCertBundle) GetCertBundle() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkerAuthServerLedActivationToken contains all fields related to a
|
||||
// WorkerAuthServerLedActivationToken resource
|
||||
type WorkerAuthServerLedActivationToken struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The worker_id of the worker that this activates
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
WorkerId string `protobuf:"bytes,10,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty" gorm:"primary_key"`
|
||||
// The token identifier, which is used for lookup
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
TokenId string `protobuf:"bytes,15,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty" gorm:"not_null"`
|
||||
// The creation time, encrypted to prevent tampering, as the time plus
|
||||
// existence of the record allows authorization
|
||||
// @inject_tag: `gorm:"not_null" wrapping:"ct,creation_time_data"`
|
||||
CreationTimeEncrypted []byte `protobuf:"bytes,20,opt,name=creation_time_encrypted,json=creationTimeEncrypted,proto3" json:"creation_time_encrypted,omitempty" gorm:"not_null" wrapping:"ct,creation_time_data"`
|
||||
// The plaintext bytes of the creation time, which are never stored. This is a
|
||||
// marshaled timestamppb.Timestamp.
|
||||
// @inject_tag: `gorm:"-" wrapping:"pt,creation_time_data"`
|
||||
CreationTime []byte `protobuf:"bytes,21,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty" gorm:"-" wrapping:"pt,creation_time_data"`
|
||||
// The key ID of the encrypting key
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
KeyId string `protobuf:"bytes,40,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"`
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) Reset() {
|
||||
*x = WorkerAuthServerLedActivationToken{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WorkerAuthServerLedActivationToken) ProtoMessage() {}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WorkerAuthServerLedActivationToken.ProtoReflect.Descriptor instead.
|
||||
func (*WorkerAuthServerLedActivationToken) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_servers_store_v1_worker_auth_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) GetWorkerId() string {
|
||||
if x != nil {
|
||||
return x.WorkerId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) GetTokenId() string {
|
||||
if x != nil {
|
||||
return x.TokenId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) GetCreationTimeEncrypted() []byte {
|
||||
if x != nil {
|
||||
return x.CreationTimeEncrypted
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) GetCreationTime() []byte {
|
||||
if x != nil {
|
||||
return x.CreationTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WorkerAuthServerLedActivationToken) GetKeyId() string {
|
||||
if x != nil {
|
||||
return x.KeyId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_servers_store_v1_worker_auth_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc = []byte{
|
||||
@@ -242,11 +335,24 @@ var file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc = []byte{
|
||||
0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x62,
|
||||
0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x65, 0x72,
|
||||
0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
|
||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x22, 0x57, 0x6f, 0x72, 0x6b,
|
||||
0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x41,
|
||||
0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b,
|
||||
0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
||||
0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, 0x23,
|
||||
0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
|
||||
0x15, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x28, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
|
||||
0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72,
|
||||
0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -261,10 +367,11 @@ func file_controller_storage_servers_store_v1_worker_auth_proto_rawDescGZIP() []
|
||||
return file_controller_storage_servers_store_v1_worker_auth_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_controller_storage_servers_store_v1_worker_auth_proto_goTypes = []interface{}{
|
||||
(*WorkerAuth)(nil), // 0: controller.storage.servers.store.v1.WorkerAuth
|
||||
(*WorkerCertBundle)(nil), // 1: controller.storage.servers.store.v1.WorkerCertBundle
|
||||
(*WorkerAuth)(nil), // 0: controller.storage.servers.store.v1.WorkerAuth
|
||||
(*WorkerCertBundle)(nil), // 1: controller.storage.servers.store.v1.WorkerCertBundle
|
||||
(*WorkerAuthServerLedActivationToken)(nil), // 2: controller.storage.servers.store.v1.WorkerAuthServerLedActivationToken
|
||||
}
|
||||
var file_controller_storage_servers_store_v1_worker_auth_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
@@ -304,6 +411,18 @@ func file_controller_storage_servers_store_v1_worker_auth_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkerAuthServerLedActivationToken); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -311,7 +430,7 @@ func file_controller_storage_servers_store_v1_worker_auth_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestKmsWorker(t *testing.T, conn *db.DB, wrapper wrapping.Wrapper, opt ...O
|
||||
serversRepo, err := NewRepository(rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
|
||||
namePart, err := newWorkerId(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -150,7 +150,7 @@ func TestPkiWorker(t *testing.T, conn *db.DB, wrapper wrapping.Wrapper, opt ...O
|
||||
serversRepo, err := NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
|
||||
require.NoError(t, err)
|
||||
wrk := NewWorker(scope.Global.String(),
|
||||
|
||||
@@ -2,10 +2,12 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -38,6 +40,8 @@ func TestTestKmsWorker(t *testing.T) {
|
||||
func TestTestPkiWorker(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
const (
|
||||
name = "test name"
|
||||
description = "test description"
|
||||
|
||||
@@ -67,13 +67,16 @@ type Worker struct {
|
||||
// other based on the context in which the worker is passed. As such
|
||||
// inputTags should only be read when performing mutations on the database.
|
||||
inputTags []*Tag `gorm:"-"`
|
||||
|
||||
// This is used to pass the token back to the calling function
|
||||
ControllerGeneratedActivationToken string `gorm:"-"`
|
||||
}
|
||||
|
||||
// NewWorker returns a new Worker. Valid options are WithName, WithDescription
|
||||
// WithAddress, and WithWorkerTags. All other options are ignored. This does
|
||||
// not set any of the worker reported values.
|
||||
func NewWorker(scopeId string, opt ...Option) *Worker {
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
return &Worker{
|
||||
Worker: &store.Worker{
|
||||
ScopeId: scopeId,
|
||||
|
||||
@@ -24,7 +24,7 @@ type WorkerKeys struct {
|
||||
|
||||
func newWorkerAuth(ctx context.Context, workerKeyIdentifier, workerId string, opt ...Option) (*WorkerAuth, error) {
|
||||
const op = "server.newWorkerAuth"
|
||||
opts := getOpts(opt...)
|
||||
opts := GetOpts(opt...)
|
||||
|
||||
if workerKeyIdentifier == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no workerKeyIdentifier")
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/server/store"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// WorkerAuthServerLedActivationToken contains an activation token for a worker
|
||||
type WorkerAuthServerLedActivationToken struct {
|
||||
*store.WorkerAuthServerLedActivationToken
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// newWorkerAuthServerLedActivation creates a new token from the given values
|
||||
func newWorkerAuthServerLedActivationToken(ctx context.Context, workerId, tokenId string, creationTime []byte, _ ...Option) (*WorkerAuthServerLedActivationToken, error) {
|
||||
const op = "server.newWorkerAuthServerLedActivationToken"
|
||||
|
||||
switch {
|
||||
case workerId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no worker id")
|
||||
case tokenId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no token id")
|
||||
case len(creationTime) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty creation time")
|
||||
}
|
||||
|
||||
l := &WorkerAuthServerLedActivationToken{
|
||||
WorkerAuthServerLedActivationToken: &store.WorkerAuthServerLedActivationToken{
|
||||
WorkerId: workerId,
|
||||
TokenId: tokenId,
|
||||
CreationTime: creationTime,
|
||||
},
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func allocWorkerAuthServerLedActivationToken() *WorkerAuthServerLedActivationToken {
|
||||
return &WorkerAuthServerLedActivationToken{
|
||||
WorkerAuthServerLedActivationToken: &store.WorkerAuthServerLedActivationToken{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkerAuthServerLedActivationToken) clone() *WorkerAuthServerLedActivationToken {
|
||||
cp := proto.Clone(w.WorkerAuthServerLedActivationToken)
|
||||
return &WorkerAuthServerLedActivationToken{
|
||||
WorkerAuthServerLedActivationToken: cp.(*store.WorkerAuthServerLedActivationToken),
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateNewWorkerAuthServerLedActivationToken is called before storing a WorkerAuthActivationToken in the db
|
||||
func (w *WorkerAuthServerLedActivationToken) ValidateNewWorkerAuthServerLedActivationToken(ctx context.Context) error {
|
||||
const op = "server.(WorkerAuthServerLedActivationToken).ValidateNewWorkerAuthServerLedActivationToken"
|
||||
if w.WorkerId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing worker id")
|
||||
}
|
||||
if w.TokenId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing token id")
|
||||
}
|
||||
if len(w.CreationTimeEncrypted) == 0 {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing encrypted creation time")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableName returns the table name.
|
||||
func (w *WorkerAuthServerLedActivationToken) TableName() string {
|
||||
if w.tableName != "" {
|
||||
return w.tableName
|
||||
}
|
||||
return "worker_auth_server_led_activation_token"
|
||||
}
|
||||
|
||||
// SetTableName sets the table name.
|
||||
func (w *WorkerAuthServerLedActivationToken) SetTableName(n string) {
|
||||
w.tableName = n
|
||||
}
|
||||
|
||||
// encrypt the activation token before storing
|
||||
func (w *WorkerAuthServerLedActivationToken) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "server.(WorkerAuthServerLedActivationToken).encrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
if err := structwrapping.WrapStruct(ctx, cipher, w.WorkerAuthServerLedActivationToken); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
|
||||
}
|
||||
keyId, err := cipher.KeyId(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id"))
|
||||
}
|
||||
w.KeyId = keyId
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// decrypt the auth method after reading it from the db
|
||||
func (w *WorkerAuthServerLedActivationToken) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "server.(WorkerAuthServerLedActivationToken).decrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
if err := structwrapping.UnwrapStruct(ctx, cipher, w.WorkerAuthServerLedActivationToken); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorkerAuthActivationTokenConstraints(t *testing.T) {
|
||||
t.Parallel()
|
||||
tlRequire, tlAssert := require.New(t), assert.New(t)
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrap := db.TestWrapper(t)
|
||||
|
||||
rw := db.New(conn)
|
||||
kmsCache := kms.TestKms(t, conn, wrap)
|
||||
tlRequire.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
|
||||
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
tlRequire.NoError(err)
|
||||
|
||||
// First create a worker without an activation token so we can verify it doesn't show up in the table
|
||||
origWorker := NewWorker(scope.Global.String())
|
||||
origWorker, err = repo.CreateWorker(ctx, origWorker)
|
||||
tlRequire.NoError(err)
|
||||
tlAssert.Empty(origWorker.ControllerGeneratedActivationToken)
|
||||
activationToken := allocWorkerAuthServerLedActivationToken()
|
||||
err = rw.LookupWhere(ctx, activationToken, "worker_id = ?", []any{origWorker.PublicId})
|
||||
tlRequire.Error(err)
|
||||
tlAssert.ErrorIs(err, dbw.ErrRecordNotFound)
|
||||
|
||||
// Now create a worker with an activation token and verify we can find it
|
||||
worker := NewWorker(scope.Global.String())
|
||||
worker, err = repo.CreateWorker(ctx, worker, WithCreateControllerLedActivationToken(true))
|
||||
tlRequire.NoError(err)
|
||||
tlAssert.NotEmpty(worker.ControllerGeneratedActivationToken)
|
||||
activationToken = allocWorkerAuthServerLedActivationToken()
|
||||
err = rw.LookupWhere(ctx, activationToken, "worker_id = ?", []any{worker.PublicId})
|
||||
tlRequire.NoError(err)
|
||||
tlAssert.Empty(activationToken.CreationTime)
|
||||
tlAssert.NotEmpty(activationToken.KeyId)
|
||||
tlAssert.NotEmpty(activationToken.TokenId)
|
||||
tlAssert.NotEmpty(activationToken.CreationTimeEncrypted)
|
||||
|
||||
// Run some test cases. All are expected to result in error.
|
||||
cases := []struct {
|
||||
name string
|
||||
createModifyFn func(*testing.T, *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken
|
||||
updateModifyFn func(*testing.T, *WorkerAuthServerLedActivationToken) (string, []any) // If set, will update instead of create
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "fully-duplicate",
|
||||
createModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken {
|
||||
return in
|
||||
},
|
||||
wantErrContains: "worker_auth_server_led_activation_token_pkey",
|
||||
},
|
||||
{
|
||||
name: "same-token-id",
|
||||
createModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken {
|
||||
in.WorkerId = origWorker.PublicId
|
||||
return in
|
||||
},
|
||||
wantErrContains: "worker_auth_server_led_activation_token_token_id_uq",
|
||||
},
|
||||
{
|
||||
name: "same-worker-id",
|
||||
createModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken {
|
||||
in.TokenId = in.TokenId[0:5]
|
||||
return in
|
||||
},
|
||||
wantErrContains: "worker_auth_server_led_activation_token_pkey",
|
||||
},
|
||||
{
|
||||
name: "modify-worker-id",
|
||||
updateModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) (string, []any) {
|
||||
return "update worker_auth_server_led_activation_token set worker_id = ? where token_id = ?", []any{origWorker.PublicId, in.TokenId}
|
||||
},
|
||||
wantErrContains: "immutable column: worker_auth_server_led_activation_token.worker_id: integrity violation",
|
||||
},
|
||||
{
|
||||
name: "modify-token-id",
|
||||
updateModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) (string, []any) {
|
||||
return "update worker_auth_server_led_activation_token set token_id = ? where worker_id = ?", []any{in.TokenId[0:5], in.WorkerId}
|
||||
},
|
||||
wantErrContains: "immutable column: worker_auth_server_led_activation_token.token_id: integrity violation",
|
||||
},
|
||||
{
|
||||
name: "modify-create-time",
|
||||
updateModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) (string, []any) {
|
||||
return "update worker_auth_server_led_activation_token set creation_time_encrypted = ? where worker_id = ?", []any{in.CreationTimeEncrypted[0:5], in.WorkerId}
|
||||
},
|
||||
wantErrContains: "immutable column: worker_auth_server_led_activation_token.creation_time_encrypted: integrity violation",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
testActivationToken := activationToken.clone()
|
||||
var err error
|
||||
if tc.updateModifyFn != nil {
|
||||
queryStr, args := tc.updateModifyFn(t, testActivationToken)
|
||||
var rowsUpdated int
|
||||
rowsUpdated, err = rw.Exec(ctx, queryStr, args)
|
||||
assert.Empty(rowsUpdated)
|
||||
} else {
|
||||
err = rw.Create(ctx, tc.createModifyFn(t, testActivationToken))
|
||||
}
|
||||
require.Error(err)
|
||||
assert.Contains(err.Error(), tc.wantErrContains)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user