mirror of
https://github.com/kemko/nomad.git
synced 2026-01-07 02:45:42 +03:00
Refactor permissions checks into funcs
funcs are in the _oss file to ease creating Enterprise versions which support Quotas and Namespaces.
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"strings"
|
||||
|
||||
memdb "github.com/hashicorp/go-memdb"
|
||||
"github.com/hashicorp/nomad/acl"
|
||||
"github.com/hashicorp/nomad/nomad/state"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
@@ -18,8 +17,13 @@ const (
|
||||
var (
|
||||
// ossContexts are the oss contexts which are searched to find matches
|
||||
// for a given prefix
|
||||
ossContexts = []structs.Context{structs.Allocs, structs.Jobs, structs.Nodes,
|
||||
structs.Evals, structs.Deployments}
|
||||
ossContexts = []structs.Context{
|
||||
structs.Allocs,
|
||||
structs.Jobs,
|
||||
structs.Nodes,
|
||||
structs.Evals,
|
||||
structs.Deployments,
|
||||
}
|
||||
)
|
||||
|
||||
// Search endpoint is used to look up matches for a given prefix and context
|
||||
@@ -114,28 +118,11 @@ func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.Search
|
||||
return err
|
||||
}
|
||||
|
||||
// Require either node:read or namespace:read-job
|
||||
nodeRead := true
|
||||
jobRead := true
|
||||
if aclObj != nil {
|
||||
nodeRead = aclObj.AllowNodeRead()
|
||||
jobRead = aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob)
|
||||
if !nodeRead && !jobRead {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
namespace := args.RequestNamespace()
|
||||
|
||||
// Reject requests that explicitly specify a disallowed context. This
|
||||
// should give the user better feedback then simply filtering out all
|
||||
// results and returning an empty list.
|
||||
if !nodeRead && args.Context == structs.Nodes {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
if !jobRead {
|
||||
switch args.Context {
|
||||
case structs.Allocs, structs.Deployments, structs.Evals, structs.Jobs:
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
// Require either node:read or namespace:read-job
|
||||
if !anySearchPerms(aclObj, namespace, args.Context) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
reply.Matches = make(map[structs.Context][]string)
|
||||
@@ -149,27 +136,10 @@ func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.Search
|
||||
|
||||
iters := make(map[structs.Context]memdb.ResultIterator)
|
||||
|
||||
contexts := allContexts
|
||||
if args.Context != structs.All {
|
||||
contexts = []structs.Context{args.Context}
|
||||
}
|
||||
contexts := searchContexts(aclObj, namespace, args.Context)
|
||||
|
||||
for _, ctx := range contexts {
|
||||
if ctx == structs.Nodes && !nodeRead {
|
||||
// Not allowed to search nodes
|
||||
continue
|
||||
}
|
||||
|
||||
if !jobRead {
|
||||
switch ctx {
|
||||
case structs.Allocs, structs.Deployments, structs.Evals, structs.Jobs:
|
||||
// Not allowed to read jobs
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
iter, err := getResourceIter(ctx, args.RequestNamespace(), roundUUIDDownIfOdd(args.Prefix, args.Context), ws, state)
|
||||
|
||||
iter, err := getResourceIter(ctx, namespace, roundUUIDDownIfOdd(args.Prefix, args.Context), ws, state)
|
||||
if err != nil {
|
||||
e := err.Error()
|
||||
switch {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
memdb "github.com/hashicorp/go-memdb"
|
||||
"github.com/hashicorp/nomad/acl"
|
||||
"github.com/hashicorp/nomad/nomad/state"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
@@ -28,3 +29,69 @@ func getEnterpriseResourceIter(context structs.Context, namespace, prefix string
|
||||
// open source contexts.
|
||||
return nil, fmt.Errorf("context must be one of %v or 'all' for all contexts; got %q", allContexts, context)
|
||||
}
|
||||
|
||||
// anySearchPerms returns true if the provided ACL has access to any
|
||||
// capabilities required for prefix searching. Returns true if aclObj is nil.
|
||||
func anySearchPerms(aclObj *acl.ACL, namespace string, context structs.Context) bool {
|
||||
if aclObj == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
nodeRead := aclObj.AllowNodeRead()
|
||||
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
if !nodeRead && !jobRead {
|
||||
return false
|
||||
}
|
||||
|
||||
// Reject requests that explicitly specify a disallowed context. This
|
||||
// should give the user better feedback then simply filtering out all
|
||||
// results and returning an empty list.
|
||||
if !nodeRead && context == structs.Nodes {
|
||||
return false
|
||||
}
|
||||
if !jobRead {
|
||||
switch context {
|
||||
case structs.Allocs, structs.Deployments, structs.Evals, structs.Jobs:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// searchContexts returns the contexts the aclObj is valid for. If aclObj is
|
||||
// nil all contexts are returned.
|
||||
func searchContexts(aclObj *acl.ACL, namespace string, context structs.Context) []structs.Context {
|
||||
var all []structs.Context
|
||||
|
||||
switch context {
|
||||
case structs.All:
|
||||
all = make([]structs.Context, len(allContexts))
|
||||
copy(all, allContexts)
|
||||
default:
|
||||
all = []structs.Context{context}
|
||||
}
|
||||
|
||||
// If ACLs aren't enabled return all contexts
|
||||
if aclObj == nil {
|
||||
return all
|
||||
}
|
||||
|
||||
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
|
||||
// Filter contexts down to those the ACL grants access to
|
||||
available := make([]structs.Context, 0, len(all))
|
||||
for _, c := range all {
|
||||
switch c {
|
||||
case structs.Allocs, structs.Jobs, structs.Evals, structs.Deployments:
|
||||
if jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Nodes:
|
||||
if aclObj.AllowNodeRead() {
|
||||
available = append(available, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return available
|
||||
}
|
||||
|
||||
@@ -136,10 +136,12 @@ func TestSearch_PrefixSearch_ACL(t *testing.T) {
|
||||
req.SecretID = validToken.SecretID
|
||||
var resp structs.SearchResponse
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp))
|
||||
assert.Equal(uint64(1001), resp.Index)
|
||||
assert.Len(resp.Matches[structs.Jobs], 1)
|
||||
assert.Equal(job.ID, resp.Matches[structs.Jobs][0])
|
||||
|
||||
// Index of job - not node - because node context is filtered out
|
||||
assert.Equal(uint64(1000), resp.Index)
|
||||
|
||||
// Nodes filtered out since token only has access to namespace:read-job
|
||||
assert.Len(resp.Matches[structs.Nodes], 0)
|
||||
}
|
||||
@@ -153,10 +155,10 @@ func TestSearch_PrefixSearch_ACL(t *testing.T) {
|
||||
req.SecretID = validToken.SecretID
|
||||
var resp structs.SearchResponse
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp))
|
||||
assert.Equal(uint64(1001), resp.Index)
|
||||
assert.Len(resp.Matches[structs.Jobs], 1)
|
||||
assert.Equal(job.ID, resp.Matches[structs.Jobs][0])
|
||||
assert.Len(resp.Matches[structs.Nodes], 1)
|
||||
assert.Equal(uint64(1001), resp.Index)
|
||||
}
|
||||
|
||||
// Try with a management token
|
||||
|
||||
Reference in New Issue
Block a user