Skip to content

Commit

Permalink
client: split identity_hook across allocrunner and taskrunner (#18431)
Browse files Browse the repository at this point in the history
This commit splits identity_hook between the allocrunner and taskrunner. The
allocrunner-level part of the hook signs each task identity, and the
taskrunner-level part picks it up and stores secrets for each task.

The code revamps the WIDMgr, which is now split into 2 interfaces:
IdentityManager which manages renewals of signatures and handles sending
updates to subscribers via Watch method, and IdentitySigner which only does the
signing.

This work is necessary for having a unified Consul login workflow that comes
with the new Consul integration. A new, allocrunner-level consul_hook will now
be the only hook doing Consul authentication.
  • Loading branch information
pkazmierczak authored Sep 21, 2023
1 parent cf8dde0 commit 86d2cdc
Show file tree
Hide file tree
Showing 17 changed files with 823 additions and 403 deletions.
13 changes: 10 additions & 3 deletions client/allocrunner/alloc_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,11 @@ type allocRunner struct {
// partitions is an interface for managing cpuset partitions
partitions cinterfaces.CPUPartitions

// widmgr fetches workload identities
widmgr *widmgr.WIDMgr
// widsigner signs workload identities
widsigner widmgr.IdentitySigner

// widmgr manages workload identity signatures
widmgr widmgr.IdentityManager
}

// NewAllocRunner returns a new allocation runner.
Expand Down Expand Up @@ -251,7 +254,7 @@ func NewAllocRunner(config *config.AllocRunnerConfig) (interfaces.AllocRunner, e
wranglers: config.Wranglers,
partitions: config.Partitions,
hookResources: cstructs.NewAllocHookResources(),
widmgr: config.WIDMgr,
widsigner: config.WIDSigner,
}

// Create the logger based on the allocation ID
Expand All @@ -269,6 +272,10 @@ func NewAllocRunner(config *config.AllocRunnerConfig) (interfaces.AllocRunner, e
ar.shutdownDelayCtx = shutdownDelayCtx
ar.shutdownDelayCancelFn = shutdownDelayCancel

// initialize the workload identity manager
widmgr := widmgr.NewWIDMgr(ar.widsigner, alloc, ar.logger)
ar.widmgr = widmgr

// Initialize the runners hooks.
if err := ar.initRunnerHooks(config.ClientConfig); err != nil {
return nil, err
Expand Down
1 change: 1 addition & 0 deletions client/allocrunner/alloc_runner_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (ar *allocRunner) initRunnerHooks(config *clientconfig.Config) error {
// directory path exists for other hooks.
alloc := ar.Alloc()
ar.runnerHooks = []interfaces.RunnerHook{
newIdentityHook(hookLogger, ar.widmgr),
newAllocDirHook(hookLogger, ar.allocDir),
newUpstreamAllocsHook(hookLogger, ar.prevAllocWatcher),
newDiskMigrationHook(hookLogger, ar.prevAllocMigrator, ar.allocDir),
Expand Down
49 changes: 49 additions & 0 deletions client/allocrunner/identity_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package allocrunner

import (
"context"

log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
"github.com/hashicorp/nomad/client/widmgr"
)

type identityHook struct {
widmgr widmgr.IdentityManager
logger log.Logger
}

func newIdentityHook(logger log.Logger, widmgr widmgr.IdentityManager) *identityHook {
h := &identityHook{
widmgr: widmgr,
}
h.logger = logger.Named(h.Name())
return h
}

func (*identityHook) Name() string {
return "identity"
}

func (h *identityHook) Prerun() error {
// run the renewal
if err := h.widmgr.Run(); err != nil {
return err
}

return nil
}

// Stop implements interfaces.TaskStopHook
func (h *identityHook) Stop(context.Context, *interfaces.TaskStopRequest, *interfaces.TaskStopResponse) error {
h.widmgr.Shutdown()
return nil
}

// Shutdown implements interfaces.ShutdownHook
func (h *identityHook) Shutdown() {
h.widmgr.Shutdown()
}
87 changes: 87 additions & 0 deletions client/allocrunner/identity_hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package allocrunner

import (
"context"
"testing"
"time"

"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
cstructs "github.com/hashicorp/nomad/client/structs"
"github.com/hashicorp/nomad/client/widmgr"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/shoenig/test/must"
)

// statically assert network hook implements the expected interfaces
var _ interfaces.RunnerPrerunHook = (*identityHook)(nil)
var _ interfaces.ShutdownHook = (*identityHook)(nil)
var _ interfaces.TaskStopHook = (*identityHook)(nil)

func TestIdentityHook_Prerun(t *testing.T) {
ci.Parallel(t)

ttl := 30 * time.Second

wid := &structs.WorkloadIdentity{
Name: "testing",
Audience: []string{"consul.io"},
Env: true,
File: true,
TTL: ttl,
}

alloc := mock.Alloc()
task := alloc.LookupTask("web")
task.Identity = wid
task.Identities = []*structs.WorkloadIdentity{wid}

allocrunner, stopAR := TestAllocRunnerFromAlloc(t, alloc)
defer stopAR()

logger := testlog.HCLogger(t)

// setup mock signer and WIDMgr
mockSigner := widmgr.NewMockWIDSigner(task.Identities)
mockWIDMgr := widmgr.NewWIDMgr(mockSigner, alloc, logger)
allocrunner.widmgr = mockWIDMgr
allocrunner.widsigner = mockSigner

// do the initial signing
_, err := mockSigner.SignIdentities(1, []*structs.WorkloadIdentityRequest{
{
AllocID: alloc.ID,
TaskName: task.Name,
IdentityName: task.Identities[0].Name,
},
})
must.NoError(t, err)

start := time.Now()
hook := newIdentityHook(logger, mockWIDMgr)
must.Eq(t, hook.Name(), "identity")
must.NoError(t, hook.Prerun())

time.Sleep(time.Second) // give goroutines a moment to run
sid, err := hook.widmgr.Get(cstructs.TaskIdentity{
TaskName: task.Name,
IdentityName: task.Identities[0].Name},
)
must.Nil(t, err)
must.Eq(t, sid.IdentityName, task.Identity.Name)
must.NotEq(t, sid.JWT, "")

// pad expiry time with a second to be safe
must.Between(t,
start.Add(ttl).Add(-1*time.Second).Unix(),
sid.Expiration.Unix(),
start.Add(ttl).Add(1*time.Second).Unix(),
)

must.NoError(t, hook.Stop(context.Background(), nil, nil))
}
Loading

0 comments on commit 86d2cdc

Please sign in to comment.