Unverified Commit c9ee4be9 authored by Tim Gross's avatar Tim Gross Committed by GitHub
Browse files

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.
No related merge requests found
Showing with 38 additions and 10 deletions
+38 -10
...@@ -298,7 +298,7 @@ func TestEncrypter_SignVerify(t *testing.T) { ...@@ -298,7 +298,7 @@ func TestEncrypter_SignVerify(t *testing.T) {
testutil.WaitForLeader(t, srv.RPC) testutil.WaitForLeader(t, srv.RPC)
alloc := mock.Alloc() alloc := mock.Alloc()
claim := alloc.ToTaskIdentityClaims("web") claim := alloc.ToTaskIdentityClaims(nil, "web")
e := srv.encrypter e := srv.encrypter
out, err := e.SignClaims(claim) out, err := e.SignClaims(claim)
......
...@@ -415,7 +415,7 @@ func (p *planner) signAllocIdentities(job *structs.Job, allocations []*structs.A ...@@ -415,7 +415,7 @@ func (p *planner) signAllocIdentities(job *structs.Job, allocations []*structs.A
alloc.SignedIdentities = map[string]string{} 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 {
claims := alloc.ToTaskIdentityClaims(task.Name) claims := alloc.ToTaskIdentityClaims(job, task.Name)
token, err := encrypter.SignClaims(claims) token, err := encrypter.SignClaims(claims)
if err != nil { if err != nil {
return err return err
......
...@@ -515,7 +515,7 @@ func (sv *SecureVariables) authValidatePrefix(claims *structs.IdentityClaims, ns ...@@ -515,7 +515,7 @@ func (sv *SecureVariables) authValidatePrefix(claims *structs.IdentityClaims, ns
} }
parts := strings.Split(pathOrPrefix, "/") 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) { if len(parts) > len(expect) {
return structs.ErrPermissionDenied return structs.ErrPermissionDenied
} }
......
...@@ -39,19 +39,29 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) { ...@@ -39,19 +39,29 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) {
alloc2.Job.Namespace = ns alloc2.Job.Namespace = ns
alloc2.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() store := srv.fsm.State()
require.NoError(t, store.UpsertNamespaces(1000, []*structs.Namespace{{Name: ns}})) require.NoError(t, store.UpsertNamespaces(1000, []*structs.Namespace{{Name: ns}}))
require.NoError(t, store.UpsertAllocs( 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) idToken, err := srv.encrypter.SignClaims(claims1)
require.NoError(t, err) require.NoError(t, err)
claims2 := alloc2.ToTaskIdentityClaims("web") claims2 := alloc2.ToTaskIdentityClaims(nil, "web")
noPermissionsToken, err := srv.encrypter.SignClaims(claims2) noPermissionsToken, err := srv.encrypter.SignClaims(claims2)
require.NoError(t, err) 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 // corrupt the signature of the token
idTokenParts := strings.Split(idToken, ".") idTokenParts := strings.Split(idToken, ".")
require.Len(t, idTokenParts, 3) require.Len(t, idTokenParts, 3)
...@@ -125,6 +135,13 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) { ...@@ -125,6 +135,13 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) {
path: fmt.Sprintf("nomad/jobs/%s", jobID), path: fmt.Sprintf("nomad/jobs/%s", jobID),
expectedErr: nil, 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", name: "valid claim for path with namespace secret",
token: idToken, token: idToken,
...@@ -201,6 +218,13 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) { ...@@ -201,6 +218,13 @@ func TestSecureVariablesEndpoint_auth(t *testing.T) {
path: fmt.Sprintf("nomad/jobs/%s/web/web", jobID), path: fmt.Sprintf("nomad/jobs/%s/web/web", jobID),
expectedErr: structs.ErrPermissionDenied, 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", name: "acl token read policy is allowed to list",
token: aclToken.SecretID, token: aclToken.SecretID,
......
...@@ -10312,9 +10312,9 @@ func (a *Allocation) Reconnected() (bool, bool) { ...@@ -10312,9 +10312,9 @@ func (a *Allocation) Reconnected() (bool, bool) {
return true, a.Expired(lastReconnect) return true, a.Expired(lastReconnect)
} }
func (a *Allocation) ToIdentityClaims() *IdentityClaims { func (a *Allocation) ToIdentityClaims(job *Job) *IdentityClaims {
now := jwt.NewNumericDate(time.Now().UTC()) now := jwt.NewNumericDate(time.Now().UTC())
return &IdentityClaims{ claims := &IdentityClaims{
Namespace: a.Namespace, Namespace: a.Namespace,
JobID: a.JobID, JobID: a.JobID,
AllocationID: a.ID, AllocationID: a.ID,
...@@ -10327,10 +10327,14 @@ func (a *Allocation) ToIdentityClaims() *IdentityClaims { ...@@ -10327,10 +10327,14 @@ func (a *Allocation) ToIdentityClaims() *IdentityClaims {
IssuedAt: now, IssuedAt: now,
}, },
} }
if job != nil && job.ParentID != "" {
claims.JobID = job.ParentID
}
return claims
} }
func (a *Allocation) ToTaskIdentityClaims(taskName string) *IdentityClaims { func (a *Allocation) ToTaskIdentityClaims(job *Job, taskName string) *IdentityClaims {
claims := a.ToIdentityClaims() claims := a.ToIdentityClaims(job)
if claims != nil { if claims != nil {
claims.TaskName = taskName claims.TaskName = taskName
} }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment