cf1ea983e5
Now morph library returns error if there is not eACL in sidechain storage. However in this case eACL check should be passed since it is the same as having empty eACL table. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
187 lines
4 KiB
Go
187 lines
4 KiB
Go
package eacl
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
"github.com/nspcc-dev/neofs-node/pkg/morph/client/container/wrapper"
|
|
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Validator is a tool that calculates
|
|
// the action on a request according
|
|
// to the extended ACL rule table.
|
|
type Validator struct {
|
|
*cfg
|
|
}
|
|
|
|
// Option represents Validator option.
|
|
type Option func(*cfg)
|
|
|
|
type cfg struct {
|
|
logger *logger.Logger
|
|
|
|
storage Storage
|
|
}
|
|
|
|
func defaultCfg() *cfg {
|
|
return &cfg{
|
|
logger: zap.L(),
|
|
}
|
|
}
|
|
|
|
// NewValidator creates and initializes a new Validator using options.
|
|
func NewValidator(opts ...Option) *Validator {
|
|
cfg := defaultCfg()
|
|
|
|
for i := range opts {
|
|
opts[i](cfg)
|
|
}
|
|
|
|
return &Validator{
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
// CalculateAction calculates action on the request according
|
|
// to its information represented in ValidationUnit.
|
|
//
|
|
// The action is calculated according to the application of
|
|
// eACL table of rules to the request.
|
|
//
|
|
// If the eACL table is not available at the time of the call,
|
|
// eacl.ActionUnknown is returned.
|
|
//
|
|
// If no matching table entry is found, ActionAllow is returned.
|
|
func (v *Validator) CalculateAction(unit *ValidationUnit) eacl.Action {
|
|
var (
|
|
err error
|
|
table *eacl.Table
|
|
)
|
|
|
|
if unit.bearer != nil {
|
|
table = eacl.NewTableFromV2(unit.bearer.GetBody().GetEACL())
|
|
} else {
|
|
// get eACL table by container ID
|
|
table, err = v.storage.GetEACL(unit.cid)
|
|
if err != nil {
|
|
if errors.Is(err, wrapper.ErrEACLNotFound) {
|
|
return eacl.ActionAllow
|
|
}
|
|
|
|
v.logger.Error("could not get eACL table",
|
|
zap.String("error", err.Error()),
|
|
)
|
|
|
|
return eacl.ActionUnknown
|
|
}
|
|
}
|
|
|
|
return tableAction(unit, table)
|
|
}
|
|
|
|
// calculates action on the request based on the eACL rules.
|
|
func tableAction(unit *ValidationUnit, table *eacl.Table) eacl.Action {
|
|
for _, record := range table.Records() {
|
|
// check type of operation
|
|
if record.Operation() != unit.op {
|
|
continue
|
|
}
|
|
|
|
// check target
|
|
if !targetMatches(unit, record) {
|
|
continue
|
|
}
|
|
|
|
// check headers
|
|
switch val := matchFilters(unit.hdrSrc, record.Filters()); {
|
|
case val < 0:
|
|
// headers of some type could not be composed => allow
|
|
return eacl.ActionAllow
|
|
case val == 0:
|
|
return record.Action()
|
|
}
|
|
}
|
|
|
|
return eacl.ActionAllow
|
|
}
|
|
|
|
// returns:
|
|
// - positive value if no matching header is found for at least one filter;
|
|
// - zero if at least one suitable header is found for all filters;
|
|
// - negative value if the headers of at least one filter cannot be obtained.
|
|
func matchFilters(hdrSrc TypedHeaderSource, filters []*eacl.Filter) int {
|
|
matched := 0
|
|
|
|
for _, filter := range filters {
|
|
headers, ok := hdrSrc.HeadersOfType(filter.From())
|
|
if !ok {
|
|
return -1
|
|
}
|
|
|
|
// get headers of filtering type
|
|
for _, header := range headers {
|
|
// prevent NPE
|
|
if header == nil {
|
|
continue
|
|
}
|
|
|
|
// check header name
|
|
if header.Key() != filter.Key() {
|
|
continue
|
|
}
|
|
|
|
// get match function
|
|
matchFn, ok := mMatchFns[filter.Matcher()]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// check match
|
|
if !matchFn(header, filter) {
|
|
continue
|
|
}
|
|
|
|
// increment match counter
|
|
matched++
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return len(filters) - matched
|
|
}
|
|
|
|
// returns true if one of ExtendedACLTarget has
|
|
// suitable target OR suitable public key.
|
|
func targetMatches(unit *ValidationUnit, record *eacl.Record) bool {
|
|
for _, target := range record.Targets() {
|
|
// check public key match
|
|
for _, key := range target.Keys() {
|
|
if bytes.Equal(crypto.MarshalPublicKey(&key), unit.key) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// check target group match
|
|
if unit.role == target.Role() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Maps match type to corresponding function.
|
|
var mMatchFns = map[eacl.Match]func(Header, *eacl.Filter) bool{
|
|
eacl.MatchStringEqual: func(header Header, filter *eacl.Filter) bool {
|
|
return header.Value() == filter.Value()
|
|
},
|
|
|
|
eacl.MatchStringNotEqual: func(header Header, filter *eacl.Filter) bool {
|
|
return header.Value() != filter.Value()
|
|
},
|
|
}
|