Files
nomad/client/allocrunner/identity_hook_test.go
Tim Gross f0330d6df1 identity_hook: implement PreKill hook, not TaskStop hook (#18913)
The allocrunner's `identity_hook` implements the interface for TaskStop, but
this interface is only ever called for task-level hooks. This results in a
leaked goroutine that tries to periodically renew WIs until the client shuts
down gracefully.

Add an implementation for the allocrunner's `PreKill` and `Destroy` hooks, so
that whenever an allocation is stopped or garbage collected we stop renewing its
Workload Identities. This also requires making the `Shutdown` method of `WIDMgr`
safe to call multiple times.
2023-10-30 10:54:22 -04:00

93 lines
2.4 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package allocrunner
import (
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
cstate "github.com/hashicorp/nomad/client/state"
"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.RunnerPreKillHook = (*identityHook)(nil)
var _ interfaces.RunnerDestroyHook = (*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)
db := cstate.NewMemDB(logger)
// setup mock signer and WIDMgr
mockSigner := widmgr.NewMockWIDSigner(task.Identities)
mockWIDMgr := widmgr.NewWIDMgr(mockSigner, alloc, db, logger)
allocrunner.widmgr = mockWIDMgr
allocrunner.widsigner = mockSigner
// do the initial signing
_, err := mockSigner.SignIdentities(1, []*structs.WorkloadIdentityRequest{
{
AllocID: alloc.ID,
WIHandle: structs.WIHandle{
WorkloadIdentifier: 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(structs.WIHandle{
WorkloadIdentifier: 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(),
)
// shutting down twice must not panic
hook.PreKill()
hook.PreKill()
}