Set group id to APE requests for tree, container and object services #1117

Merged
fyrchik merged 4 commits from aarifullin/frostfs-node:feat/ape_fill_group_id into master 2024-05-07 10:01:24 +00:00
17 changed files with 529 additions and 77 deletions

View file

@ -32,6 +32,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/chainbase"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
netmapCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
@ -46,7 +47,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
containerClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
frostfsidClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
netmap2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap"
@ -421,7 +421,7 @@ type shared struct {
cnrClient *containerClient.Client
frostfsidClient *frostfsidClient.Client
frostfsidClient frostfsidcore.SubjectProvider
respSvc *response.Service

View file

@ -28,6 +28,9 @@ const (
// APEChainCacheSizeDefault is a default value of APE chain cache.
APEChainCacheSizeDefault = 10_000
// FrostfsIDCacheSizeDefault is a default value of APE chain cache.
FrostfsIDCacheSizeDefault = 10_000
)
var errNoMorphEndpoints = errors.New("no morph chain RPC endpoints, see `morph.rpc_endpoint` section")
@ -114,3 +117,15 @@ func APEChainCacheSize(c *config.Config) uint32 {
}
return config.Uint32Safe(c.Sub(subsection), "ape_chain_cache_size")
}
// FrostfsIDCacheSize returns the value of "frostfsid_cache_size" config parameter
// from "morph" section.
//
// Returns 0 if the value is not positive integer.
// Returns FrostfsIDCacheSizeDefault if the value is missing.
func FrostfsIDCacheSize(c *config.Config) uint32 {
if c.Sub(subsection).Value("frostfsid_cache_size") == nil {
return FrostfsIDCacheSizeDefault
}
return config.Uint32Safe(c.Sub(subsection), "frostfsid_cache_size")
}

View file

