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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user