Set group id to APE requests for tree, container and object services #1117
17 changed files with 529 additions and 77 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
71
cmd/frostfs-node/frostfsid.go
Normal file
71
cmd/frostfs-node/frostfsid.go
Normal 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,
|
||||
}
|
||||
}
|
36
pkg/ape/request/frostfsid.go
Normal file
36
pkg/ape/request/frostfsid.go
Normal 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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ape
|
||||
package request
|
||||
|
||||
import (
|
||||
aperesource "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
|
|
16
pkg/core/frostfsid/subject_provider.go
Normal file
16
pkg/core/frostfsid/subject_provider.go
Normal 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)
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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) {
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue
@dkirillov do we have similar code in s3-gw?
No. We use client from frostfs-contract repo that parses it for us
func (c Client) GetSubjectExtended(addr util.Uint160) (*SubjectExtended, error) {