updated Allocation.List to properly handle ACL checking for namespace=*

This commit is contained in:
Chris Baker 2020-11-05 17:26:33 +00:00
parent 6743803e5c
commit be32fb7d3c
3 changed files with 71 additions and 35 deletions

View File

@ -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)]

View File

@ -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 {
@ -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,
@ -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())
@ -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 {

View File

@ -3329,35 +3329,17 @@ func allocNamespaceFilter(namespace string) func(interface{}) bool {
}
// AllocsByIDPrefix is used to lookup allocs by prefix
func (s *StateStore) AllocsByIDPrefixInNSes(ws memdb.WatchSet, namespaces map[string]bool, prefix string) (memdb.ResultIterator, error) {
func (s *StateStore) AllocsByIDPrefixAllNSs(ws memdb.WatchSet, prefix string) (memdb.ResultIterator, error) {
txn := s.db.ReadTxn()
var iter memdb.ResultIterator
var err error
if prefix != "" {
iter, err = txn.Get("allocs", "id_prefix", prefix)
} else {
iter, err = txn.Get("allocs", "id")
}
iter, err := txn.Get("allocs", "id_prefix", prefix)
if err != nil {
return nil, fmt.Errorf("alloc lookup failed: %v", err)
}
ws.Add(iter.WatchCh())
// Wrap the iterator in a filter
nsesFilter := func(raw interface{}) bool {
alloc, ok := raw.(*structs.Allocation)
if !ok {
return true
}
return namespaces[alloc.Namespace]
}
wrap := memdb.NewFilterIterator(iter, nsesFilter)
return wrap, nil
return iter, nil
}
// AllocsByNode returns all the allocations by node