open-consul/agent/consul/filter.go
Freddy e96c0e1dad
Fixup authz for data imported from peers (#15347)
There are a few changes that needed to be made to to handle authorizing
reads for imported data:

- If the data was imported from a peer we should not attempt to read the
  data using the traditional authz rules. This is because the name of
  services/nodes in a peer cluster are not equivalent to those of the
  importing cluster.

- If the data was imported from a peer we need to check whether the
  token corresponds to a service, meaning that it has service:write
  permissions, or to a local read only token that can read all
  nodes/services in a namespace.

This required changes at the policyAuthorizer level, since that is the
only view available to OSS Consul, and at the enterprise
partition/namespace level.
2022-11-14 11:36:27 -07:00

116 lines
2.9 KiB
Go

package consul
import (
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
)
type dirEntFilter struct {
authorizer acl.Authorizer
ent structs.DirEntries
}
func (d *dirEntFilter) Len() int {
return len(d.ent)
}
func (d *dirEntFilter) Filter(i int) bool {
var entCtx acl.AuthorizerContext
d.ent[i].FillAuthzContext(&entCtx)
return d.authorizer.KeyRead(d.ent[i].Key, &entCtx) != acl.Allow
}
func (d *dirEntFilter) Move(dst, src, span int) {
copy(d.ent[dst:dst+span], d.ent[src:src+span])
}
// FilterDirEnt is used to filter a list of directory entries
// by applying an ACL policy
func FilterDirEnt(authorizer acl.Authorizer, ent structs.DirEntries) structs.DirEntries {
df := dirEntFilter{authorizer: authorizer, ent: ent}
return ent[:FilterEntries(&df)]
}
type txnResultsFilter struct {
authorizer acl.Authorizer
results structs.TxnResults
}
func (t *txnResultsFilter) Len() int {
return len(t.results)
}
func (t *txnResultsFilter) Filter(i int) bool {
result := t.results[i]
var authzContext acl.AuthorizerContext
switch {
case result.KV != nil:
result.KV.EnterpriseMeta.FillAuthzContext(&authzContext)
return t.authorizer.KeyRead(result.KV.Key, &authzContext) != acl.Allow
case result.Node != nil:
(*structs.Node)(result.Node).FillAuthzContext(&authzContext)
return t.authorizer.NodeRead(result.Node.Node, &authzContext) != acl.Allow
case result.Service != nil:
(*structs.NodeService)(result.Service).FillAuthzContext(&authzContext)
return t.authorizer.ServiceRead(result.Service.Service, &authzContext) != acl.Allow
case result.Check != nil:
(*structs.HealthCheck)(result.Check).FillAuthzContext(&authzContext)
if result.Check.ServiceName != "" {
return t.authorizer.ServiceRead(result.Check.ServiceName, &authzContext) != acl.Allow
}
return t.authorizer.NodeRead(result.Check.Node, &authzContext) != acl.Allow
}
return false
}
func (t *txnResultsFilter) Move(dst, src, span int) {
copy(t.results[dst:dst+span], t.results[src:src+span])
}
// FilterTxnResults is used to filter a list of transaction results by
// applying an ACL policy.
func FilterTxnResults(authorizer acl.Authorizer, results structs.TxnResults) structs.TxnResults {
rf := txnResultsFilter{authorizer: authorizer, results: results}
return results[:FilterEntries(&rf)]
}
// Filter interface is used with FilterEntries to do an
// in-place filter of a slice.
type Filter interface {
Len() int
Filter(int) bool
Move(dst, src, span int)
}
// FilterEntries is used to do an inplace filter of
// a slice. This has cost proportional to the list length.
func FilterEntries(f Filter) int {
// Compact the list
dst := 0
src := 0
n := f.Len()
for dst < n {
for src < n && f.Filter(src) {
src++
}
if src == n {
break
}
end := src + 1
for end < n && !f.Filter(end) {
end++
}
span := end - src
if span > 0 {
f.Move(dst, src, span)
dst += span
src += span
}
}
// Return the size of the slice
return dst
}