mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
job statuses: fix filtering for namespace parameter (#23456)
The job statuses endpoint does not filter jobs by the namespace query parameter unless the user passes a management token. The RPC handler creates a filter based on all the allowed namespaces but improperly conditions reducing this down to only the requested set on there being a management token. Note this does not give the user access to jobs they shouldn't have, only ignores the parameter. Remove the RPC handler's extra condition that prevents using the requested namespace. This is safe because we specifically check the ACL for that namespace earlier in the handler. Fixes: https://github.com/hashicorp/nomad/issues/23370
This commit is contained in:
3
.changelog/23456.txt
Normal file
3
.changelog/23456.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:bug
|
||||
ui: Fixed support for namespace parameter on job statuses API
|
||||
```
|
||||
@@ -70,12 +70,10 @@ func (j *Job) Statuses(
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
// since the state index we're using doesn't include namespace,
|
||||
// explicitly add the user-provided ns to our filter if needed.
|
||||
// (allowableNamespaces will be nil if the caller sent a mgmt token)
|
||||
if allowableNamespaces == nil &&
|
||||
namespace != "" &&
|
||||
namespace != structs.AllNamespacesSentinel {
|
||||
// since the state index we're using doesn't include namespace, explicitly
|
||||
// set the user-provided ns to our filter if needed. we've already verified
|
||||
// that the user has access to the specific namespace above
|
||||
if namespace != "" && namespace != structs.AllNamespacesSentinel {
|
||||
allowableNamespaces = map[string]bool{
|
||||
namespace: true,
|
||||
}
|
||||
|
||||
@@ -21,22 +21,32 @@ func TestJob_Statuses_ACL(t *testing.T) {
|
||||
t.Cleanup(cleanup)
|
||||
testutil.WaitForLeader(t, s.RPC)
|
||||
|
||||
job1 := mock.MinJob()
|
||||
job2 := mock.MinJob()
|
||||
job2.Namespace = "infra"
|
||||
must.NoError(t, s.State().UpsertNamespaces(100, []*structs.Namespace{{Name: "infra"}}))
|
||||
must.NoError(t, s.State().UpsertJob(structs.MsgTypeTestSetup, 101, nil, job1))
|
||||
must.NoError(t, s.State().UpsertJob(structs.MsgTypeTestSetup, 102, nil, job2))
|
||||
|
||||
insufficientToken := mock.CreatePolicyAndToken(t, s.State(), 1, "job-lister",
|
||||
mock.NamespacePolicy("default", "", []string{"list-jobs"}))
|
||||
happyToken := mock.CreatePolicyAndToken(t, s.State(), 2, "job-reader",
|
||||
mock.NamespacePolicy("default", "", []string{"read-job"}))
|
||||
mock.NamespacePolicy("*", "", []string{"read-job"}))
|
||||
|
||||
for _, tc := range []struct {
|
||||
name, token, err string
|
||||
name, token, err, ns string
|
||||
expectJobs int
|
||||
}{
|
||||
{"no token", "", "Permission denied"},
|
||||
{"insufficient perms", insufficientToken.SecretID, "Permission denied"},
|
||||
{"happy token", happyToken.SecretID, ""},
|
||||
{"no token", "", "Permission denied", "", 0},
|
||||
{"insufficient perms", insufficientToken.SecretID, "Permission denied", "", 0},
|
||||
{"happy token specific ns", happyToken.SecretID, "", "infra", 1},
|
||||
{"happy token wildcard ns", happyToken.SecretID, "", "*", 2},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := &structs.JobStatusesRequest{}
|
||||
req.QueryOptions.Region = "global"
|
||||
req.QueryOptions.AuthToken = tc.token
|
||||
req.QueryOptions.Namespace = tc.ns
|
||||
|
||||
var resp structs.JobStatusesResponse
|
||||
err := s.RPC("Job.Statuses", &req, &resp)
|
||||
@@ -45,6 +55,8 @@ func TestJob_Statuses_ACL(t *testing.T) {
|
||||
must.ErrorContains(t, err, tc.err)
|
||||
} else {
|
||||
must.NoError(t, err)
|
||||
must.Len(t, tc.expectJobs, resp.Jobs,
|
||||
must.Sprint("expected jobs to be filtered by namespace"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user