diff --git a/nomad/job_endpoint.go b/nomad/job_endpoint.go index 0b8cc9cb8..36f91834f 100644 --- a/nomad/job_endpoint.go +++ b/nomad/job_endpoint.go @@ -285,6 +285,14 @@ func (j *Job) Summary(args *structs.JobSummaryRequest, return err } defer metrics.MeasureSince([]string{"nomad", "job_summary", "get_job_summary"}, time.Now()) + + // Check for read-job permissions + if aclObj, err := j.srv.resolveToken(args.SecretID); err != nil { + return err + } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { + return structs.ErrPermissionDenied + } + // Setup the blocking query opts := blockingOptions{ queryOpts: &args.QueryOptions, diff --git a/nomad/job_endpoint_test.go b/nomad/job_endpoint_test.go index 0f1640528..a3d933055 100644 --- a/nomad/job_endpoint_test.go +++ b/nomad/job_endpoint_test.go @@ -8,7 +8,8 @@ import ( "time" memdb "github.com/hashicorp/go-memdb" - "github.com/hashicorp/net-rpc-msgpackrpc" + msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" + "github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" @@ -2006,6 +2007,93 @@ func TestJobEndpoint_GetJobSummary(t *testing.T) { } } +func TestJobEndpoint_Summary_ACL(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + srv, root := testACLServer(t, func(c *Config) { + c.NumSchedulers = 0 // Prevent automatic dequeue + }) + defer srv.Shutdown() + codec := rpcClient(t, srv) + testutil.WaitForLeader(t, srv.RPC) + + // Create the job + job := mock.Job() + reg := &structs.JobRegisterRequest{ + Job: job, + WriteRequest: structs.WriteRequest{ + Region: "global", + Namespace: job.Namespace, + }, + } + reg.SecretID = root.SecretID + + var err error + + // Register the job with a valid token + var regResp structs.JobRegisterResponse + err = msgpackrpc.CallWithCodec(codec, "Job.Register", reg, ®Resp) + assert.Nil(err) + + job.CreateIndex = regResp.JobModifyIndex + job.ModifyIndex = regResp.JobModifyIndex + job.JobModifyIndex = regResp.JobModifyIndex + + req := &structs.JobSummaryRequest{ + JobID: job.ID, + QueryOptions: structs.QueryOptions{ + Region: "global", + Namespace: job.Namespace, + }, + } + + // Expect failure for request without a token + var resp structs.JobSummaryResponse + err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp) + assert.NotNil(err) + + expectedJobSummary := &structs.JobSummary{ + JobID: job.ID, + Namespace: job.Namespace, + Summary: map[string]structs.TaskGroupSummary{ + "web": structs.TaskGroupSummary{}, + }, + Children: new(structs.JobChildrenSummary), + CreateIndex: job.CreateIndex, + ModifyIndex: job.ModifyIndex, + } + + // Expect success when using a management token + req.SecretID = root.SecretID + var mgmtResp structs.JobSummaryResponse + err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &mgmtResp) + assert.Nil(err) + assert.Equal(expectedJobSummary, mgmtResp.JobSummary) + + // Create the namespace policy and tokens + state := srv.fsm.State() + + // Expect failure for request with an invalid token + invalidToken := CreatePolicyAndToken(t, state, 1003, "test-invalid", + NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) + + req.SecretID = invalidToken.SecretID + var invalidResp structs.JobSummaryResponse + err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &invalidResp) + assert.NotNil(err) + + // Try with a valid token + validToken := CreatePolicyAndToken(t, state, 1001, "test-valid", + NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) + + req.SecretID = validToken.SecretID + var authResp structs.JobSummaryResponse + err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &authResp) + assert.Nil(err) + assert.Equal(expectedJobSummary, authResp.JobSummary) +} + func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) { t.Parallel() s1 := testServer(t, nil) diff --git a/website/source/api/jobs.html.md b/website/source/api/jobs.html.md index 28661226d..9478e1733 100644 --- a/website/source/api/jobs.html.md +++ b/website/source/api/jobs.html.md @@ -972,9 +972,9 @@ The table below shows this endpoint's support for [blocking queries](/api/index.html#blocking-queries) and [required ACLs](/api/index.html#acls). -| Blocking Queries | ACL Required | -| ---------------- | ------------ | -| `YES` | `none` | +| Blocking Queries | ACL Required | +| ---------------- | -------------------------- | +| `YES` | `namespace:read-job` | ### Parameters