@ -6,8 +6,10 @@ import (
"net"
containerGRPC "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container/grpc"
morphconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/morph"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
containerCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
@ -33,9 +35,16 @@ func initContainerService(_ context.Context, c *cfg) {
cnrRdr, cnrWrt := configureEACLAndContainerSources(c, wrap, cnrSrc)
frostFSIDClient, err := frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0)
var frostfsIDSubjectProvider frostfsidcore.SubjectProvider
frostfsIDSubjectProvider, err = frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0)
fatalOnErr(err)
c.shared.frostfsidClient = frostFSIDClient
cacheSize := morphconfig.FrostfsIDCacheSize(c.appCfg)
if cacheSize > 0 {
frostfsIDSubjectProvider = newMorphFrostfsIDCache(frostfsIDSubjectProvider, int(cacheSize), c.cfgMorph.cacheTTL)
}
c.shared.frostfsidClient = frostfsIDSubjectProvider
server := containerTransportGRPC.New(
containerService.NewSignService(

View file

@ -0,0 +1,71 @@
package main
import (
"time"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type morphFrostfsIDCache struct {
subjProvider frostfsidcore.SubjectProvider
subjCache *expirable.LRU[util.Uint160, *client.Subject]
subjExtCache *expirable.LRU[util.Uint160, *client.SubjectExtended]
}
func newMorphFrostfsIDCache(subjProvider frostfsidcore.SubjectProvider, size int, ttl time.Duration) frostfsidcore.SubjectProvider {
return &morphFrostfsIDCache{
subjProvider: subjProvider,
subjCache: expirable.NewLRU(size, func(util.Uint160, *client.Subject) {}, ttl),
subjExtCache: expirable.NewLRU(size, func(util.Uint160, *client.SubjectExtended) {}, ttl),
}
}
func (m *morphFrostfsIDCache) GetSubject(addr util.Uint160) (*client.Subject, error) {
result, found := m.subjCache.Get(addr)

Should we also check m.subjExtCache here? Or update m.subjCache in GetSubjectExtended as well

Should we also check `m.subjExtCache` here? Or update `m.subjCache` in `GetSubjectExtended` as well

Or update m.subjCache in GetSubjectExtended as well

I think you're right

> Or update m.subjCache in GetSubjectExtended as well I think you're right

For now subjCache is also refreshed

For now `subjCache` is also refreshed
if found {
return result, nil
}
result, err := m.subjProvider.GetSubject(addr)
if err != nil {
return nil, err
}
m.subjCache.Add(addr, result)
return result, nil
}
func (m *morphFrostfsIDCache) GetSubjectExtended(addr util.Uint160) (*client.SubjectExtended, error) {
subjExt, found := m.subjExtCache.Get(addr)
if found {
return subjExt, nil
}
var err error
subjExt, err = m.subjProvider.GetSubjectExtended(addr)
if err != nil {
return nil, err
}
m.subjExtCache.Add(addr, subjExt)
m.subjCache.Add(addr, subjectFromSubjectExtended(subjExt))
return subjExt, nil
}
func subjectFromSubjectExtended(subjExt *client.SubjectExtended) *client.Subject {
return &client.Subject{
PrimaryKey: subjExt.PrimaryKey,
AdditionalKeys: subjExt.AdditionalKeys,
Namespace: subjExt.Name,
Name: subjExt.Name,
KV: subjExt.KV,
}
}

View file

@ -0,0 +1,36 @@
package request
import (
"fmt"
"strconv"
"strings"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
// FormFrostfsIDRequestProperties forms frostfsid specific request properties like user-claim tags and group ID.
func FormFrostfsIDRequestProperties(frostFSIDClient frostfsidcore.SubjectProvider, pk *keys.PublicKey) (map[string]string, error) {
reqProps := make(map[string]string)
subj, err := frostFSIDClient.GetSubjectExtended(pk.GetScriptHash())
if err != nil {
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
}
for k, v := range subj.KV {
propertyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)

typo in propertyKey

typo in `propertyKey`

Fixed!

Fixed!
reqProps[propertyKey] = v
}
groups := make([]string, len(subj.Groups))
for i, group := range subj.Groups {
groups[i] = strconv.FormatInt(group.ID, 10)
}
reqProps[commonschema.PropertyKeyFrostFSIDGroupID] = apechain.FormCondSliceContainsValue(groups)
return reqProps, nil
}

View file

@ -1,4 +1,4 @@
package ape
package request
import (
aperesource "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"

View file

@ -0,0 +1,16 @@
package frostfsid
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const (
SubjectNotFoundErrorMessage = "subject not found"
)
// SubjectProvider interface provides methods to get subject from FrostfsID contract.
type SubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
GetSubjectExtended(util.Uint160) (*client.SubjectExtended, error)
}

View file

@ -3,6 +3,7 @@ package frostfsid
import (
"fmt"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/util"
@ -20,6 +21,8 @@ type Client struct {
client *client.StaticClient // static FrostFS ID contract client
}
var _ frostfsidcore.SubjectProvider = (*Client)(nil)
// NewFromMorph wraps client to work with FrostFS ID contract.
func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8) (*Client, error) {
sc, err := client.NewStatic(cli, contract, fee, client.TryNotary(), client.AsAlphabet())

View file

@ -13,7 +13,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
const methodGetSubject = "getSubject"
const (
methodGetSubject = "getSubject"
methodGetSubjectExtended = "getSubjectExtended"
)
func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) {
prm := client.TestInvokePrm{}
@ -33,6 +36,24 @@ func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error)
return subj, nil
}
func (c *Client) GetSubjectExtended(addr util.Uint160) (*frostfsidclient.SubjectExtended, error) {
prm := client.TestInvokePrm{}
prm.SetMethod(methodGetSubjectExtended)
prm.SetArgs(addr)
res, err := c.client.TestInvoke(prm)
if err != nil {
return nil, fmt.Errorf("could not perform test invocation (%s): %w", methodGetSubject, err)
}
subj, err := parseSubjectExtended(res)
if err != nil {
return nil, fmt.Errorf("could not parse test invocation result (%s): %w", methodGetSubject, err)
}
return subj, nil
}
func parseSubject(res []stackitem.Item) (*frostfsidclient.Subject, error) {
if ln := len(res); ln != 1 {
return nil, fmt.Errorf("unexpected stack item count (%s): %d", methodGetSubject, ln)
@ -79,6 +100,64 @@ func parseSubject(res []stackitem.Item) (*frostfsidclient.Subject, error) {
return &subj, nil
}
func parseSubjectExtended(res []stackitem.Item) (*frostfsidclient.SubjectExtended, error) {
if ln := len(res); ln != 1 {
return nil, fmt.Errorf("unexpected stack item count (%s): %d", methodGetSubject, ln)
}
structArr, err := client.ArrayFromStackItem(res[0])
if err != nil {
return nil, fmt.Errorf("could not get item array of container (%s): %w", methodGetSubject, err)
}
var subj frostfsidclient.SubjectExtended
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil {
return nil, err
}
if !structArr[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1]))
if err != nil {
return nil, err
}
}
if !structArr[2].Equals(stackitem.Null{}) {
subj.Namespace, err = unwrap.UTF8String(makeValidRes(structArr[2]))
if err != nil {
return nil, err
}
}
if !structArr[3].Equals(stackitem.Null{}) {
subj.Name, err = unwrap.UTF8String(makeValidRes(structArr[3]))
if err != nil {
return nil, err
}
}
subj.KV, err = parseMap(structArr[4])
if err != nil {
return nil, err
}
if !structArr[5].Equals(stackitem.Null{}) {
groupItems, ok := structArr[5].Value().([]stackitem.Item)
if !ok {
return nil, errors.New("invalid groups field")
}
subj.Groups, err = parseGroups(groupItems)
if err != nil {
return nil, err
}
}
return &subj, nil
}
func makeValidRes(item stackitem.Item) (*result.Invoke, error) {
return &result.Invoke{
Stack: []stackitem.Item{item},
@ -116,3 +195,54 @@ func parseMap(item stackitem.Item) (map[string]string, error) {
return res, nil
}
func parseGroups(items []stackitem.Item) ([]*frostfsidclient.Group, error) {
var err error
res := make([]*frostfsidclient.Group, len(items))
for i := 0; i < len(items); i++ {
arr, ok := items[i].Value().([]stackitem.Item)
if !ok {
return nil, errors.New("invalid group type")
}
res[i], err = parseGroup(arr)
if err != nil {
return nil, err
}
}
return res, nil
}
func parseGroup(structArr []stackitem.Item) (*frostfsidclient.Group, error) {
Review

@dkirillov do we have similar code in s3-gw?

@dkirillov do we have similar code in s3-gw?
Review

No. We use client from frostfs-contract repo that parses it for us

func (c Client) GetSubjectExtended(addr util.Uint160) (*SubjectExtended, error) {

No. We use client from frostfs-contract repo that parses it for us https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/db361318009cc9c9df97133c8c2acedad4eb2fd4/frostfsid/client/client.go#L253
if len(structArr) < 4 {
return nil, errors.New("invalid response group struct")
}
groupID, err := structArr[0].TryInteger()
if err != nil {
return nil, err
}
name, err := structArr[1].TryBytes()
if err != nil {
return nil, err
}
namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := parseMap(structArr[3])
if err != nil {
return nil, err
}
return &frostfsidclient.Group{
ID: groupID.Int64(),
Name: string(name),
Namespace: string(namespace),
KV: kvs,
}, nil
}

View file

@ -14,9 +14,9 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
@ -27,14 +27,8 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const (
subjectNotFoundErrorMessage = "subject not found"
)
var (
@ -59,22 +53,18 @@ type containers interface {
Get(cid.ID) (*containercore.Container, error)
}
type frostfsidSubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
}
type apeChecker struct {
router policyengine.ChainRouter
reader containers
ir ir
nm netmap.Source
frostFSIDClient frostfsidSubjectProvider
frostFSIDClient frostfsidcore.SubjectProvider
next Server
}
func NewAPEServer(router policyengine.ChainRouter, reader containers, ir ir, nm netmap.Source, frostFSIDClient frostfsidSubjectProvider, srv Server) Server {
func NewAPEServer(router policyengine.ChainRouter, reader containers, ir ir, nm netmap.Source, frostFSIDClient frostfsidcore.SubjectProvider, srv Server) Server {
return &apeChecker{
router: router,
reader: reader,
@ -574,7 +564,7 @@ func (ac *apeChecker) namespaceByOwner(owner *refs.OwnerID) (string, error) {
if err == nil {
namespace = subject.Namespace
} else {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return "", fmt.Errorf("get subject error: %w", err)
}
}
@ -628,16 +618,12 @@ func (ac *apeChecker) fillWithUserClaimTags(reqProps map[string]string, pk *keys
if reqProps == nil {
reqProps = make(map[string]string)
}
subj, err := ac.frostFSIDClient.GetSubject(pk.GetScriptHash())
props, err := aperequest.FormFrostfsIDRequestProperties(ac.frostFSIDClient, pk)
if err != nil {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
return reqProps, err
}
for k, v := range subj.KV {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[properyKey] = v
for propertyName, properyValue := range props {
reqProps[propertyName] = properyValue
}
return reqProps, nil
}

View file

@ -15,6 +15,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
@ -44,6 +45,7 @@ func TestAPE(t *testing.T) {
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
t.Run("deny get container for others", testDenyGetContainerForOthers)
t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag)
t.Run("deny get container by group id", testDenyGetContainerByGroupID)
t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR)
t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken)
@ -278,6 +280,19 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) {
},
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
@ -338,6 +353,104 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied)
}
func testDenyGetContainerByGroupID(t *testing.T) {
t.Parallel()
srv := &srvStub{
calls: map[string]int{},
}
router := inmemory.NewInMemory()
contRdr := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
nm := &netmapStub{}
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
contID := cidtest.ID()
testContainer := containertest.Container()
pp := netmap.PlacementPolicy{}
require.NoError(t, pp.DecodeString("REP 1"))
testContainer.SetPlacementPolicy(pp)
contRdr.c[contID] = &containercore.Container{Value: testContainer}
nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{}
var testNetmap netmap.NetMap
testNetmap.SetEpoch(nm.currentEpoch)
testNetmap.SetNodes([]netmap.NodeInfo{{}})
nm.netmaps[nm.currentEpoch] = &testNetmap
nm.netmaps[nm.currentEpoch-1] = &testNetmap
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: commonschema.PropertyKeyFrostFSIDGroupID,
Value: "19888",
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := &container.GetRequest{}
req.SetBody(&container.GetRequestBody{})
var refContID refs.ContainerID
contID.WriteToV2(&refContID)
req.GetBody().SetContainerID(&refContID)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
resp, err := apeSrv.Get(context.Background(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
}
func testDenySetContainerEACLForIR(t *testing.T) {
t.Parallel()
srv := &srvStub{
@ -620,6 +733,21 @@ func testDenyPutContainerReadNamespaceFromFrostfsID(t *testing.T) {
Name: testDomainName,
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req)
@ -689,6 +817,21 @@ func testDenyPutContainerInvalidNamespace(t *testing.T) {
Name: testDomainName,
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req)
@ -799,6 +942,34 @@ func testDenyListContainersValidationNamespaceError(t *testing.T) {
Name: testDomainName,
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
actorScriptHash: {
Namespace: actorDomain,
Name: actorDomain,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19777,
},
},
},
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
@ -922,13 +1093,22 @@ func (s *netmapStub) Epoch() (uint64, error) {
}
type frostfsidStub struct {
subjects map[util.Uint160]*client.Subject
subjects map[util.Uint160]*client.Subject
subjectsExt map[util.Uint160]*client.SubjectExtended
}
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
s, ok := f.subjects[owner]
if !ok {
return nil, fmt.Errorf("%s", subjectNotFoundErrorMessage)
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
}
return s, nil
}
func (f *frostfsidStub) GetSubjectExtended(owner util.Uint160) (*client.SubjectExtended, error) {
s, ok := f.subjectsExt[owner]
if !ok {
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
}
return s, nil
}
@ -965,7 +1145,8 @@ func newTestAPEServer() testAPEServer {
netmap := &netmapStub{}
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
subjects: map[util.Uint160]*client.Subject{},
subjectsExt: map[util.Uint160]*client.SubjectExtended{},
}
apeChecker := &apeChecker{

View file

@ -6,7 +6,7 @@ import (
"fmt"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
@ -14,7 +14,6 @@ import (
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type checkerImpl struct {
@ -22,14 +21,10 @@ type checkerImpl struct {
headerProvider HeaderProvider
frostFSIDClient frostfsidSubjectProvider
frostFSIDClient frostfsidcore.SubjectProvider
}
type frostfsidSubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
}
func NewChecker(chainRouter policyengine.ChainRouter, headerProvider HeaderProvider, frostFSIDClient frostfsidSubjectProvider) Checker {
func NewChecker(chainRouter policyengine.ChainRouter, headerProvider HeaderProvider, frostFSIDClient frostfsidcore.SubjectProvider) Checker {
return &checkerImpl{
chainRouter: chainRouter,

View file

@ -7,6 +7,7 @@ import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
@ -156,14 +157,15 @@ var (
)
type frostfsIDProviderMock struct {
m map[util.Uint160]*client.Subject
subjects map[util.Uint160]*client.Subject
subjectsExtended map[util.Uint160]*client.SubjectExtended
}
var _ frostfsidSubjectProvider = (*frostfsIDProviderMock)(nil)
var _ frostfsidcore.SubjectProvider = (*frostfsIDProviderMock)(nil)
func newFrostfsIDProviderMock(t *testing.T) *frostfsIDProviderMock {
return &frostfsIDProviderMock{
m: map[util.Uint160]*client.Subject{
subjects: map[util.Uint160]*client.Subject{
scriptHashFromSenderKey(t, senderKey): {
Namespace: "testnamespace",
Name: "test",
@ -173,6 +175,27 @@ func newFrostfsIDProviderMock(t *testing.T) *frostfsIDProviderMock {
},
},
},
subjectsExtended: map[util.Uint160]*client.SubjectExtended{
scriptHashFromSenderKey(t, senderKey): {
Namespace: "testnamespace",
Name: "test",
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 1,
Name: "test",
Namespace: "testnamespace",
KV: map[string]string{
"attr1": "value1",
"attr2": "value2",
},
},
},
},
},
}
}
@ -183,9 +206,17 @@ func scriptHashFromSenderKey(t *testing.T, senderKey string) util.Uint160 {
}
func (f *frostfsIDProviderMock) GetSubject(key util.Uint160) (*client.Subject, error) {
v, ok := f.m[key]
v, ok := f.subjects[key]
if !ok {
return nil, fmt.Errorf("%s", subjectNotFoundErrorMessage)
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
}
return v, nil
}
func (f *frostfsIDProviderMock) GetSubjectExtended(key util.Uint160) (*client.SubjectExtended, error) {
v, ok := f.subjectsExtended[key]
if !ok {
return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
}
return v, nil
}

View file

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"strconv"
"strings"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
@ -13,15 +12,10 @@ import (
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
const (
subjectNotFoundErrorMessage = "subject not found"
)
var defaultRequest = aperequest.Request{}
func nativeSchemaRole(role acl.Role) string {
@ -151,16 +145,12 @@ func (c *checkerImpl) fillWithUserClaimTags(reqProps map[string]string, prm Prm)
if err != nil {
return nil, err
}
subj, err := c.frostFSIDClient.GetSubject(pk.GetScriptHash())
props, err := aperequest.FormFrostfsIDRequestProperties(c.frostFSIDClient, pk)
if err != nil {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
return reqProps, err
}
for k, v := range subj.KV {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[properyKey] = v
for propertyName, properyValue := range props {
reqProps[propertyName] = properyValue
}
return reqProps, nil
}

View file

@ -275,6 +275,7 @@ func TestNewAPERequest(t *testing.T) {
nativeschema.PropertyKeyActorRole: prm.Role,
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1",
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2",
commonschema.PropertyKeyFrostFSIDGroupID: "1",
},
)

View file

@ -14,13 +14,10 @@ import (
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
var subjectNotFoundErrorMessage = "subject not found"
func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey) error {
namespace := ""
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns")
@ -81,16 +78,12 @@ func (s *Service) fillWithUserClaimTags(reqProps map[string]string, publicKey *k
if reqProps == nil {
reqProps = make(map[string]string)
}
subj, err := s.frostfsidSubjectProvider.GetSubject(publicKey.GetScriptHash())
props, err := aperequest.FormFrostfsIDRequestProperties(s.frostfsidSubjectProvider, publicKey)
if err != nil {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
return reqProps, err
}
for k, v := range subj.KV {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[properyKey] = v
for propertyName, properyValue := range props {
reqProps[propertyName] = properyValue
}
return reqProps, nil
}

View file

@ -4,21 +4,16 @@ import (
"crypto/ecdsa"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type FrostfsidSubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
}
type ContainerSource interface {
container.Source
@ -36,7 +31,7 @@ type cfg struct {
rawPub []byte
nmSource netmap.Source
cnrSource ContainerSource
frostfsidSubjectProvider FrostfsidSubjectProvider
frostfsidSubjectProvider frostfsidcore.SubjectProvider
eaclSource container.EACLSource
forest pilorama.Forest
// replication-related parameters
@ -62,7 +57,7 @@ func WithContainerSource(src ContainerSource) Option {
}
}
func WithFrostfsidSubjectProvider(provider FrostfsidSubjectProvider) Option {
func WithFrostfsidSubjectProvider(provider frostfsidcore.SubjectProvider) Option {
return func(c *cfg) {
c.frostfsidSubjectProvider = provider
}