[#36] eacl: add eACL table to ValidationUnit

Improve SDK usability a bit:
1. Replace bearer and storage with a single eACL table. This way
   caller can implement it's own behaviour for missing eACL.
2. Remove logging. SDK library shouldn't be dependent on a specific
   logger.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2021-10-05 15:24:04 +03:00 committed by Alex Vanin
parent 8c5a596ea2
commit f83ff628fb
4 changed files with 28 additions and 141 deletions

View file

@ -1,34 +0,0 @@
package eacl
import (
"go.uber.org/zap"
)
// Option represents Validator option.
type Option func(*cfg)
type cfg struct {
logger *zap.Logger
storage Source
}
func defaultCfg() *cfg {
return &cfg{
logger: zap.L(),
}
}
// WithLogger configures the Validator to use logger v.
func WithLogger(v *zap.Logger) Option {
return func(c *cfg) {
c.logger = v
}
}
// WithEACLSource configures the validator to use v as eACL source.
func WithEACLSource(v Source) Option {
return func(c *cfg) {
c.storage = v
}
}

View file

@ -1,25 +1,9 @@
package eacl package eacl
import ( import (
"errors"
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
) )
// Source is the interface that wraps
// basic methods of extended ACL table source.
type Source interface {
// GetEACL reads the table from the source by identifier.
// It returns any error encountered.
//
// GetEACL must return exactly one non-nil value.
//
// Must return pkg/core/container.ErrEACLNotFound if requested
// eACL table is not in source.
GetEACL(*cid.ID) (*Table, error)
}
// Header is an interface of string key-value header. // Header is an interface of string key-value header.
type Header interface { type Header interface {
Key() string Key() string
@ -49,13 +33,9 @@ type ValidationUnit struct {
key []byte key []byte
bearer *bearer.BearerToken table *Table
} }
// ErrEACLNotFound is returned by eACL storage implementations when
// requested eACL table is not in storage.
var ErrEACLNotFound = errors.New("extended ACL table is not set for this container")
// WithContainerID configures ValidationUnit to use v as request's container ID. // WithContainerID configures ValidationUnit to use v as request's container ID.
func (u *ValidationUnit) WithContainerID(v *cid.ID) *ValidationUnit { func (u *ValidationUnit) WithContainerID(v *cid.ID) *ValidationUnit {
if u != nil { if u != nil {
@ -102,9 +82,9 @@ func (u *ValidationUnit) WithSenderKey(v []byte) *ValidationUnit {
} }
// WithBearerToken configures ValidationUnit to use v as request's bearer token. // WithBearerToken configures ValidationUnit to use v as request's bearer token.
func (u *ValidationUnit) WithBearerToken(bearer *bearer.BearerToken) *ValidationUnit { func (u *ValidationUnit) WithEACLTable(table *Table) *ValidationUnit {
if u != nil { if u != nil {
u.bearer = bearer u.table = table
} }
return u return u

View file

@ -2,29 +2,17 @@ package eacl
import ( import (
"bytes" "bytes"
"errors"
"go.uber.org/zap"
) )
// Validator is a tool that calculates // Validator is a tool that calculates
// the action on a request according // the action on a request according
// to the extended ACL rule table. // to the extended ACL rule table.
type Validator struct { type Validator struct {
*cfg
} }
// NewValidator creates and initializes a new Validator using options. // NewValidator creates and initializes a new Validator using options.
func NewValidator(opts ...Option) *Validator { func NewValidator() *Validator {
cfg := defaultCfg() return &Validator{}
for i := range opts {
opts[i](cfg)
}
return &Validator{
cfg: cfg,
}
} }
// CalculateAction calculates action on the request according // CalculateAction calculates action on the request according
@ -33,40 +21,9 @@ func NewValidator(opts ...Option) *Validator {
// The action is calculated according to the application of // The action is calculated according to the application of
// eACL table of rules to the request. // eACL table of rules to the request.
// //
// If the eACL table is not available at the time of the call,
// ActionUnknown is returned.
//
// If no matching table entry is found, ActionAllow is returned. // If no matching table entry is found, ActionAllow is returned.
func (v *Validator) CalculateAction(unit *ValidationUnit) Action { func (v *Validator) CalculateAction(unit *ValidationUnit) Action {
var ( for _, record := range unit.table.Records() {
err error
table *Table
)
if unit.bearer != nil {
table = 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, ErrEACLNotFound) {
return ActionAllow
}
v.logger.Error("could not get eACL table",
zap.String("error", err.Error()),
)
return ActionUnknown
}
}
return tableAction(unit, table)
}
// tableAction calculates action on the request based on the eACL rules.
func tableAction(unit *ValidationUnit, table *Table) Action {
for _, record := range table.Records() {
// check type of operation // check type of operation
if record.Operation() != unit.op { if record.Operation() != unit.op {
continue continue

View file

@ -4,9 +4,7 @@ import (
"math/rand" "math/rand"
"testing" "testing"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
) )
func TestFilterMatch(t *testing.T) { func TestFilterMatch(t *testing.T) {
@ -26,8 +24,8 @@ func TestFilterMatch(t *testing.T) {
tb.AddRecord(newRecord(ActionAllow, OperationUnknown, tgt)) tb.AddRecord(newRecord(ActionAllow, OperationUnknown, tgt))
v := newValidator(t, tb) v := NewValidator()
vu := newValidationUnit(RoleOthers, nil) vu := newValidationUnit(RoleOthers, nil, tb)
hs := headers{} hs := headers{}
vu.hdrSrc = &hs vu.hdrSrc = &hs
@ -55,8 +53,8 @@ func TestFilterMatch(t *testing.T) {
tb.AddRecord(r) tb.AddRecord(r)
tb.AddRecord(newRecord(ActionAllow, OperationUnknown, tgt)) tb.AddRecord(newRecord(ActionAllow, OperationUnknown, tgt))
v := newValidator(t, tb) v := NewValidator()
vu := newValidationUnit(RoleOthers, nil) vu := newValidationUnit(RoleOthers, nil, tb)
hs := headers{} hs := headers{}
vu.hdrSrc = &hs vu.hdrSrc = &hs
@ -82,8 +80,8 @@ func TestFilterMatch(t *testing.T) {
tb.AddRecord(newRecord(ActionDeny, OperationUnknown, tgt)) tb.AddRecord(newRecord(ActionDeny, OperationUnknown, tgt))
v := newValidator(t, tb) v := NewValidator()
vu := newValidationUnit(RoleOthers, nil) vu := newValidationUnit(RoleOthers, nil, tb)
hs := headers{} hs := headers{}
vu.hdrSrc = &hs vu.hdrSrc = &hs
@ -104,8 +102,8 @@ func TestFilterMatch(t *testing.T) {
tb.AddRecord(r) tb.AddRecord(r)
tb.AddRecord(newRecord(ActionDeny, OperationUnknown, tgt)) tb.AddRecord(newRecord(ActionDeny, OperationUnknown, tgt))
v := newValidator(t, tb) v := NewValidator()
vu := newValidationUnit(RoleOthers, nil) vu := newValidationUnit(RoleOthers, nil, tb)
hs := headers{} hs := headers{}
vu.hdrSrc = &hs vu.hdrSrc = &hs
@ -125,8 +123,8 @@ func TestOperationMatch(t *testing.T) {
tb.AddRecord(newRecord(ActionDeny, OperationPut, tgt)) tb.AddRecord(newRecord(ActionDeny, OperationPut, tgt))
tb.AddRecord(newRecord(ActionAllow, OperationGet, tgt)) tb.AddRecord(newRecord(ActionAllow, OperationGet, tgt))
v := newValidator(t, tb) v := NewValidator()
vu := newValidationUnit(RoleOthers, nil) vu := newValidationUnit(RoleOthers, nil, tb)
vu.op = OperationPut vu.op = OperationPut
require.Equal(t, ActionDeny, v.CalculateAction(vu)) require.Equal(t, ActionDeny, v.CalculateAction(vu))
@ -140,8 +138,8 @@ func TestOperationMatch(t *testing.T) {
tb.AddRecord(newRecord(ActionDeny, OperationUnknown, tgt)) tb.AddRecord(newRecord(ActionDeny, OperationUnknown, tgt))
tb.AddRecord(newRecord(ActionAllow, OperationGet, tgt)) tb.AddRecord(newRecord(ActionAllow, OperationGet, tgt))
v := newValidator(t, tb) v := NewValidator()
vu := newValidationUnit(RoleOthers, nil) vu := newValidationUnit(RoleOthers, nil, tb)
// TODO discuss if both next tests should result in DENY // TODO discuss if both next tests should result in DENY
vu.op = OperationPut vu.op = OperationPut
@ -165,19 +163,19 @@ func TestTargetMatches(t *testing.T) {
r := NewRecord() r := NewRecord()
r.SetTargets(tgt1, tgt2) r.SetTargets(tgt1, tgt2)
u := newValidationUnit(RoleUser, pubs[0]) u := newValidationUnit(RoleUser, pubs[0], nil)
require.True(t, targetMatches(u, r)) require.True(t, targetMatches(u, r))
u = newValidationUnit(RoleUser, pubs[2]) u = newValidationUnit(RoleUser, pubs[2], nil)
require.False(t, targetMatches(u, r)) require.False(t, targetMatches(u, r))
u = newValidationUnit(RoleUnknown, pubs[1]) u = newValidationUnit(RoleUnknown, pubs[1], nil)
require.True(t, targetMatches(u, r)) require.True(t, targetMatches(u, r))
u = newValidationUnit(RoleOthers, pubs[2]) u = newValidationUnit(RoleOthers, pubs[2], nil)
require.True(t, targetMatches(u, r)) require.True(t, targetMatches(u, r))
u = newValidationUnit(RoleSystem, pubs[2]) u = newValidationUnit(RoleSystem, pubs[2], nil)
require.False(t, targetMatches(u, r)) require.False(t, targetMatches(u, r))
} }
@ -234,23 +232,9 @@ func newRecord(a Action, op Operation, tgt ...*Target) *Record {
return r return r
} }
type dummySource struct { func newValidationUnit(role Role, key []byte, table *Table) *ValidationUnit {
tb *Table return new(ValidationUnit).
} WithRole(role).
WithSenderKey(key).
func (d dummySource) GetEACL(*cid.ID) (*Table, error) { WithEACLTable(table)
return d.tb, nil
}
func newValidator(t *testing.T, tb *Table) *Validator {
return NewValidator(
WithLogger(zaptest.NewLogger(t)),
WithEACLSource(dummySource{tb}))
}
func newValidationUnit(role Role, key []byte) *ValidationUnit {
return &ValidationUnit{
role: role,
key: key,
}
} }