diff --git a/nomad/alloc_endpoint.go b/nomad/alloc_endpoint.go index bc14e1407..cbd0d5dd9 100644 --- a/nomad/alloc_endpoint.go +++ b/nomad/alloc_endpoint.go @@ -5,6 +5,7 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/go-memdb" + "github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/structs" ) @@ -21,6 +22,13 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes } defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now()) + // Check namespace read-job permissions + if aclObj, err := a.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/alloc_endpoint_test.go b/nomad/alloc_endpoint_test.go index f40ca972a..52334f62e 100644 --- a/nomad/alloc_endpoint_test.go +++ b/nomad/alloc_endpoint_test.go @@ -6,9 +6,11 @@ import ( "time" "github.com/hashicorp/net-rpc-msgpackrpc" + "github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/testutil" + "github.com/stretchr/testify/assert" ) func TestAllocEndpoint_List(t *testing.T) { @@ -77,6 +79,62 @@ func TestAllocEndpoint_List(t *testing.T) { } } +func TestAllocEndpoint_List_ACL(t *testing.T) { + t.Parallel() + s1, root := testACLServer(t, nil) + defer s1.Shutdown() + codec := rpcClient(t, s1) + testutil.WaitForLeader(t, s1.RPC) + assert := assert.New(t) + + // Create the alloc + alloc := mock.Alloc() + allocs := []*structs.Allocation{alloc} + summary := mock.JobSummary(alloc.JobID) + state := s1.fsm.State() + + assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary") + assert.Nil(state.UpsertAllocs(1000, allocs), "UpsertAllocs") + + stubAllocs := []*structs.AllocListStub{alloc.Stub()} + stubAllocs[0].CreateIndex = 1000 + stubAllocs[0].ModifyIndex = 1000 + + // Create the namespace policy and tokens + validToken := CreatePolicyAndToken(t, state, 1001, "test-valid", + NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) + invalidToken := CreatePolicyAndToken(t, state, 1003, "test-invalid", + NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) + + // Lookup the allocs without a token and expect failure + get := &structs.AllocListRequest{ + QueryOptions: structs.QueryOptions{ + Region: "global", + Namespace: structs.DefaultNamespace, + }, + } + var resp structs.AllocListResponse + assert.NotNil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") + + // Try with a valid token + get.SecretID = validToken.SecretID + assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") + assert.EqualValues(resp.Index, 1000, "resp.Index") + assert.Equal(stubAllocs, resp.Allocations, "Returned alloc list not equal") + + // Try with a invalid token + get.SecretID = invalidToken.SecretID + err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp) + assert.NotNil(err, "RPC") + assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) + + // Try with a root token + get.SecretID = root.SecretID + assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") + assert.EqualValues(resp.Index, 1000, "resp.Index") + assert.Equal(stubAllocs, resp.Allocations, "Returned alloc list not equal") +} + func TestAllocEndpoint_List_Blocking(t *testing.T) { t.Parallel() s1 := testServer(t, nil) diff --git a/website/source/api/allocations.html.md b/website/source/api/allocations.html.md index 6ad7dde8d..dfee5b223 100644 --- a/website/source/api/allocations.html.md +++ b/website/source/api/allocations.html.md @@ -22,9 +22,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