Skip to content

Commit

Permalink
node pool: add search support (#17385)
Browse files Browse the repository at this point in the history
  • Loading branch information
lgfa29 committed Jun 1, 2023
1 parent 2d059bb commit 9ee68fc
Show file tree
Hide file tree
Showing 5 changed files with 553 additions and 33 deletions.
29 changes: 29 additions & 0 deletions acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,35 @@ func (a *ACL) AllowNodePool(pool string) bool {
return !capabilities.Check(PolicyDeny)
}

// AllowNodePoolSearch returns true if any operation is allowed in at least one
// node pool.
//
// This is a very loose check and is expected that callers perform more precise
// verification later.
func (a *ACL) AllowNodePoolSearch() bool {
// Hot path if ACL is not enabled or token is management.
if a == nil || a.management {
return true
}

// Check for any non-deny capabilities.
iter := a.nodePools.Root().Iterator()
for _, capability, ok := iter.Next(); ok; _, capability, ok = iter.Next() {
if !capability.Check(NodePoolCapabilityDeny) {
return true
}
}

iter = a.wildcardNodePools.Root().Iterator()
for _, capability, ok := iter.Next(); ok; _, capability, ok = iter.Next() {
if !capability.Check(NodePoolCapabilityDeny) {
return true
}
}

return false
}

// AllowHostVolumeOperation checks if a given operation is allowed for a host volume
func (a *ACL) AllowHostVolumeOperation(hv string, op string) bool {
// Hot path management tokens
Expand Down
51 changes: 49 additions & 2 deletions nomad/search_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
structs.Allocs,
structs.Jobs,
structs.Nodes,
structs.NodePools,
structs.Evals,
structs.Deployments,
structs.Plugins,
Expand Down Expand Up @@ -75,6 +76,8 @@ func (s *Search) getPrefixMatches(iter memdb.ResultIterator, prefix string) ([]s
id = t.ID
case *structs.Node:
id = t.ID
case *structs.NodePool:
id = t.Name
case *structs.Deployment:
id = t.ID
case *structs.CSIPlugin:
Expand Down Expand Up @@ -216,6 +219,10 @@ func (s *Search) fuzzyMatchSingle(raw interface{}, text string) (structs.Context
name = t.Name
scope = []string{t.ID}
ctx = structs.Nodes
case *structs.NodePool:
name = t.Name
scope = []string{t.Name}
ctx = structs.NodePools
case *structs.Namespace:
name = t.Name
ctx = structs.Namespaces
Expand Down Expand Up @@ -381,6 +388,15 @@ func getResourceIter(context structs.Context, aclObj *acl.ACL, namespace, prefix
return store.AllocsByIDPrefix(ws, namespace, prefix, state.SortDefault)
case structs.Nodes:
return store.NodesByIDPrefix(ws, prefix)
case structs.NodePools:
iter, err := store.NodePoolsByNamePrefix(ws, prefix, state.SortDefault)
if err != nil {
return nil, err
}
if aclObj == nil || aclObj.IsManagement() {
return iter, nil
}
return memdb.NewFilterIterator(iter, nodePoolCapFilter(aclObj)), nil
case structs.Deployments:
return store.DeploymentsByIDPrefix(ws, namespace, prefix, state.SortDefault)
case structs.Plugins:
Expand Down Expand Up @@ -449,6 +465,17 @@ func getFuzzyResourceIterator(context structs.Context, aclObj *acl.ACL, namespac
}
return store.Nodes(ws)

case structs.NodePools:
iter, err := store.NodePools(ws, state.SortDefault)
if err != nil {
return nil, err
}

if aclObj == nil || aclObj.IsManagement() {
return iter, nil
}
return memdb.NewFilterIterator(iter, nodePoolCapFilter(aclObj)), nil

case structs.Plugins:
if wildcard(namespace) {
iter, err := store.CSIPlugins(ws)
Expand Down Expand Up @@ -507,6 +534,15 @@ func nsCapFilter(aclObj *acl.ACL) memdb.FilterFunc {
}
}

// nodePoolCapFilter produces a memdb.FilterFunc for removing node pools not
// accessible by aclObj during a table scan.
func nodePoolCapFilter(aclObj *acl.ACL) memdb.FilterFunc {
return func(v interface{}) bool {
pool := v.(*structs.NodePool)
return !aclObj.AllowNodePoolOperation(pool.Name, acl.NodePoolCapabilityRead)
}
}

// If the length of a prefix is odd, return a subset to the last even character
// This only applies to UUIDs, jobs are excluded
func roundUUIDDownIfOdd(prefix string, context structs.Context) string {
Expand Down Expand Up @@ -633,11 +669,12 @@ func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Co
}

nodeRead := aclObj.AllowNodeRead()
allowNodePool := aclObj.AllowNodePoolSearch()
allowNS := aclObj.AllowNamespace(namespace)
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
allowEnt := sufficientSearchPermsEnt(aclObj)

if !nodeRead && !allowNS && !allowEnt && !jobRead {
if !nodeRead && !allowNodePool && !allowNS && !allowEnt && !jobRead {
return false
}

Expand All @@ -647,6 +684,12 @@ func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Co
switch context {
case structs.Nodes:
return nodeRead
case structs.NodePools:
// The search term alone is not enough to determine if the token is
// allowed to access the given prefix since it may not match node pool
// label in the policy. Node pools will be filtered when iterating over
// the results.
return true
case structs.Namespaces:
return allowNS
case structs.Allocs, structs.Deployments, structs.Evals, structs.Jobs:
Expand All @@ -673,7 +716,7 @@ func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Co
//
// These types are available for fuzzy searching:
//
// Nodes, Namespaces, Jobs, Allocs, Plugins
// Nodes, Node Pools, Namespaces, Jobs, Allocs, Plugins
//
// Jobs are a special case that expand into multiple types, and whose return
// values include Scope which is a descending list of IDs of parent objects,
Expand Down Expand Up @@ -891,6 +934,10 @@ func filteredSearchContexts(aclObj *acl.ACL, namespace string, context structs.C
if aclObj.AllowNodeRead() {
available = append(available, c)
}
case structs.NodePools:
if aclObj.AllowNodePoolSearch() {
available = append(available, c)
}
case structs.Volumes:
if volRead {
available = append(available, c)
Expand Down
Loading

0 comments on commit 9ee68fc

Please sign in to comment.