Compare commits

...

4 commits

Author SHA1 Message Date
c11c936112 [#1117] node: Introduce cache for frostfsid contract client
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-05-07 11:19:09 +03:00
022c34cb14 [#1117] ape: Introduce FormFrostfsIDRequestProperties method
* `FormFrostfsIDRequestProperties` gets user claim tags and group id and sets them
  as ape request properties.
* Make tree, container and object service use the method.
* Fix unit-tests.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-05-07 11:19:00 +03:00
74dd845f8d [#1117] core: Introduce SubjectProvider interface for FrostfsID
* Make tree, object and container services use SubjectProvider interface.
* Fix unit-tests.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-05-02 21:29:55 +03:00
4e5cac074a [#1117] morph: Make frostfsid client provide GetSubjectExtended method
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-05-02 21:29:55 +03: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/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/chainbase" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/chainbase"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "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" 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"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree" "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/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
containerClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" 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" nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
netmap2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap" netmap2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap"
@ -421,7 +421,7 @@ type shared struct {
cnrClient *containerClient.Client cnrClient *containerClient.Client
frostfsidClient *frostfsidClient.Client frostfsidClient frostfsidcore.SubjectProvider
respSvc *response.Service respSvc *response.Service

View file

@ -28,6 +28,9 @@ const (
// APEChainCacheSizeDefault is a default value of APE chain cache. // APEChainCacheSizeDefault is a default value of APE chain cache.
APEChainCacheSizeDefault = 10_000 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") 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") 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" "net"
containerGRPC "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container/grpc" 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" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
containerCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" 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" 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/client/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" "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) 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) 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( server := containerTransportGRPC.New(
containerService.NewSignService( 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)
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)
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 ( import (
aperesource "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource" 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 ( import (
"fmt" "fmt"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" "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/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -20,6 +21,8 @@ type Client struct {
client *client.StaticClient // static FrostFS ID contract client client *client.StaticClient // static FrostFS ID contract client
} }
var _ frostfsidcore.SubjectProvider = (*Client)(nil)
// NewFromMorph wraps client to work with FrostFS ID contract. // NewFromMorph wraps client to work with FrostFS ID contract.
func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8) (*Client, error) { func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8) (*Client, error) {
sc, err := client.NewStatic(cli, contract, fee, client.TryNotary(), client.AsAlphabet()) 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" "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) { func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) {
prm := client.TestInvokePrm{} prm := client.TestInvokePrm{}
@ -33,6 +36,24 @@ func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error)
return subj, nil 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) { func parseSubject(res []stackitem.Item) (*frostfsidclient.Subject, error) {
if ln := len(res); ln != 1 { if ln := len(res); ln != 1 {
return nil, fmt.Errorf("unexpected stack item count (%s): %d", methodGetSubject, ln) 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 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) { func makeValidRes(item stackitem.Item) (*result.Invoke, error) {
return &result.Invoke{ return &result.Invoke{
Stack: []stackitem.Item{item}, Stack: []stackitem.Item{item},
@ -116,3 +195,54 @@ func parseMap(item stackitem.Item) (map[string]string, error) {
return res, nil 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) {
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/container"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" 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" aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" 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-node/pkg/core/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
@ -27,14 +27,8 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" 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" 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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const (
subjectNotFoundErrorMessage = "subject not found"
) )
var ( var (
@ -59,22 +53,18 @@ type containers interface {
Get(cid.ID) (*containercore.Container, error) Get(cid.ID) (*containercore.Container, error)
} }
type frostfsidSubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
}
type apeChecker struct { type apeChecker struct {
router policyengine.ChainRouter router policyengine.ChainRouter
reader containers reader containers
ir ir ir ir
nm netmap.Source nm netmap.Source
frostFSIDClient frostfsidSubjectProvider frostFSIDClient frostfsidcore.SubjectProvider
next Server 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{ return &apeChecker{
router: router, router: router,
reader: reader, reader: reader,
@ -574,7 +564,7 @@ func (ac *apeChecker) namespaceByOwner(owner *refs.OwnerID) (string, error) {
if err == nil { if err == nil {
namespace = subject.Namespace namespace = subject.Namespace
} else { } else {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) { if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return "", fmt.Errorf("get subject error: %w", err) 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 { if reqProps == nil {
reqProps = make(map[string]string) reqProps = make(map[string]string)
} }
subj, err := ac.frostFSIDClient.GetSubject(pk.GetScriptHash()) props, err := aperequest.FormFrostfsIDRequestProperties(ac.frostFSIDClient, pk)
if err != nil { if err != nil {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) { return reqProps, err
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
} }
for k, v := range subj.KV { for propertyName, properyValue := range props {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k) reqProps[propertyName] = properyValue
reqProps[properyKey] = v
} }
return reqProps, nil return reqProps, nil
} }

View file

@ -15,6 +15,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "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"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" 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 no rule found", testDenyGetContainerNoRuleFound)
t.Run("deny get container for others", testDenyGetContainerForOthers) t.Run("deny get container for others", testDenyGetContainerForOthers)
t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag) 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 set container eACL for IR", testDenySetContainerEACLForIR)
t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken) t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken) 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) apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
@ -338,6 +353,104 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied) 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) { func testDenySetContainerEACLForIR(t *testing.T) {
t.Parallel() t.Parallel()
srv := &srvStub{ srv := &srvStub{
@ -620,6 +733,21 @@ func testDenyPutContainerReadNamespaceFromFrostfsID(t *testing.T) {
Name: testDomainName, 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) apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req) resp, err := apeSrv.Put(context.Background(), req)
@ -689,6 +817,21 @@ func testDenyPutContainerInvalidNamespace(t *testing.T) {
Name: testDomainName, 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) apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req) resp, err := apeSrv.Put(context.Background(), req)
@ -799,6 +942,34 @@ func testDenyListContainersValidationNamespaceError(t *testing.T) {
Name: testDomainName, 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) apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
@ -922,13 +1093,22 @@ func (s *netmapStub) Epoch() (uint64, error) {
} }
type frostfsidStub struct { 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) { func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
s, ok := f.subjects[owner] s, ok := f.subjects[owner]
if !ok { 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 return s, nil
} }
@ -965,7 +1145,8 @@ func newTestAPEServer() testAPEServer {
netmap := &netmapStub{} netmap := &netmapStub{}
frostfsIDSubjectReader := &frostfsidStub{ frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{}, subjects: map[util.Uint160]*client.Subject{},
subjectsExt: map[util.Uint160]*client.SubjectExtended{},
} }
apeChecker := &apeChecker{ apeChecker := &apeChecker{

View file

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

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "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" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
@ -156,14 +157,15 @@ var (
) )
type frostfsIDProviderMock struct { 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 { func newFrostfsIDProviderMock(t *testing.T) *frostfsIDProviderMock {
return &frostfsIDProviderMock{ return &frostfsIDProviderMock{
m: map[util.Uint160]*client.Subject{ subjects: map[util.Uint160]*client.Subject{
scriptHashFromSenderKey(t, senderKey): { scriptHashFromSenderKey(t, senderKey): {
Namespace: "testnamespace", Namespace: "testnamespace",
Name: "test", 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) { func (f *frostfsIDProviderMock) GetSubject(key util.Uint160) (*client.Subject, error) {
v, ok := f.m[key] v, ok := f.subjects[key]
if !ok { 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 return v, nil
} }

View file

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

View file

@ -275,6 +275,7 @@ func TestNewAPERequest(t *testing.T) {
nativeschema.PropertyKeyActorRole: prm.Role, nativeschema.PropertyKeyActorRole: prm.Role,
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1", fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1",
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2", 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" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "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" 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/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 { func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey) error {
namespace := "" namespace := ""
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns") 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 { if reqProps == nil {
reqProps = make(map[string]string) reqProps = make(map[string]string)
} }
subj, err := s.frostfsidSubjectProvider.GetSubject(publicKey.GetScriptHash()) props, err := aperequest.FormFrostfsIDRequestProperties(s.frostfsidSubjectProvider, publicKey)
if err != nil { if err != nil {
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) { return reqProps, err
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
} }
for k, v := range subj.KV { for propertyName, properyValue := range props {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k) reqProps[propertyName] = properyValue
reqProps[properyKey] = v
} }
return reqProps, nil return reqProps, nil
} }

View file

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