Only generate default workload identity once per alloc task - 1.6.x (#18783)
this can save a bit of cpu when running plans for tasks that already exist, and prevents Nomad tokens from changing, which can cause task template{}s to restart unnecessarily.
This commit is contained in:
parent
657c430e0b
commit
b6298dc073
|
@ -55,13 +55,6 @@ func (h *identityHook) Prestart(ctx context.Context, req *interfaces.TaskPrestar
|
||||||
return h.setToken()
|
return h.setToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *identityHook) Update(_ context.Context, req *interfaces.TaskUpdateRequest, _ *interfaces.TaskUpdateResponse) error {
|
|
||||||
h.lock.Lock()
|
|
||||||
defer h.lock.Unlock()
|
|
||||||
|
|
||||||
return h.setToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
// setToken adds the Nomad token to the task's environment and writes it to a
|
// setToken adds the Nomad token to the task's environment and writes it to a
|
||||||
// file if requested by the jobsepc.
|
// file if requested by the jobsepc.
|
||||||
func (h *identityHook) setToken() error {
|
func (h *identityHook) setToken() error {
|
||||||
|
|
|
@ -6,6 +6,5 @@ package taskrunner
|
||||||
import "github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
import "github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
||||||
|
|
||||||
var _ interfaces.TaskPrestartHook = (*identityHook)(nil)
|
var _ interfaces.TaskPrestartHook = (*identityHook)(nil)
|
||||||
var _ interfaces.TaskUpdateHook = (*identityHook)(nil)
|
|
||||||
|
|
||||||
// See task_runner_test.go:TestTaskRunner_IdentityHook
|
// See task_runner_test.go:TestTaskRunner_IdentityHook
|
||||||
|
|
|
@ -30,6 +30,12 @@ import (
|
||||||
|
|
||||||
const nomadKeystoreExtension = ".nks.json"
|
const nomadKeystoreExtension = ".nks.json"
|
||||||
|
|
||||||
|
type claimSigner interface {
|
||||||
|
SignClaims(*structs.IdentityClaims) (string, string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ claimSigner = &Encrypter{}
|
||||||
|
|
||||||
// Encrypter is the keyring for encrypting variables and signing workload
|
// Encrypter is the keyring for encrypting variables and signing workload
|
||||||
// identities.
|
// identities.
|
||||||
type Encrypter struct {
|
type Encrypter struct {
|
||||||
|
|
|
@ -21,6 +21,18 @@ import (
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type mockSigner struct {
|
||||||
|
calls []*structs.IdentityClaims
|
||||||
|
|
||||||
|
nextToken, nextKeyID string
|
||||||
|
nextErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSigner) SignClaims(c *structs.IdentityClaims) (token, keyID string, err error) {
|
||||||
|
s.calls = append(s.calls, c)
|
||||||
|
return s.nextToken, s.nextKeyID, s.nextErr
|
||||||
|
}
|
||||||
|
|
||||||
// TestEncrypter_LoadSave exercises round-tripping keys to disk
|
// TestEncrypter_LoadSave exercises round-tripping keys to disk
|
||||||
func TestEncrypter_LoadSave(t *testing.T) {
|
func TestEncrypter_LoadSave(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
|
|
|
@ -279,7 +279,7 @@ func (p *planner) applyPlan(plan *structs.Plan, result *structs.PlanResult, snap
|
||||||
// to approximate the scheduling time.
|
// to approximate the scheduling time.
|
||||||
updateAllocTimestamps(req.AllocsUpdated, now)
|
updateAllocTimestamps(req.AllocsUpdated, now)
|
||||||
|
|
||||||
err := p.signAllocIdentities(plan.Job, req.AllocsUpdated)
|
err := signAllocIdentities(p.Server.encrypter, plan.Job, req.AllocsUpdated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -409,16 +409,19 @@ func updateAllocTimestamps(allocations []*structs.Allocation, timestamp int64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *planner) signAllocIdentities(job *structs.Job, allocations []*structs.Allocation) error {
|
func signAllocIdentities(signer claimSigner, job *structs.Job, allocations []*structs.Allocation) error {
|
||||||
|
|
||||||
encrypter := p.Server.encrypter
|
|
||||||
|
|
||||||
for _, alloc := range allocations {
|
for _, alloc := range allocations {
|
||||||
alloc.SignedIdentities = map[string]string{}
|
if alloc.SignedIdentities == nil {
|
||||||
|
alloc.SignedIdentities = map[string]string{}
|
||||||
|
}
|
||||||
tg := job.LookupTaskGroup(alloc.TaskGroup)
|
tg := job.LookupTaskGroup(alloc.TaskGroup)
|
||||||
for _, task := range tg.Tasks {
|
for _, task := range tg.Tasks {
|
||||||
|
// skip tasks that already have an identity
|
||||||
|
if _, ok := alloc.SignedIdentities[task.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
claims := alloc.ToTaskIdentityClaims(job, task.Name)
|
claims := alloc.ToTaskIdentityClaims(job, task.Name)
|
||||||
token, keyID, err := encrypter.SignClaims(claims)
|
token, keyID, err := signer.SignClaims(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package nomad
|
package nomad
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -393,6 +395,78 @@ func TestPlanApply_applyPlanWithNormalizedAllocs(t *testing.T) {
|
||||||
assert.Equal(index, evalOut.ModifyIndex)
|
assert.Equal(index, evalOut.ModifyIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlanApply_signAllocIdentities(t *testing.T) {
|
||||||
|
// note: this is mutated by the method under test
|
||||||
|
alloc := mockAlloc()
|
||||||
|
job := alloc.Job
|
||||||
|
taskName := job.TaskGroups[0].Tasks[0].Name // "web"
|
||||||
|
allocs := []*structs.Allocation{alloc}
|
||||||
|
|
||||||
|
signErr := errors.New("could not sign the thing")
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
signer *mockSigner
|
||||||
|
expectErr error
|
||||||
|
callNum int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "signer error",
|
||||||
|
signer: &mockSigner{
|
||||||
|
nextErr: signErr,
|
||||||
|
},
|
||||||
|
expectErr: signErr,
|
||||||
|
callNum: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "first signing",
|
||||||
|
signer: &mockSigner{
|
||||||
|
nextToken: "first-token",
|
||||||
|
nextKeyID: "first-key",
|
||||||
|
},
|
||||||
|
callNum: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "second signing",
|
||||||
|
signer: &mockSigner{
|
||||||
|
nextToken: "dont-sign-token",
|
||||||
|
nextKeyID: "dont-sign-key",
|
||||||
|
},
|
||||||
|
callNum: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
err := signAllocIdentities(tc.signer, job, allocs)
|
||||||
|
|
||||||
|
if tc.expectErr != nil {
|
||||||
|
must.Error(t, err)
|
||||||
|
must.ErrorIs(t, err, tc.expectErr)
|
||||||
|
} else {
|
||||||
|
must.NoError(t, err)
|
||||||
|
// assert mutations happened
|
||||||
|
must.MapLen(t, 1, allocs[0].SignedIdentities)
|
||||||
|
// we should always keep the first signing
|
||||||
|
must.Eq(t, "first-token", allocs[0].SignedIdentities[taskName])
|
||||||
|
must.Eq(t, "first-key", allocs[0].SigningKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
must.Len(t, tc.callNum, tc.signer.calls, must.Sprint("unexpected call count"))
|
||||||
|
if tc.callNum > 0 {
|
||||||
|
call := tc.signer.calls[tc.callNum-1]
|
||||||
|
must.NotNil(t, call)
|
||||||
|
must.Eq(t, call.AllocationID, alloc.ID)
|
||||||
|
must.Eq(t, call.Namespace, alloc.Namespace)
|
||||||
|
must.Eq(t, call.JobID, job.ID)
|
||||||
|
must.Eq(t, call.TaskName, taskName)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPlanApply_EvalPlan_Simple(t *testing.T) {
|
func TestPlanApply_EvalPlan_Simple(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
state := testStateStore(t)
|
state := testStateStore(t)
|
||||||
|
|
|
@ -888,7 +888,7 @@ func TestServiceRegistration_List(t *testing.T) {
|
||||||
job.Namespace = "platform"
|
job.Namespace = "platform"
|
||||||
allocs[0].Namespace = "platform"
|
allocs[0].Namespace = "platform"
|
||||||
require.NoError(t, s.State().UpsertJob(structs.MsgTypeTestSetup, 10, nil, job))
|
require.NoError(t, s.State().UpsertJob(structs.MsgTypeTestSetup, 10, nil, job))
|
||||||
s.signAllocIdentities(job, allocs)
|
signAllocIdentities(s.encrypter, job, allocs)
|
||||||
require.NoError(t, s.State().UpsertAllocs(structs.MsgTypeTestSetup, 15, allocs))
|
require.NoError(t, s.State().UpsertAllocs(structs.MsgTypeTestSetup, 15, allocs))
|
||||||
|
|
||||||
signedToken := allocs[0].SignedIdentities["web"]
|
signedToken := allocs[0].SignedIdentities["web"]
|
||||||
|
@ -1175,7 +1175,7 @@ func TestServiceRegistration_GetService(t *testing.T) {
|
||||||
allocs := []*structs.Allocation{mock.Alloc()}
|
allocs := []*structs.Allocation{mock.Alloc()}
|
||||||
job := allocs[0].Job
|
job := allocs[0].Job
|
||||||
require.NoError(t, s.State().UpsertJob(structs.MsgTypeTestSetup, 10, nil, job))
|
require.NoError(t, s.State().UpsertJob(structs.MsgTypeTestSetup, 10, nil, job))
|
||||||
s.signAllocIdentities(job, allocs)
|
signAllocIdentities(s.encrypter, job, allocs)
|
||||||
require.NoError(t, s.State().UpsertAllocs(structs.MsgTypeTestSetup, 15, allocs))
|
require.NoError(t, s.State().UpsertAllocs(structs.MsgTypeTestSetup, 15, allocs))
|
||||||
|
|
||||||
signedToken := allocs[0].SignedIdentities["web"]
|
signedToken := allocs[0].SignedIdentities["web"]
|
||||||
|
|
Loading…
Reference in New Issue