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:
Jeff MitchellandIrena Rindos authored and GitHub committed 2022-09-02 14:32:41 -04:00
1 parent a1be125f5b
commit 01fb949d0b
55 files changed
+2728 -625

No files matched your search

+89 -37
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math/rand"
"net"
"os"
"os/signal"
@@ -90,7 +91,7 @@ type Command struct {
flagEveryEventDenyFilters []string
flagCreateLoopbackHostPlugin bool
flagPluginExecutionDir string
flagUseKmsWorkerAuthMethod bool
flagWorkerAuthMethod string
flagWorkerAuthStorageDir string
flagWorkerAuthStorageSkipCleanup bool
flagWorkerAuthRotationInterval time.Duration
@@ -337,10 +338,11 @@ func (c *Command) Flags() *base.FlagSets {
EnvVar: "BOUNDARY_DEV_PLUGIN_EXECUTION_DIR",
Usage: "Specifies where Boundary should write plugins that it is executing; if not set defaults to system temp directory.",
})
f.BoolVar(&base.BoolVar{
Name: "use-kms-worker-auth-method",
Target: &c.flagUseKmsWorkerAuthMethod,
Usage: "If set, the original KMS-based method of worker auth will be used to connect the initial dev worker to the controller.",
f.StringVar(&base.StringVar{
Name: "worker-auth-method",
Target: &c.flagWorkerAuthMethod,
Default: "random-pki",
Usage: `Allows specifying how the generated worker will authenticate to the controller. Valid values are "kms" for the KMS-based mechanism; "pki-controller-led" for the PKI mechanism via the server-led authorization flow; "pki-worker-led" for the PKI mechanism via the worker-led authorization flow; and "random-pki" to randomly choose one of the PKI methods.`,
})
f.StringVar(&base.StringVar{
@@ -598,7 +600,7 @@ func (c *Command) Run(args []string) int {
c.InfoKeys = append(c.InfoKeys, "[Worker-Auth] AEAD Key Bytes")
c.Info["[Worker-Auth] AEAD Key Bytes"] = c.Config.DevWorkerAuthKey
if !c.flagUseKmsWorkerAuthMethod {
if c.flagWorkerAuthMethod != "kms" {
c.DevUsePkiForUpstream = true
// These must be unset for PKI
c.Config.Worker.Name = ""
@@ -701,6 +703,54 @@ func (c *Command) Run(args []string) int {
c.worker.TestOverrideAuthRotationPeriod = c.flagWorkerAuthRotationInterval
}
// Note: this should be done before starting the worker so that the
// activation token is populated
var useWorkerLed bool
if c.flagWorkerAuthMethod != "kms" {
// Flip a coin. Use one method or the other; it's transparent to
// users, but keeps both exercised.
if c.flagWorkerAuthMethod == "random-pki" {
randPki := rand.New(rand.NewSource(time.Now().UnixMicro())).Intn(2)
if randPki == 0 {
c.flagWorkerAuthMethod = "pki-controller-led"
} else {
c.flagWorkerAuthMethod = "pki-worker-led"
}
}
switch c.flagWorkerAuthMethod {
case "pki-controller-led":
// Controller-led
serversRepo, err := c.controller.ServersRepoFn()
if err != nil {
c.UI.Error(fmt.Errorf("Error instantiating server repo: %w", err).Error())
if err := c.controller.Shutdown(); err != nil {
c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error())
}
return base.CommandCliError
}
// Create the worker in the database and fetch an activation token
worker, err := serversRepo.CreateWorker(c.Context, &server.Worker{
Worker: &store.Worker{
ScopeId: scope.Global.String(),
},
}, server.WithCreateControllerLedActivationToken(true))
if err != nil {
c.UI.Error(fmt.Errorf("Error creating worker in database: %w", err).Error())
if err := c.controller.Shutdown(); err != nil {
c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error())
}
return base.CommandCliError
}
// Set the activation token in the config
conf.RawConfig.Worker.ControllerGeneratedActivationToken = worker.ControllerGeneratedActivationToken
default:
useWorkerLed = true
}
}
if err := c.worker.Start(); err != nil {
retErr := fmt.Errorf("Error starting worker: %w", err)
if err := c.worker.Shutdown(); err != nil {
@@ -714,46 +764,48 @@ func (c *Command) Run(args []string) int {
return base.CommandCliError
}
if !c.flagUseKmsWorkerAuthMethod {
req := c.worker.WorkerAuthRegistrationRequest
if req == "" {
c.UI.Error("No worker auth registration request found at worker start time")
return base.CommandCliError
}
c.InfoKeys = append(c.InfoKeys, "worker auth registration request")
c.Info["worker auth registration request"] = req
if c.flagWorkerAuthMethod != "kms" {
c.InfoKeys = append(c.InfoKeys, "worker auth current key id")
c.Info["worker auth current key id"] = c.worker.WorkerAuthCurrentKeyId.Load()
c.InfoKeys = append(c.InfoKeys, "worker auth storage path")
c.Info["worker auth storage path"] = c.worker.WorkerAuthStorage.BaseDir()
if err := c.StoreWorkerAuthReq(c.worker.WorkerAuthRegistrationRequest, c.worker.WorkerAuthStorage.BaseDir()); err != nil {
// Shutdown on failure
retErr := fmt.Errorf("Error storing worker auth request: %w", err)
if err := c.worker.Shutdown(); err != nil {
if useWorkerLed {
req := c.worker.WorkerAuthRegistrationRequest
if req == "" {
c.UI.Error("No worker auth registration request found at worker start time")
return base.CommandCliError
}
if err := c.StoreWorkerAuthReq(c.worker.WorkerAuthRegistrationRequest, c.worker.WorkerAuthStorage.BaseDir()); err != nil {
// Shutdown on failure
retErr := fmt.Errorf("Error storing worker auth request: %w", err)
if err := c.worker.Shutdown(); err != nil {
c.UI.Error(retErr.Error())
retErr = fmt.Errorf("Error shutting down worker: %w", err)
}
c.UI.Error(retErr.Error())
retErr = fmt.Errorf("Error shutting down worker: %w", err)
if err := c.controller.Shutdown(); err != nil {
c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error())
}
return base.CommandCliError
}
c.UI.Error(retErr.Error())
if err := c.controller.Shutdown(); err != nil {
c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error())
}
return base.CommandCliError
}
go func() {
for {
select {
case <-c.Context.Done():
return
case <-time.After(time.Second):
if err := authorizeWorker(c.Context, c.controller, req); err != nil {
c.UI.Error(fmt.Errorf("Error authorizing node: %w", err).Error())
errorEncountered.Store(true)
go func() {
for {
select {
case <-c.Context.Done():
return
case <-time.After(time.Second):
if err := authorizeWorker(c.Context, c.controller, req); err != nil {
c.UI.Error(fmt.Errorf("Error authorizing node: %w", err).Error())
errorEncountered.Store(true)
return
}
return
}
return
}
}
}()
}()
}
}
}