e8bfef8148
ACL permissions for the search endpoints are done in three passes. The first (the `sufficientSearchPerms` method) is for performance and coarsely rejects requests based on the passed-in context parameter if the user has no permissions to any object in that context. The second (the `filteredSearchContexts` method) filters out contexts based on whether the user has permissions either to the requested namespace or again by context (to catch the "all" context). Finally, when iterating over the objects available, we do the usual filtering in the iterator. Internal testing found several bugs in this filtering: * CSI plugins can be searched by any authenticated user. * Variables can be searched if the user has `job:read` permissions to the variable's namespace instead of `variable:list`. * Variables cannot be searched by wildcard namespace. This is an information leak of the plugin names and variable paths, which we don't consider to be privileged information but intended to protect anyways. This changeset fixes these bugs by ensuring CSI plugins are filtered in the 1st and 2nd pass ACL filters, and changes variables to check `variable:list` in the 2nd pass filter unless the wildcard namespace is passed (at which point we'll fallback to filtering in the iterator). Fixes: CVE-2023-3300 Fixes: #17906
57 lines
1.8 KiB
Go
57 lines
1.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
//go:build !ent
|
|
// +build !ent
|
|
|
|
package nomad
|
|
|
|
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"
|
|
)
|
|
|
|
var (
|
|
// allContexts are the available contexts which are searched to find matches
|
|
// for a given prefix
|
|
allContexts = ossContexts
|
|
)
|
|
|
|
// contextToIndex returns the index name to lookup in the state store.
|
|
func contextToIndex(ctx structs.Context) string {
|
|
switch ctx {
|
|
// Handle cases where context name and state store table name do not match
|
|
case structs.Variables:
|
|
return state.TableVariables
|
|
default:
|
|
return string(ctx)
|
|
}
|
|
}
|
|
|
|
// getEnterpriseMatch is a no-op in oss since there are no enterprise objects.
|
|
func getEnterpriseMatch(match interface{}) (id string, ok bool) {
|
|
return "", false
|
|
}
|
|
|
|
// getEnterpriseResourceIter is used to retrieve an iterator over an enterprise
|
|
// only table.
|
|
func getEnterpriseResourceIter(context structs.Context, _ *acl.ACL, namespace, prefix string, ws memdb.WatchSet, state *state.StateStore) (memdb.ResultIterator, error) {
|
|
// If we have made it here then it is an error since we have exhausted all
|
|
// open source contexts.
|
|
return nil, fmt.Errorf("context must be one of %v or 'all' for all contexts; got %q", allContexts, context)
|
|
}
|
|
|
|
// getEnterpriseFuzzyResourceIter is used to retrieve an iterator over an enterprise
|
|
// only table.
|
|
func getEnterpriseFuzzyResourceIter(context structs.Context, _ *acl.ACL, _ string, _ memdb.WatchSet, _ *state.StateStore) (memdb.ResultIterator, error) {
|
|
return nil, fmt.Errorf("context must be one of %v or 'all' for all contexts; got %q", allContexts, context)
|
|
}
|
|
|
|
func filteredSearchContextsEnt(aclObj *acl.ACL, namespace string, context structs.Context) bool {
|
|
return true
|
|
}
|