[#1425] services/tree: Remove eACL processing

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
This commit is contained in:
Evgenii Stratonikov 2024-10-09 10:50:30 +03:00
parent 94302235d0
commit 02bb7159a5
Signed by: fyrchik
SSH key fingerprint: SHA256:m/TTwCzjnRkXgnzEx9X92ccxy1CcVeinOgDb3NPWWmg
4 changed files with 100 additions and 198 deletions

View file

@ -54,7 +54,6 @@ func initTreeService(c *cfg) {
cli: c.shared.cnrClient, cli: c.shared.cnrClient,
}), }),
tree.WithFrostfsidSubjectProvider(c.shared.frostfsidClient), tree.WithFrostfsidSubjectProvider(c.shared.frostfsidClient),
tree.WithEACLSource(c.cfgObject.eaclSource),
tree.WithNetmapSource(c.netMapSource), tree.WithNetmapSource(c.netMapSource),
tree.WithPrivateKey(&c.key.PrivateKey), tree.WithPrivateKey(&c.key.PrivateKey),
tree.WithLogger(c.log), tree.WithLogger(c.log),

View file

@ -33,7 +33,6 @@ type cfg struct {
nmSource netmap.Source nmSource netmap.Source
cnrSource ContainerSource cnrSource ContainerSource
frostfsidSubjectProvider frostfsidcore.SubjectProvider frostfsidSubjectProvider frostfsidcore.SubjectProvider
eaclSource container.EACLSource
forest pilorama.Forest forest pilorama.Forest
// replication-related parameters // replication-related parameters
replicatorChannelCapacity int replicatorChannelCapacity int
@ -65,14 +64,6 @@ func WithFrostfsidSubjectProvider(provider frostfsidcore.SubjectProvider) Option
} }
} }
// WithEACLSource sets a eACL table source for a tree service.
// This option is required.
func WithEACLSource(src container.EACLSource) Option {
return func(c *cfg) {
c.eaclSource = src
}
}
// WithNetmapSource sets a netmap source for a tree service. // WithNetmapSource sets a netmap source for a tree service.
// This option is required. // This option is required.
func WithNetmapSource(src netmap.Source) Option { func WithNetmapSource(src netmap.Source) Option {

View file

@ -9,10 +9,8 @@ import (
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
@ -20,7 +18,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"go.uber.org/zap"
) )
type message interface { type message interface {
@ -30,16 +27,11 @@ type message interface {
SetSignature(*Signature) SetSignature(*Signature)
} }
func basicACLErr(op acl.Op) error {
return fmt.Errorf("access to operation %s is denied by basic ACL check", op)
}
func eACLErr(op eacl.Operation, err error) error { func eACLErr(op eacl.Operation, err error) error {
return fmt.Errorf("access to operation %s is denied by extended ACL check: %w", op, err) return fmt.Errorf("access to operation %s is denied by extended ACL check: %w", op, err)
} }
var ( var (
errBearerWrongOwner = errors.New("bearer token must be signed by the container owner")
errBearerWrongContainer = errors.New("bearer token is created for another container") errBearerWrongContainer = errors.New("bearer token is created for another container")
errBearerSignature = errors.New("invalid bearer token signature") errBearerSignature = errors.New("invalid bearer token signature")
) )
@ -77,56 +69,7 @@ func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID,
return fmt.Errorf("can't get request role: %w", err) return fmt.Errorf("can't get request role: %w", err)
} }
basicACL := cnr.Value.BasicACL() return s.checkAPE(ctx, bt, cnr, cid, op, role, pubKey)
// Basic ACL mask can be unset, if a container operations are performed
// with strict APE checks only.
//
// FIXME(@aarifullin): tree service temporiraly performs APE checks on
// object verbs, because tree verbs have not been introduced yet.
if basicACL == 0x0 {
return s.checkAPE(ctx, bt, cnr, cid, op, role, pubKey)
}
if !basicACL.IsOpAllowed(op, role) {
return basicACLErr(op)
}
if !basicACL.Extendable() {
return nil
}
var useBearer bool
if len(rawBearer) != 0 {
if !basicACL.AllowedBearerRules(op) {
s.log.Debug(logs.TreeBearerPresentedButNotAllowedByACL,
zap.String("cid", cid.EncodeToString()),
zap.Stringer("op", op),
)
} else {
useBearer = true
}
}
var tb eacl.Table
signer := req.GetSignature().GetKey()
if useBearer && !bt.Impersonate() {
if !bearer.ResolveIssuer(*bt).Equals(cnr.Value.Owner()) {
return eACLErr(eaclOp, errBearerWrongOwner)
}
tb = bt.EACLTable()
} else {
tbCore, err := s.eaclSource.GetEACL(cid)
if err != nil {
return handleGetEACLError(err)
}
tb = *tbCore.Value
if useBearer && bt.Impersonate() {
signer = bt.SigningKeyBytes()
}
}
return checkEACL(tb, signer, eACLRole(role), eaclOp)
} }
// Returns true iff the operation is read-only and request was signed // Returns true iff the operation is read-only and request was signed
@ -168,14 +111,6 @@ func parseBearer(rawBearer []byte, cid cidSDK.ID, eaclOp eacl.Operation) (*beare
return bt, nil return bt, nil
} }
func handleGetEACLError(err error) error {
if client.IsErrEACLNotFound(err) {
return nil
}
return fmt.Errorf("get eACL table: %w", err)
}
func verifyMessage(m message) error { func verifyMessage(m message) error {
binBody, err := m.ReadSignedData(nil) binBody, err := m.ReadSignedData(nil)
if err != nil { if err != nil {
@ -260,73 +195,3 @@ func eACLOp(op acl.Op) eacl.Operation {
panic(fmt.Sprintf("unexpected tree service ACL operation: %s", op)) panic(fmt.Sprintf("unexpected tree service ACL operation: %s", op))
} }
} }
func eACLRole(role acl.Role) eacl.Role {
switch role {
case acl.RoleOwner:
return eacl.RoleUser
case acl.RoleOthers:
return eacl.RoleOthers
default:
panic(fmt.Sprintf("unexpected tree service ACL role: %s", role))
}
}
var (
errDENY = errors.New("DENY eACL rule")
errNoAllowRules = errors.New("not found allowing rules for the request")
)
// checkEACL searches for the eACL rules that could be applied to the request
// (a tuple of a signer key, his FrostFS role and a request operation).
// It does not filter the request by the filters of the eACL table since tree
// requests do not contain any "object" information that could be filtered and,
// therefore, filtering leads to unexpected results.
// The code was copied with the minor updates from the SDK repo:
// https://github.com/nspcc-dev/frostfs-sdk-go/blob/43a57d42dd50dc60465bfd3482f7f12bcfcf3411/eacl/validator.go#L28.
func checkEACL(tb eacl.Table, signer []byte, role eacl.Role, op eacl.Operation) error {
for _, record := range tb.Records() {
// check type of operation
if record.Operation() != op {
continue
}
// check target
if !targetMatches(record, role, signer) {
continue
}
switch a := record.Action(); a {
case eacl.ActionAllow:
return nil
case eacl.ActionDeny:
return eACLErr(op, errDENY)
default:
return eACLErr(op, fmt.Errorf("unexpected action: %s", a))
}
}
return eACLErr(op, errNoAllowRules)
}
func targetMatches(rec eacl.Record, role eacl.Role, signer []byte) bool {
for _, target := range rec.Targets() {
// check public key match
if pubs := target.BinaryKeys(); len(pubs) != 0 {
for _, key := range pubs {
if bytes.Equal(key, signer) {
return true
}
}
continue
}
// check target group match
if role == target.Role() {
return true
}
}
return false
}

View file

@ -4,22 +4,30 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"errors" "errors"
"testing" "testing"
aclV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" aclV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
checkercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/common/ape"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -27,6 +35,34 @@ type dummyNetmapSource struct {
netmap.Source netmap.Source
} }
type dummySubjectProvider struct {
subjects map[util.Uint160]client.SubjectExtended
}
func (s dummySubjectProvider) GetSubject(addr util.Uint160) (*client.Subject, error) {
res := s.subjects[addr]
return &client.Subject{
PrimaryKey: res.PrimaryKey,
AdditionalKeys: res.AdditionalKeys,
Namespace: res.Namespace,
Name: res.Name,
KV: res.KV,
}, nil
}
func (s dummySubjectProvider) GetSubjectExtended(addr util.Uint160) (*client.SubjectExtended, error) {
res := s.subjects[addr]
return &res, nil
}
type dummyEpochSource struct {
epoch uint64
}
func (s dummyEpochSource) CurrentEpoch() uint64 {
return s.epoch
}
type dummyContainerSource map[string]*containercore.Container type dummyContainerSource map[string]*containercore.Container
func (s dummyContainerSource) List() ([]cid.ID, error) { func (s dummyContainerSource) List() ([]cid.ID, error) {
@ -57,16 +93,6 @@ func (s dummyContainerSource) DeletionInfo(id cid.ID) (*containercore.DelInfo, e
return &containercore.DelInfo{}, nil return &containercore.DelInfo{}, nil
} }
type dummyEACLSource map[string]*containercore.EACL
func (s dummyEACLSource) GetEACL(id cid.ID) (*containercore.EACL, error) {
cntEACL, ok := s[id.String()]
if !ok {
return nil, errors.New("container not found")
}
return cntEACL, nil
}
func testContainer(owner user.ID) container.Container { func testContainer(owner user.ID) container.Container {
var r netmapSDK.ReplicaDescriptor var r netmapSDK.ReplicaDescriptor
r.SetNumberOfObjects(1) r.SetNumberOfObjects(1)
@ -81,6 +107,8 @@ func testContainer(owner user.ID) container.Container {
return cnt return cnt
} }
const currentEpoch = 123
func TestMessageSign(t *testing.T) { func TestMessageSign(t *testing.T) {
privs := make([]*keys.PrivateKey, 4) privs := make([]*keys.PrivateKey, 4)
for i := range privs { for i := range privs {
@ -99,6 +127,15 @@ func TestMessageSign(t *testing.T) {
Value: testContainer(ownerID), Value: testContainer(ownerID),
} }
e := inmemory.NewInMemoryLocalOverrides()
e.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.Target{
Type: engine.Container,
Name: cid1.EncodeToString(),
}, testChain(privs[0].PublicKey(), privs[1].PublicKey()))
frostfsidProvider := dummySubjectProvider{
subjects: make(map[util.Uint160]client.SubjectExtended),
}
s := &Service{ s := &Service{
cfg: cfg{ cfg: cfg{
log: test.NewLogger(t), log: test.NewLogger(t),
@ -107,12 +144,10 @@ func TestMessageSign(t *testing.T) {
cnrSource: dummyContainerSource{ cnrSource: dummyContainerSource{
cid1.String(): cnr, cid1.String(): cnr,
}, },
eaclSource: dummyEACLSource{ frostfsidSubjectProvider: frostfsidProvider,
cid1.String(): &containercore.EACL{ state: dummyEpochSource{epoch: currentEpoch},
Value: testTable(cid1, privs[0].PublicKey(), privs[1].PublicKey()),
},
},
}, },
apeChecker: checkercore.New(e.LocalStorage(), e.MorphRuleChainStorage(), frostfsidProvider, dummyEpochSource{}),
} }
rawCID1 := make([]byte, sha256.Size) rawCID1 := make([]byte, sha256.Size)
@ -235,46 +270,58 @@ func TestMessageSign(t *testing.T) {
func testBearerToken(cid cid.ID, forPutGet, forGet *keys.PublicKey) bearer.Token { func testBearerToken(cid cid.ID, forPutGet, forGet *keys.PublicKey) bearer.Token {
var b bearer.Token var b bearer.Token
b.SetEACLTable(*testTable(cid, forPutGet, forGet)) b.SetExp(currentEpoch + 1)
b.SetAPEOverride(bearer.APEOverride{
Target: ape.ChainTarget{
TargetType: ape.TargetTypeContainer,
Name: cid.EncodeToString(),
},
Chains: []ape.Chain{{Raw: testChain(forPutGet, forGet).Bytes()}},
})
return b return b
} }
func testTable(cid cid.ID, forPutGet, forGet *keys.PublicKey) *eaclSDK.Table { func testChain(forPutGet, forGet *keys.PublicKey) *chain.Chain {
tgtGet := eaclSDK.NewTarget() ruleGet := chain.Rule{
tgtGet.SetRole(eaclSDK.RoleUnknown) Status: chain.Allow,
tgtGet.SetBinaryKeys([][]byte{forPutGet.Bytes(), forGet.Bytes()}) Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
Actions: chain.Actions{Names: []string{native.MethodGetObject}},
rGet := eaclSDK.NewRecord() Any: true,
rGet.SetAction(eaclSDK.ActionAllow) Condition: []chain.Condition{
rGet.SetOperation(eaclSDK.OperationGet) {
rGet.SetTargets(*tgtGet) Op: chain.CondStringEquals,
Kind: chain.KindRequest,
tgtPut := eaclSDK.NewTarget() Key: native.PropertyKeyActorPublicKey,
tgtPut.SetRole(eaclSDK.RoleUnknown) Value: hex.EncodeToString(forPutGet.Bytes()),
tgtPut.SetBinaryKeys([][]byte{forPutGet.Bytes()}) },
{
rPut := eaclSDK.NewRecord() Op: chain.CondStringEquals,
rPut.SetAction(eaclSDK.ActionAllow) Kind: chain.KindRequest,
rPut.SetOperation(eaclSDK.OperationPut) Key: native.PropertyKeyActorPublicKey,
rPut.SetTargets(*tgtPut) Value: hex.EncodeToString(forGet.Bytes()),
},
tb := eaclSDK.NewTable() },
tb.AddRecord(rGet) }
tb.AddRecord(rPut) rulePut := chain.Rule{
Status: chain.Allow,
tgt := eaclSDK.NewTarget() Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
tgt.SetRole(eaclSDK.RoleOthers) Actions: chain.Actions{Names: []string{native.MethodPutObject}},
Any: true,
for _, op := range []eaclSDK.Operation{eaclSDK.OperationGet, eaclSDK.OperationPut} { Condition: []chain.Condition{
r := eaclSDK.NewRecord() {
r.SetAction(eaclSDK.ActionDeny) Op: chain.CondStringEquals,
r.SetTargets(*tgt) Kind: chain.KindRequest,
r.SetOperation(op) Key: native.PropertyKeyActorPublicKey,
tb.AddRecord(r) Value: hex.EncodeToString(forPutGet.Bytes()),
},
},
} }
tb.SetCID(cid) return &chain.Chain{
Rules: []chain.Rule{
return tb ruleGet,
rulePut,
},
}
} }