Skip to content

Commit

Permalink
Merge pull request #9278 from hashicorp/b-9268-all-namespace-allocs-acl
Browse files Browse the repository at this point in the history
fix ACL bugs in listing allocs across all namespaces
  • Loading branch information
cgbaker authored Nov 5, 2020
2 parents 60838c9 + 4aeb890 commit b11d067
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 65 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ __BACKWARDS INCOMPATIBILITIES:__
BUG FIXES:

* core: Fixed a bug where blocking queries would not include the query's maximum wait time when calculating whether it was safe to retry. [[GH-8921](https://github.com/hashicorp/nomad/issues/8921)]
* core: Fixed a bug where ACL handling prevented cross-namespace allocation listing [[GH-9278](https://github.com/hashicorp/nomad/issues/9278)]
* config (Enterprise): Fixed default enterprise config merging. [[GH-9083](https://github.com/hashicorp/nomad/pull/9083)]
* client: Fixed an issue with the Java fingerprinter on macOS causing pop-up notifications when no JVM installed. [[GH-9225](https://github.com/hashicorp/nomad/pull/9225)]
* consul: Fixed a bug to correctly validate task when using script-checks in group-level services [[GH-8952](https://github.com/hashicorp/nomad/issues/8952)]
Expand Down
81 changes: 67 additions & 14 deletions nomad/alloc_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes
}
defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now())

if args.RequestNamespace() == structs.AllNamespacesSentinel {
return a.listAllNamespaces(args, reply)
}

// Check namespace read-job permissions
aclObj, err := a.srv.ResolveToken(args.AuthToken)
if err != nil {
Expand All @@ -37,10 +41,6 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes
return structs.ErrPermissionDenied
}

allow := func(ns string) bool {
return aclObj.AllowNsOp(ns, acl.NamespaceCapabilityListJobs)
}

// Setup the blocking query
opts := blockingOptions{
queryOpts: &args.QueryOptions,
Expand All @@ -51,16 +51,7 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes
var iter memdb.ResultIterator

prefix := args.QueryOptions.Prefix
if args.RequestNamespace() == structs.AllNamespacesSentinel {
allowedNSes, err := allowedNSes(aclObj, state, allow)
if err != nil {
return err
}
iter, err = state.AllocsByIDPrefixInNSes(ws, allowedNSes, prefix)
if err != nil {
return err
}
} else if prefix != "" {
if prefix != "" {
iter, err = state.AllocsByIDPrefix(ws, args.RequestNamespace(), prefix)
} else {
iter, err = state.AllocsByNamespace(ws, args.RequestNamespace())
Expand Down Expand Up @@ -94,6 +85,68 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes
return a.srv.blockingRPC(&opts)
}

// listAllNamespaces lists all allocations across all namespaces
func (a *Alloc) listAllNamespaces(args *structs.AllocListRequest, reply *structs.AllocListResponse) error {
// Check for read-job permissions
aclObj, err := a.srv.ResolveToken(args.AuthToken)
if err != nil {
return err
}
prefix := args.QueryOptions.Prefix
allow := func(ns string) bool {
return aclObj.AllowNsOp(ns, acl.NamespaceCapabilityReadJob)
}

// Setup the blocking query
opts := blockingOptions{
queryOpts: &args.QueryOptions,
queryMeta: &reply.QueryMeta,
run: func(ws memdb.WatchSet, state *state.StateStore) error {
// get list of accessible namespaces
allowedNSes, err := allowedNSes(aclObj, state, allow)
if err == structs.ErrPermissionDenied {
// return empty allocations if token isn't authorized for any
// namespace, matching other endpoints
reply.Allocations = []*structs.AllocListStub{}
} else if err != nil {
return err
} else {
var iter memdb.ResultIterator
var err error
if prefix != "" {
iter, err = state.AllocsByIDPrefixAllNSs(ws, prefix)
} else {
iter, err = state.Allocs(ws)
}
if err != nil {
return err
}

var allocs []*structs.AllocListStub
for raw := iter.Next(); raw != nil; raw = iter.Next() {
alloc := raw.(*structs.Allocation)
if allowedNSes != nil && !allowedNSes[alloc.Namespace] {
continue
}
allocs = append(allocs, alloc.Stub(args.Fields))
}
reply.Allocations = allocs
}

// Use the last index that affected the jobs table
index, err := state.Index("allocs")
if err != nil {
return err
}
reply.Index = index

// Set the query response
a.srv.setQueryMeta(&reply.QueryMeta)
return nil
}}
return a.srv.blockingRPC(&opts)
}

// GetAlloc is used to lookup a particular allocation
func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
reply *structs.SingleAllocResponse) error {
Expand Down
Loading

0 comments on commit b11d067

Please sign in to comment.