workload identity: use parent ID for dispatch/periodic jobs (#13748)

Workload identities grant implicit access to policies, and operators
will not want to craft separate policies for each invocation of a
periodic or dispatch job. Use the parent job's ID as the JobID claim.
This commit is contained in:
Tim Gross 2022-07-21 09:05:54 -04:00 committed by GitHub
parent 9c43c28575
commit d11da1df5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 10 deletions

View File

@ -298,7 +298,7 @@ func TestEncrypter_SignVerify(t *testing.T) {
testutil.WaitForLeader(t, srv.RPC)
alloc := mock.Alloc()
claim := alloc.ToTaskIdentityClaims("web")
claim := alloc.ToTaskIdentityClaims(nil, "web")
e := srv.encrypter
out, err := e.SignClaims(claim)

View File

@ -415,7 +415,7 @@ func (p *planner) signAllocIdentities(job *structs.Job, allocations []*structs.A
alloc.SignedIdentities = map[string]string{}
tg := job.LookupTaskGroup(alloc.TaskGroup)
for _, task := range tg.Tasks {
claims := alloc.ToTaskIdentityClaims(task.Name)
claims := alloc.ToTaskIdentityClaims(job, task.Name)
token, err := encrypter.SignClaims(claims)
if err != nil {
return err

View File

@ -515,7 +515,7 @@ func (sv *SecureVariables) authValidatePrefix(claims *structs.IdentityClaims, ns
}
parts := strings.Split(pathOrPrefix, "/")
expect := []string{"nomad", "jobs", alloc.Job.ID, alloc.TaskGroup, claims.TaskName}
expect := []string{"nomad", "jobs", claims.JobID, alloc.TaskGroup, claims.TaskName}
if len(parts) > len(expect) {
return structs.ErrPermissionDenied
}

View File

@ -39,19 +39,29 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) {
alloc2.Job.Namespace = ns
alloc2.Namespace = ns
alloc3 := mock.Alloc()
alloc3.ClientStatus = structs.AllocClientStatusRunning
alloc3.Job.Namespace = ns
alloc3.Namespace = ns
alloc3.Job.ParentID = jobID
store := srv.fsm.State()
require.NoError(t, store.UpsertNamespaces(1000, []*structs.Namespace{{Name: ns}}))
require.NoError(t, store.UpsertAllocs(
structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc1, alloc2}))
structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc1, alloc2, alloc3}))
claims1 := alloc1.ToTaskIdentityClaims("web")
claims1 := alloc1.ToTaskIdentityClaims(nil, "web")
idToken, err := srv.encrypter.SignClaims(claims1)
require.NoError(t, err)
claims2 := alloc2.ToTaskIdentityClaims("web")
claims2 := alloc2.ToTaskIdentityClaims(nil, "web")
noPermissionsToken, err := srv.encrypter.SignClaims(claims2)
require.NoError(t, err)
claims3 := alloc3.ToTaskIdentityClaims(alloc3.Job, "web")
idDispatchToken, err := srv.encrypter.SignClaims(claims3)
require.NoError(t, err)
// corrupt the signature of the token
idTokenParts := strings.Split(idToken, ".")
require.Len(t, idTokenParts, 3)
@ -125,6 +135,13 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) {
path: fmt.Sprintf("nomad/jobs/%s", jobID),
expectedErr: nil,
},
{
name: "valid claim for path with dispatch job secret",
token: idDispatchToken,
cap: "n/a",
path: fmt.Sprintf("nomad/jobs/%s", jobID),
expectedErr: nil,
},
{
name: "valid claim for path with namespace secret",
token: idToken,
@ -201,6 +218,13 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) {
path: fmt.Sprintf("nomad/jobs/%s/web/web", jobID),
expectedErr: structs.ErrPermissionDenied,
},
{
name: "invalid claim for dispatched ID",
token: idDispatchToken,
cap: "n/a",
path: fmt.Sprintf("nomad/jobs/%s", alloc3.JobID),
expectedErr: structs.ErrPermissionDenied,
},
{
name: "acl token read policy is allowed to list",
token: aclToken.SecretID,

View File

@ -10312,9 +10312,9 @@ func (a *Allocation) Reconnected() (bool, bool) {
return true, a.Expired(lastReconnect)
}
func (a *Allocation) ToIdentityClaims() *IdentityClaims {
func (a *Allocation) ToIdentityClaims(job *Job) *IdentityClaims {
now := jwt.NewNumericDate(time.Now().UTC())
return &IdentityClaims{
claims := &IdentityClaims{
Namespace: a.Namespace,
JobID: a.JobID,
AllocationID: a.ID,
@ -10327,10 +10327,14 @@ func (a *Allocation) ToIdentityClaims() *IdentityClaims {
IssuedAt: now,
},
}
if job != nil && job.ParentID != "" {
claims.JobID = job.ParentID
}
return claims
}
func (a *Allocation) ToTaskIdentityClaims(taskName string) *IdentityClaims {
claims := a.ToIdentityClaims()
func (a *Allocation) ToTaskIdentityClaims(job *Job, taskName string) *IdentityClaims {
claims := a.ToIdentityClaims(job)
if claims != nil {
claims.TaskName = taskName
}