search: refactor OSS/ENT split for ACL checks (#13760)
The split between OSS/ENT in ACL checks for the Search RPC has a lot of repeated code that results in merge conflicts. Move most of the logic into the shared code so that we can call out to thin functions for ENT checks.
This commit is contained in:
parent
d73d0aac21
commit
0cf8a580c7
|
@ -609,6 +609,47 @@ func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.Search
|
|||
return s.srv.blockingRPC(&opts)
|
||||
}
|
||||
|
||||
// sufficientSearchPerms returns true if the provided ACL has access to any
|
||||
// capabilities required for prefix searching.
|
||||
//
|
||||
// Returns true if aclObj is nil or is for a management token
|
||||
func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Context) bool {
|
||||
if aclObj == nil || aclObj.IsManagement() {
|
||||
return true
|
||||
}
|
||||
|
||||
nodeRead := aclObj.AllowNodeRead()
|
||||
allowNS := aclObj.AllowNamespace(namespace)
|
||||
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
allowEnt := sufficientSearchPermsEnt(aclObj)
|
||||
|
||||
if !nodeRead && !allowNS && !allowEnt && !jobRead {
|
||||
return false
|
||||
}
|
||||
|
||||
// Reject requests that explicitly specify a disallowed context. This
|
||||
// should give the user better feedback than simply filtering out all
|
||||
// results and returning an empty list.
|
||||
switch context {
|
||||
case structs.Nodes:
|
||||
return nodeRead
|
||||
case structs.Namespaces:
|
||||
return allowNS
|
||||
case structs.Allocs, structs.Deployments, structs.Evals, structs.Jobs:
|
||||
return jobRead
|
||||
case structs.Volumes:
|
||||
return acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
|
||||
acl.NamespaceCapabilityCSIReadVolume,
|
||||
acl.NamespaceCapabilityListJobs,
|
||||
acl.NamespaceCapabilityReadJob)(aclObj, namespace)
|
||||
case structs.SecureVariables:
|
||||
// FIXME: Replace with real variables capability
|
||||
return aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// FuzzySearch is used to list fuzzy or prefix matches for a given text argument and Context.
|
||||
// If the Context is "all", all searchable contexts are searched. If ACLs are enabled,
|
||||
// results are limited to policies of the provided ACL token.
|
||||
|
@ -782,6 +823,65 @@ func sufficientFuzzySearchPerms(aclObj *acl.ACL, namespace string, context struc
|
|||
return sufficientSearchPerms(aclObj, namespace, context)
|
||||
}
|
||||
|
||||
// filteredSearchContexts returns the expanded set of contexts, filtered down
|
||||
// to the subset of contexts the aclObj is valid for.
|
||||
//
|
||||
// If aclObj is nil, no contexts are filtered out.
|
||||
func filteredSearchContexts(aclObj *acl.ACL, namespace string, context structs.Context) []structs.Context {
|
||||
desired := expandContext(context)
|
||||
|
||||
// If ACLs aren't enabled return all contexts
|
||||
if aclObj == nil {
|
||||
return desired
|
||||
}
|
||||
if aclObj.IsManagement() {
|
||||
return desired
|
||||
}
|
||||
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
|
||||
acl.NamespaceCapabilityCSIReadVolume,
|
||||
acl.NamespaceCapabilityListJobs,
|
||||
acl.NamespaceCapabilityReadJob)
|
||||
volRead := allowVolume(aclObj, namespace)
|
||||
policyRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityListScalingPolicies)
|
||||
|
||||
// Filter contexts down to those the ACL grants access to
|
||||
available := make([]structs.Context, 0, len(desired))
|
||||
for _, c := range desired {
|
||||
switch c {
|
||||
case structs.Allocs, structs.Jobs, structs.Evals, structs.Deployments:
|
||||
if jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.ScalingPolicies:
|
||||
if policyRead || jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Namespaces:
|
||||
if aclObj.AllowNamespace(namespace) {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.SecureVariables:
|
||||
if jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Nodes:
|
||||
if aclObj.AllowNodeRead() {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Volumes:
|
||||
if volRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
default:
|
||||
if ok := filteredSearchContextsEnt(aclObj, namespace, c); ok {
|
||||
available = append(available, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return available
|
||||
}
|
||||
|
||||
// filterFuzzySearchContexts returns every context asked for if the searched namespace
|
||||
// is the wildcard namespace, indicating we should bypass ACL checks otherwise
|
||||
// performed by filterSearchContexts. Instead we will rely on iterator filters to
|
||||
|
|
|
@ -48,110 +48,10 @@ func getEnterpriseFuzzyResourceIter(context structs.Context, _ *acl.ACL, _ strin
|
|||
return nil, fmt.Errorf("context must be one of %v or 'all' for all contexts; got %q", allContexts, context)
|
||||
}
|
||||
|
||||
// sufficientSearchPerms returns true if the provided ACL has access to each
|
||||
// capability required for prefix searching for the given context.
|
||||
//
|
||||
// Returns true if aclObj is nil.
|
||||
func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Context) bool {
|
||||
if aclObj == nil {
|
||||
return true
|
||||
}
|
||||
if aclObj.IsManagement() {
|
||||
return true
|
||||
}
|
||||
nodeRead := aclObj.AllowNodeRead()
|
||||
allowNS := aclObj.AllowNamespace(namespace)
|
||||
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
|
||||
acl.NamespaceCapabilityCSIReadVolume,
|
||||
acl.NamespaceCapabilityListJobs,
|
||||
acl.NamespaceCapabilityReadJob)
|
||||
volRead := allowVolume(aclObj, namespace)
|
||||
// FIXME: Replace with real variables capability
|
||||
allowVariables := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
|
||||
if !nodeRead && !jobRead && !volRead && !allowNS && !allowVariables {
|
||||
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 !allowNS && context == structs.Namespaces {
|
||||
return false
|
||||
}
|
||||
|
||||
if !jobRead {
|
||||
switch context {
|
||||
case structs.Allocs, structs.Deployments, structs.Evals, structs.Jobs:
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !allowVariables && context == structs.SecureVariables {
|
||||
return false
|
||||
}
|
||||
|
||||
if !volRead && context == structs.Volumes {
|
||||
return false
|
||||
}
|
||||
|
||||
func sufficientSearchPermsEnt(aclObj *acl.ACL) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// filteredSearchContexts returns the expanded set of contexts, filtered down
|
||||
// to the subset of contexts the aclObj is valid for.
|
||||
//
|
||||
// If aclObj is nil, no contexts are filtered out.
|
||||
func filteredSearchContexts(aclObj *acl.ACL, namespace string, context structs.Context) []structs.Context {
|
||||
desired := expandContext(context)
|
||||
|
||||
// If ACLs aren't enabled return all contexts
|
||||
if aclObj == nil {
|
||||
return desired
|
||||
}
|
||||
if aclObj.IsManagement() {
|
||||
return desired
|
||||
}
|
||||
jobRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob)
|
||||
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
|
||||
acl.NamespaceCapabilityCSIReadVolume,
|
||||
acl.NamespaceCapabilityListJobs,
|
||||
acl.NamespaceCapabilityReadJob)
|
||||
volRead := allowVolume(aclObj, namespace)
|
||||
policyRead := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityListScalingPolicies)
|
||||
|
||||
// Filter contexts down to those the ACL grants access to
|
||||
available := make([]structs.Context, 0, len(desired))
|
||||
for _, c := range desired {
|
||||
switch c {
|
||||
case structs.Allocs, structs.Jobs, structs.Evals, structs.Deployments:
|
||||
if jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.ScalingPolicies:
|
||||
if policyRead || jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Namespaces:
|
||||
if aclObj.AllowNamespace(namespace) {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.SecureVariables:
|
||||
if jobRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Nodes:
|
||||
if aclObj.AllowNodeRead() {
|
||||
available = append(available, c)
|
||||
}
|
||||
case structs.Volumes:
|
||||
if volRead {
|
||||
available = append(available, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return available
|
||||
func filteredSearchContextsEnt(aclObj *acl.ACL, namespace string, context structs.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue