diff --git a/.changelog/23456.txt b/.changelog/23456.txt new file mode 100644 index 000000000..75f9b107e --- /dev/null +++ b/.changelog/23456.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fixed support for namespace parameter on job statuses API +``` diff --git a/nomad/job_endpoint_statuses.go b/nomad/job_endpoint_statuses.go index 7b54c2b83..d55ae7c36 100644 --- a/nomad/job_endpoint_statuses.go +++ b/nomad/job_endpoint_statuses.go @@ -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, } diff --git a/nomad/job_endpoint_statuses_test.go b/nomad/job_endpoint_statuses_test.go index c5b3367d8..af43f34d2 100644 --- a/nomad/job_endpoint_statuses_test.go +++ b/nomad/job_endpoint_statuses_test.go @@ -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")) } }) }