[#934] container: Make container APE middleware read namespaces
* Those methods that can access already existing containers and thus can get container properties should read namespace from Zone property. If Zone is not set, take a namespace for root. * Otherwise, define namespaces by owner ID via frostfs-id contract. * Improve unit-tests, consider more cases. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
96c86c4637
commit
5be2af881a
10 changed files with 830 additions and 50 deletions
|
@ -449,6 +449,7 @@ type cfg struct {
|
||||||
cfgMorph cfgMorph
|
cfgMorph cfgMorph
|
||||||
cfgAccounting cfgAccounting
|
cfgAccounting cfgAccounting
|
||||||
cfgContainer cfgContainer
|
cfgContainer cfgContainer
|
||||||
|
cfgFrostfsID cfgFrostfsID
|
||||||
cfgNodeInfo cfgNodeInfo
|
cfgNodeInfo cfgNodeInfo
|
||||||
cfgNetmap cfgNetmap
|
cfgNetmap cfgNetmap
|
||||||
cfgControlService cfgControlService
|
cfgControlService cfgControlService
|
||||||
|
@ -569,6 +570,10 @@ type cfgContainer struct {
|
||||||
workerPool util.WorkerPool // pool for asynchronous handlers
|
workerPool util.WorkerPool // pool for asynchronous handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cfgFrostfsID struct {
|
||||||
|
scriptHash neogoutil.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
type cfgNetmap struct {
|
type cfgNetmap struct {
|
||||||
scriptHash neogoutil.Uint160
|
scriptHash neogoutil.Uint160
|
||||||
wrapper *nmClient.Client
|
wrapper *nmClient.Client
|
||||||
|
@ -681,6 +686,8 @@ func initCfg(appCfg *config.Config) *cfg {
|
||||||
}
|
}
|
||||||
c.cfgContainer = initContainer(appCfg)
|
c.cfgContainer = initContainer(appCfg)
|
||||||
|
|
||||||
|
c.cfgFrostfsID = initFrostfsID(appCfg)
|
||||||
|
|
||||||
c.cfgNetmap = initNetmap(appCfg, netState, relayOnly)
|
c.cfgNetmap = initNetmap(appCfg, netState, relayOnly)
|
||||||
|
|
||||||
c.cfgGRPC = initCfgGRPC()
|
c.cfgGRPC = initCfgGRPC()
|
||||||
|
@ -779,6 +786,12 @@ func initContainer(appCfg *config.Config) cfgContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initFrostfsID(appCfg *config.Config) cfgFrostfsID {
|
||||||
|
return cfgFrostfsID{
|
||||||
|
scriptHash: contractsconfig.FrostfsID(appCfg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func initCfgGRPC() cfgGRPC {
|
func initCfgGRPC() cfgGRPC {
|
||||||
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
||||||
maxAddrAmount := uint64(maxChunkSize) / addressSize // each address is about 72 bytes
|
maxAddrAmount := uint64(maxChunkSize) / addressSize // each address is about 72 bytes
|
||||||
|
|
|
@ -38,6 +38,10 @@ func Container(c *config.Config) util.Uint160 {
|
||||||
return contractAddress(c, "container")
|
return contractAddress(c, "container")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FrostfsID(c *config.Config) util.Uint160 {
|
||||||
|
return contractAddress(c, "frostfsid")
|
||||||
|
}
|
||||||
|
|
||||||
// Proxy returnsthe value of "proxy" config parameter
|
// Proxy returnsthe value of "proxy" config parameter
|
||||||
// from "contracts" section.
|
// from "contracts" section.
|
||||||
//
|
//
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"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"
|
||||||
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/event"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
|
||||||
containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container"
|
containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container"
|
||||||
containerTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/container/grpc"
|
containerTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/container/grpc"
|
||||||
|
@ -32,11 +33,14 @@ 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)
|
||||||
|
fatalOnErr(err)
|
||||||
|
|
||||||
server := containerTransportGRPC.New(
|
server := containerTransportGRPC.New(
|
||||||
containerService.NewSignService(
|
containerService.NewSignService(
|
||||||
&c.key.PrivateKey,
|
&c.key.PrivateKey,
|
||||||
containerService.NewAPEServer(c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine, cnrRdr,
|
containerService.NewAPEServer(c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine, cnrRdr,
|
||||||
newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource,
|
newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource, frostFSIDClient,
|
||||||
containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc),
|
containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -288,6 +288,7 @@ func lookupScriptHashesInNNS(c *cfg) {
|
||||||
{&c.cfgNetmap.scriptHash, client.NNSNetmapContractName},
|
{&c.cfgNetmap.scriptHash, client.NNSNetmapContractName},
|
||||||
{&c.cfgAccounting.scriptHash, client.NNSBalanceContractName},
|
{&c.cfgAccounting.scriptHash, client.NNSBalanceContractName},
|
||||||
{&c.cfgContainer.scriptHash, client.NNSContainerContractName},
|
{&c.cfgContainer.scriptHash, client.NNSContainerContractName},
|
||||||
|
{&c.cfgFrostfsID.scriptHash, client.NNSFrostFSIDContractName},
|
||||||
{&c.cfgMorph.proxyScriptHash, client.NNSProxyContractName},
|
{&c.cfgMorph.proxyScriptHash, client.NNSProxyContractName},
|
||||||
{&c.cfgObject.cfgAccessPolicyEngine.policyContractHash, client.NNSPolicyContractName},
|
{&c.cfgObject.cfgAccessPolicyEngine.policyContractHash, client.NNSPolicyContractName},
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -6,7 +6,7 @@ require (
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240112150928-72885aae835c
|
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240112150928-72885aae835c
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa
|
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240117145620-110b7e41706e
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240126141009-65b4525b3bf0
|
||||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1
|
git.frostfs.info/TrueCloudLab/hrw v1.2.1
|
||||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad
|
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad
|
||||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0
|
git.frostfs.info/TrueCloudLab/tzhash v1.8.0
|
||||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
|
@ -33,16 +33,17 @@ func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error)
|
||||||
return subj, nil
|
return subj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSubject from https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/dd5919348da9731f24504e7bc485516c2ba5f11c/frostfsid/client/client.go#L592
|
func parseSubject(res []stackitem.Item) (*frostfsidclient.Subject, error) {
|
||||||
func parseSubject(structArr []stackitem.Item) (*frostfsidclient.Subject, error) {
|
if ln := len(res); ln != 1 {
|
||||||
if len(structArr) < 5 {
|
return nil, fmt.Errorf("unexpected stack item count (%s): %d", methodGetSubject, ln)
|
||||||
return nil, errors.New("invalid response subject struct")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
structArr, err := client.ArrayFromStackItem(res[0])
|
||||||
err error
|
if err != nil {
|
||||||
subj frostfsidclient.Subject
|
return nil, fmt.Errorf("could not get item array of container (%s): %w", methodGetSubject, err)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
var subj frostfsidclient.Subject
|
||||||
|
|
||||||
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
|
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -8,14 +8,17 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||||
"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"
|
||||||
|
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"
|
||||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||||
|
@ -25,6 +28,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -35,6 +39,7 @@ var (
|
||||||
errInvalidSessionTokenOwner = errors.New("malformed request: invalid session token owner")
|
errInvalidSessionTokenOwner = errors.New("malformed request: invalid session token owner")
|
||||||
errEmptyBodySignature = errors.New("malformed request: empty body signature")
|
errEmptyBodySignature = errors.New("malformed request: empty body signature")
|
||||||
errMissingOwnerID = errors.New("malformed request: missing owner ID")
|
errMissingOwnerID = errors.New("malformed request: missing owner ID")
|
||||||
|
errSubjectNotFound = errors.New("subject not found")
|
||||||
|
|
||||||
undefinedContainerID = cid.ID{}
|
undefinedContainerID = cid.ID{}
|
||||||
)
|
)
|
||||||
|
@ -47,22 +52,29 @@ 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
|
||||||
|
|
||||||
next Server
|
next Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPEServer(router policyengine.ChainRouter, reader containers, ir ir, nm netmap.Source, srv Server) Server {
|
func NewAPEServer(router policyengine.ChainRouter, reader containers, ir ir, nm netmap.Source, frostFSIDClient frostfsidSubjectProvider, srv Server) Server {
|
||||||
return &apeChecker{
|
return &apeChecker{
|
||||||
router: router,
|
router: router,
|
||||||
reader: reader,
|
reader: reader,
|
||||||
ir: ir,
|
ir: ir,
|
||||||
next: srv,
|
next: srv,
|
||||||
nm: nm,
|
nm: nm,
|
||||||
|
frostFSIDClient: frostFSIDClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,9 +137,17 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
|
||||||
nativeschema.PropertyKeyActorRole: role,
|
nativeschema.PropertyKeyActorRole: role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get owner namespace: %w", err)
|
||||||
|
}
|
||||||
|
if err := ac.validateNamespaceByPublicKey(pk, namespace); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
request := &apeRequest{
|
request := &apeRequest{
|
||||||
resource: &apeResource{
|
resource: &apeResource{
|
||||||
name: nativeschema.ResourceFormatRootContainers,
|
name: resourceName(namespace, ""),
|
||||||
props: make(map[string]string),
|
props: make(map[string]string),
|
||||||
},
|
},
|
||||||
op: nativeschema.MethodListContainers,
|
op: nativeschema.MethodListContainers,
|
||||||
|
@ -135,7 +155,7 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
|
||||||
}
|
}
|
||||||
|
|
||||||
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
||||||
policyengine.NewRequestTargetWithNamespace(""),
|
policyengine.NewRequestTargetWithNamespace(namespace),
|
||||||
request)
|
request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -162,9 +182,17 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
|
||||||
nativeschema.PropertyKeyActorRole: role,
|
nativeschema.PropertyKeyActorRole: role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get namespace error: %w", err)
|
||||||
|
}
|
||||||
|
if err = validateNamespace(req.GetBody().GetContainer(), namespace); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
request := &apeRequest{
|
request := &apeRequest{
|
||||||
resource: &apeResource{
|
resource: &apeResource{
|
||||||
name: nativeschema.ResourceFormatRootContainers,
|
name: resourceName(namespace, ""),
|
||||||
props: make(map[string]string),
|
props: make(map[string]string),
|
||||||
},
|
},
|
||||||
op: nativeschema.MethodPutContainer,
|
op: nativeschema.MethodPutContainer,
|
||||||
|
@ -172,7 +200,7 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
|
||||||
}
|
}
|
||||||
|
|
||||||
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
||||||
policyengine.NewRequestTargetWithNamespace(""),
|
policyengine.NewRequestTargetWithNamespace(namespace),
|
||||||
request)
|
request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -251,9 +279,15 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace := ""
|
||||||
|
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cont.Value).Zone(), ".ns")
|
||||||
|
if hasNamespace {
|
||||||
|
namespace = cntNamespace
|
||||||
|
}
|
||||||
|
|
||||||
request := &apeRequest{
|
request := &apeRequest{
|
||||||
resource: &apeResource{
|
resource: &apeResource{
|
||||||
name: fmt.Sprintf(nativeschema.ResourceFormatRootContainer, id.EncodeToString()),
|
name: resourceName(namespace, id.EncodeToString()),
|
||||||
props: ac.getContainerProps(cont),
|
props: ac.getContainerProps(cont),
|
||||||
},
|
},
|
||||||
op: op,
|
op: op,
|
||||||
|
@ -261,7 +295,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
|
||||||
}
|
}
|
||||||
|
|
||||||
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
||||||
policyengine.NewRequestTargetWithContainer(id.EncodeToString()),
|
policyengine.NewRequestTarget(cntNamespace, id.EncodeToString()),
|
||||||
request)
|
request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -326,6 +360,19 @@ func (r *apeResource) Property(key string) string {
|
||||||
return r.props[key]
|
return r.props[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceName(namespace string, container string) string {
|
||||||
|
if namespace == "" && container == "" {
|
||||||
|
return nativeschema.ResourceFormatRootContainers
|
||||||
|
}
|
||||||
|
if namespace == "" && container != "" {
|
||||||
|
return fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container)
|
||||||
|
}
|
||||||
|
if namespace != "" && container == "" {
|
||||||
|
return fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, namespace)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, namespace, container)
|
||||||
|
}
|
||||||
|
|
||||||
func (ac *apeChecker) getContainerProps(c *containercore.Container) map[string]string {
|
func (ac *apeChecker) getContainerProps(c *containercore.Container) map[string]string {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
nativeschema.PropertyKeyContainerOwnerID: c.Value.Owner().EncodeToString(),
|
nativeschema.PropertyKeyContainerOwnerID: c.Value.Owner().EncodeToString(),
|
||||||
|
@ -514,3 +561,70 @@ func isContainerNode(nm *netmapSDK.NetMap, pk, binCnrID []byte, cont *containerc
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ac *apeChecker) namespaceByOwner(owner *refs.OwnerID) (string, error) {
|
||||||
|
var ownerSDK user.ID
|
||||||
|
if owner == nil {
|
||||||
|
return "", fmt.Errorf("owner id is not set")
|
||||||
|
}
|
||||||
|
if err := ownerSDK.ReadFromV2(*owner); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
addr, err := ownerSDK.ScriptHash()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := ""
|
||||||
|
subject, err := ac.frostFSIDClient.GetSubject(addr)
|
||||||
|
if err == nil {
|
||||||
|
namespace = subject.Namespace
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(err.Error(), errSubjectNotFound.Error()) {
|
||||||
|
return "", fmt.Errorf("get subject error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namespace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateNamespace validates a namespace set in a container.
|
||||||
|
// If frostfs-id contract stores a namespace N1 for an owner ID and a container within a request
|
||||||
|
// is set with namespace N2 (via Zone() property), then N2 is invalid and the request is denied.
|
||||||
|
func validateNamespace(cnrV2 *container.Container, ownerIDNamespace string) error {
|
||||||
|
if cnrV2 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var cnr cnrSDK.Container
|
||||||
|
if err := cnr.ReadFromV2(*cnrV2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr).Zone(), ".ns")
|
||||||
|
if hasNamespace {
|
||||||
|
if cntNamespace != ownerIDNamespace {
|
||||||
|
if ownerIDNamespace == "" {
|
||||||
|
return fmt.Errorf("invalid domain zone: no namespace is expected")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid domain zone: expected namespace %s, but got %s", ownerIDNamespace, cntNamespace)
|
||||||
|
}
|
||||||
|
} else if ownerIDNamespace != "" {
|
||||||
|
return fmt.Errorf("invalid domain zone: expected namespace %s, but got invalid or empty", ownerIDNamespace)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateNamespace validates if a namespace of a request actor equals to owner's namespace.
|
||||||
|
// An actor's namespace is calculated by a public key.
|
||||||
|
func (ac *apeChecker) validateNamespaceByPublicKey(pk *keys.PublicKey, ownerIDNamespace string) error {
|
||||||
|
var actor user.ID
|
||||||
|
user.IDFromKey(&actor, (ecdsa.PublicKey)(*pk))
|
||||||
|
actorOwnerID := new(refs.OwnerID)
|
||||||
|
actor.WriteToV2(actorOwnerID)
|
||||||
|
actorNamespace, err := ac.namespaceByOwner(actorOwnerID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get actor namespace: %w", err)
|
||||||
|
}
|
||||||
|
if actorNamespace != ownerIDNamespace {
|
||||||
|
return fmt.Errorf("actor namespace %s differs from owner: %s", actorNamespace, ownerIDNamespace)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -9,10 +10,12 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||||
"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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||||
"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"
|
||||||
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
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"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||||
containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
|
containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
|
||||||
|
@ -24,16 +27,25 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
||||||
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"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testDomainName = "testdomainname"
|
||||||
|
testDomainZone = "testdomainname.ns"
|
||||||
|
)
|
||||||
|
|
||||||
func TestAPE(t *testing.T) {
|
func TestAPE(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
||||||
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)
|
||||||
|
t.Run("deny put container, read namespace from frostfsID", testDenyPutContainerReadNamespaceFromFrostfsID)
|
||||||
|
t.Run("deny put container with invlaid namespace", testDenyPutContainerInvalidNamespace)
|
||||||
t.Run("deny list containers for owner with PK", testDenyListContainersForPK)
|
t.Run("deny list containers for owner with PK", testDenyListContainersForPK)
|
||||||
|
t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDenyGetContainerForOthers(t *testing.T) {
|
func testDenyGetContainerForOthers(t *testing.T) {
|
||||||
|
@ -49,7 +61,10 @@ func testDenyGetContainerForOthers(t *testing.T) {
|
||||||
keys: [][]byte{},
|
keys: [][]byte{},
|
||||||
}
|
}
|
||||||
nm := &netmapStub{}
|
nm := &netmapStub{}
|
||||||
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv)
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
contID := cidtest.ID()
|
contID := cidtest.ID()
|
||||||
testContainer := containertest.Container()
|
testContainer := containertest.Container()
|
||||||
|
@ -122,7 +137,10 @@ func testDenySetContainerEACLForIR(t *testing.T) {
|
||||||
keys: [][]byte{},
|
keys: [][]byte{},
|
||||||
}
|
}
|
||||||
nm := &netmapStub{}
|
nm := &netmapStub{}
|
||||||
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv)
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
contID := cidtest.ID()
|
contID := cidtest.ID()
|
||||||
testContainer := containertest.Container()
|
testContainer := containertest.Container()
|
||||||
|
@ -197,7 +215,10 @@ func testDenyGetContainerEACLForIRSessionToken(t *testing.T) {
|
||||||
keys: [][]byte{},
|
keys: [][]byte{},
|
||||||
}
|
}
|
||||||
nm := &netmapStub{}
|
nm := &netmapStub{}
|
||||||
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv)
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
contID := cidtest.ID()
|
contID := cidtest.ID()
|
||||||
testContainer := containertest.Container()
|
testContainer := containertest.Container()
|
||||||
|
@ -283,7 +304,10 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
|
||||||
keys: [][]byte{},
|
keys: [][]byte{},
|
||||||
}
|
}
|
||||||
nm := &netmapStub{}
|
nm := &netmapStub{}
|
||||||
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv)
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
testContainer := containertest.Container()
|
testContainer := containertest.Container()
|
||||||
|
|
||||||
|
@ -317,26 +341,7 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req := &container.PutRequest{}
|
req := initPutRequest(t, testContainer)
|
||||||
req.SetBody(&container.PutRequestBody{})
|
|
||||||
var reqCont container.Container
|
|
||||||
testContainer.WriteToV2(&reqCont)
|
|
||||||
req.GetBody().SetContainer(&reqCont)
|
|
||||||
|
|
||||||
sessionPK, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
sToken := sessiontest.ContainerSigned()
|
|
||||||
sToken.ApplyOnlyTo(cid.ID{})
|
|
||||||
require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
|
|
||||||
var sTokenV2 session.Token
|
|
||||||
sToken.WriteToV2(&sTokenV2)
|
|
||||||
metaHeader := new(session.RequestMetaHeader)
|
|
||||||
metaHeader.SetSessionToken(&sTokenV2)
|
|
||||||
req.SetMetaHeader(metaHeader)
|
|
||||||
|
|
||||||
pk, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
|
||||||
|
|
||||||
resp, err := apeSrv.Put(context.Background(), req)
|
resp, err := apeSrv.Put(context.Background(), req)
|
||||||
require.Nil(t, resp)
|
require.Nil(t, resp)
|
||||||
|
@ -344,6 +349,139 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
|
||||||
require.ErrorAs(t, err, &errAccessDenied)
|
require.ErrorAs(t, err, &errAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDenyPutContainerReadNamespaceFromFrostfsID(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{}
|
||||||
|
|
||||||
|
cnrID, testContainer := initTestContainer(t, true)
|
||||||
|
contRdr.c[cnrID] = &containercore.Container{Value: testContainer}
|
||||||
|
|
||||||
|
nm.currentEpoch = 100
|
||||||
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
||||||
|
|
||||||
|
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.AccessDenied,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.MethodPutContainer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initPutRequest(t, testContainer)
|
||||||
|
ownerScriptHash := initOwnerIDScriptHash(t, testContainer)
|
||||||
|
|
||||||
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{
|
||||||
|
ownerScriptHash: {
|
||||||
|
Namespace: testDomainName,
|
||||||
|
Name: testDomainName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
resp, err := apeSrv.Put(context.Background(), req)
|
||||||
|
require.Nil(t, resp)
|
||||||
|
var errAccessDenied *apistatus.ObjectAccessDenied
|
||||||
|
require.ErrorAs(t, err, &errAccessDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDenyPutContainerInvalidNamespace(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{}
|
||||||
|
|
||||||
|
cnrID, testContainer := initTestContainer(t, false)
|
||||||
|
var domain cnrSDK.Domain
|
||||||
|
domain.SetName("incorrect" + testDomainName)
|
||||||
|
domain.SetZone("incorrect" + testDomainZone)
|
||||||
|
cnrSDK.WriteDomain(&testContainer, domain)
|
||||||
|
contRdr.c[cnrID] = &containercore.Container{Value: testContainer}
|
||||||
|
|
||||||
|
nm.currentEpoch = 100
|
||||||
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
||||||
|
|
||||||
|
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.AccessDenied,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.MethodPutContainer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initPutRequest(t, testContainer)
|
||||||
|
ownerScriptHash := initOwnerIDScriptHash(t, testContainer)
|
||||||
|
|
||||||
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{
|
||||||
|
ownerScriptHash: {
|
||||||
|
Namespace: testDomainName,
|
||||||
|
Name: testDomainName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
resp, err := apeSrv.Put(context.Background(), req)
|
||||||
|
require.Nil(t, resp)
|
||||||
|
require.ErrorContains(t, err, "invalid domain zone")
|
||||||
|
}
|
||||||
|
|
||||||
func testDenyListContainersForPK(t *testing.T) {
|
func testDenyListContainersForPK(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
srv := &srvStub{
|
srv := &srvStub{
|
||||||
|
@ -357,7 +495,10 @@ func testDenyListContainersForPK(t *testing.T) {
|
||||||
keys: [][]byte{},
|
keys: [][]byte{},
|
||||||
}
|
}
|
||||||
nm := &netmapStub{}
|
nm := &netmapStub{}
|
||||||
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv)
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
nm.currentEpoch = 100
|
nm.currentEpoch = 100
|
||||||
nm.netmaps = map[uint64]*netmap.NetMap{}
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
||||||
|
@ -409,6 +550,82 @@ func testDenyListContainersForPK(t *testing.T) {
|
||||||
require.ErrorAs(t, err, &errAccessDenied)
|
require.ErrorAs(t, err, &errAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDenyListContainersValidationNamespaceError(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{}
|
||||||
|
|
||||||
|
actorPK, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ownerPK, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actorScriptHash, ownerScriptHash := initActorOwnerScriptHashes(t, actorPK, ownerPK)
|
||||||
|
|
||||||
|
const actorDomain = "actor" + testDomainName
|
||||||
|
|
||||||
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{
|
||||||
|
actorScriptHash: {
|
||||||
|
Namespace: actorDomain,
|
||||||
|
Name: actorDomain,
|
||||||
|
},
|
||||||
|
ownerScriptHash: {
|
||||||
|
Namespace: testDomainName,
|
||||||
|
Name: testDomainName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
|
nm.currentEpoch = 100
|
||||||
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
||||||
|
|
||||||
|
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.AccessDenied,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.MethodListContainers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.ResourceFormatRootContainers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||||
|
Value: actorPK.PublicKey().String(),
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initListRequest(t, actorPK, ownerPK)
|
||||||
|
|
||||||
|
resp, err := apeSrv.List(context.Background(), req)
|
||||||
|
require.Nil(t, resp)
|
||||||
|
require.ErrorContains(t, err, "actor namespace "+actorDomain+" differs")
|
||||||
|
}
|
||||||
|
|
||||||
type srvStub struct {
|
type srvStub struct {
|
||||||
calls map[string]int
|
calls map[string]int
|
||||||
}
|
}
|
||||||
|
@ -489,3 +706,424 @@ func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) {
|
||||||
func (s *netmapStub) Epoch() (uint64, error) {
|
func (s *netmapStub) Epoch() (uint64, error) {
|
||||||
return s.currentEpoch, nil
|
return s.currentEpoch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type frostfsidStub struct {
|
||||||
|
subjects map[util.Uint160]*client.Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
|
||||||
|
s, ok := f.subjects[owner]
|
||||||
|
if !ok {
|
||||||
|
return nil, errSubjectNotFound
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testAPEServer struct {
|
||||||
|
engine engine.Engine
|
||||||
|
|
||||||
|
containerReader *containerStub
|
||||||
|
|
||||||
|
ir *irStub
|
||||||
|
|
||||||
|
netmap *netmapStub
|
||||||
|
|
||||||
|
frostfsIDSubjectReader *frostfsidStub
|
||||||
|
|
||||||
|
apeChecker *apeChecker
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestAPEServer() testAPEServer {
|
||||||
|
srv := &srvStub{
|
||||||
|
calls: map[string]int{},
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := inmemory.NewInMemory()
|
||||||
|
|
||||||
|
containerReader := &containerStub{
|
||||||
|
c: map[cid.ID]*containercore.Container{},
|
||||||
|
}
|
||||||
|
|
||||||
|
ir := &irStub{
|
||||||
|
keys: [][]byte{},
|
||||||
|
}
|
||||||
|
|
||||||
|
netmap := &netmapStub{}
|
||||||
|
|
||||||
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{},
|
||||||
|
}
|
||||||
|
|
||||||
|
apeChecker := &apeChecker{
|
||||||
|
router: engine,
|
||||||
|
reader: containerReader,
|
||||||
|
ir: ir,
|
||||||
|
nm: netmap,
|
||||||
|
frostFSIDClient: frostfsIDSubjectReader,
|
||||||
|
next: srv,
|
||||||
|
}
|
||||||
|
|
||||||
|
return testAPEServer{
|
||||||
|
engine: engine,
|
||||||
|
containerReader: containerReader,
|
||||||
|
ir: ir,
|
||||||
|
netmap: netmap,
|
||||||
|
frostfsIDSubjectReader: frostfsIDSubjectReader,
|
||||||
|
apeChecker: apeChecker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateContainerBoundedOperation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("check root-defined container in root-defined container target rule", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
components := newTestAPEServer()
|
||||||
|
contID, testContainer := initTestContainer(t, false)
|
||||||
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
initTestNetmap(components.netmap)
|
||||||
|
|
||||||
|
_, _, err := components.engine.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: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initTestGetContainerRequest(t, contID)
|
||||||
|
|
||||||
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
||||||
|
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
|
||||||
|
require.ErrorContains(t, err, aErr.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check root-defined container in testdomain-defined container target rule", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
components := newTestAPEServer()
|
||||||
|
contID, testContainer := initTestContainer(t, false)
|
||||||
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
initTestNetmap(components.netmap)
|
||||||
|
|
||||||
|
_, _, err := components.engine.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.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initTestGetContainerRequest(t, contID)
|
||||||
|
|
||||||
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check root-defined container in testdomain namespace target rule", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
components := newTestAPEServer()
|
||||||
|
contID, testContainer := initTestContainer(t, false)
|
||||||
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
initTestNetmap(components.netmap)
|
||||||
|
|
||||||
|
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.AccessDenied,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.MethodGetContainer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initTestGetContainerRequest(t, contID)
|
||||||
|
|
||||||
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check testdomain-defined container in root-defined container target rule", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
components := newTestAPEServer()
|
||||||
|
contID, testContainer := initTestContainer(t, true)
|
||||||
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
initTestNetmap(components.netmap)
|
||||||
|
|
||||||
|
_, _, err := components.engine.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: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initTestGetContainerRequest(t, contID)
|
||||||
|
|
||||||
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check testdomain-defined container in testdomain-defined container target rule", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
components := newTestAPEServer()
|
||||||
|
contID, testContainer := initTestContainer(t, true)
|
||||||
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
initTestNetmap(components.netmap)
|
||||||
|
|
||||||
|
_, _, err := components.engine.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.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initTestGetContainerRequest(t, contID)
|
||||||
|
|
||||||
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
||||||
|
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
|
||||||
|
require.ErrorContains(t, err, aErr.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check testdomain-defined container in testdomain namespace target rule", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
components := newTestAPEServer()
|
||||||
|
contID, testContainer := initTestContainer(t, true)
|
||||||
|
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
initTestNetmap(components.netmap)
|
||||||
|
|
||||||
|
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.AccessDenied,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.MethodGetContainer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Object: chain.ObjectRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := initTestGetContainerRequest(t, contID)
|
||||||
|
|
||||||
|
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
|
||||||
|
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
|
||||||
|
require.ErrorContains(t, err, aErr.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTestGetContainerRequest(t *testing.T, contID cid.ID) *container.GetRequest {
|
||||||
|
req := &container.GetRequest{}
|
||||||
|
req.SetBody(&container.GetRequestBody{})
|
||||||
|
var refContID refs.ContainerID
|
||||||
|
contID.WriteToV2(&refContID)
|
||||||
|
req.GetBody().SetContainerID(&refContID)
|
||||||
|
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTestNetmap(netmapStub *netmapStub) {
|
||||||
|
netmapStub.currentEpoch = 100
|
||||||
|
netmapStub.netmaps = map[uint64]*netmap.NetMap{}
|
||||||
|
var testNetmap netmap.NetMap
|
||||||
|
testNetmap.SetEpoch(netmapStub.currentEpoch)
|
||||||
|
testNetmap.SetNodes([]netmap.NodeInfo{{}})
|
||||||
|
netmapStub.netmaps[netmapStub.currentEpoch] = &testNetmap
|
||||||
|
netmapStub.netmaps[netmapStub.currentEpoch-1] = &testNetmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTestContainer(t *testing.T, isDomainSet bool) (cid.ID, cnrSDK.Container) {
|
||||||
|
contID := cidtest.ID()
|
||||||
|
testContainer := containertest.Container()
|
||||||
|
pp := netmap.PlacementPolicy{}
|
||||||
|
require.NoError(t, pp.DecodeString("REP 1"))
|
||||||
|
testContainer.SetPlacementPolicy(pp)
|
||||||
|
if isDomainSet {
|
||||||
|
// no domain defined -> container is defined in root namespace
|
||||||
|
var domain cnrSDK.Domain
|
||||||
|
domain.SetName(testDomainName)
|
||||||
|
domain.SetZone(testDomainZone)
|
||||||
|
cnrSDK.WriteDomain(&testContainer, domain)
|
||||||
|
}
|
||||||
|
return contID, testContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPutRequest(t *testing.T, testContainer cnrSDK.Container) *container.PutRequest {
|
||||||
|
req := &container.PutRequest{}
|
||||||
|
req.SetBody(&container.PutRequestBody{})
|
||||||
|
var reqCont container.Container
|
||||||
|
testContainer.WriteToV2(&reqCont)
|
||||||
|
req.GetBody().SetContainer(&reqCont)
|
||||||
|
|
||||||
|
sessionPK, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
sToken := sessiontest.ContainerSigned()
|
||||||
|
sToken.ApplyOnlyTo(cid.ID{})
|
||||||
|
require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
|
||||||
|
var sTokenV2 session.Token
|
||||||
|
sToken.WriteToV2(&sTokenV2)
|
||||||
|
metaHeader := new(session.RequestMetaHeader)
|
||||||
|
metaHeader.SetSessionToken(&sTokenV2)
|
||||||
|
req.SetMetaHeader(metaHeader)
|
||||||
|
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func initOwnerIDScriptHash(t *testing.T, testContainer cnrSDK.Container) util.Uint160 {
|
||||||
|
var ownerSDK *user.ID
|
||||||
|
owner := testContainer.Owner()
|
||||||
|
ownerSDK = &owner
|
||||||
|
sc, err := ownerSDK.ScriptHash()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
func initActorOwnerScriptHashes(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) (actorScriptHash util.Uint160, ownerScriptHash util.Uint160) {
|
||||||
|
var actorUserID user.ID
|
||||||
|
user.IDFromKey(&actorUserID, ecdsa.PublicKey(*actorPK.PublicKey()))
|
||||||
|
var err error
|
||||||
|
actorScriptHash, err = actorUserID.ScriptHash()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var ownerUserID user.ID
|
||||||
|
user.IDFromKey(&ownerUserID, ecdsa.PublicKey(*ownerPK.PublicKey()))
|
||||||
|
ownerScriptHash, err = ownerUserID.ScriptHash()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqual(t, ownerScriptHash.String(), actorScriptHash.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func initListRequest(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) *container.ListRequest {
|
||||||
|
var ownerUserID user.ID
|
||||||
|
user.IDFromKey(&ownerUserID, ownerPK.PrivateKey.PublicKey)
|
||||||
|
|
||||||
|
req := &container.ListRequest{}
|
||||||
|
req.SetBody(&container.ListRequestBody{})
|
||||||
|
var ownerID refs.OwnerID
|
||||||
|
ownerUserID.WriteToV2(&ownerID)
|
||||||
|
req.GetBody().SetOwnerID(&ownerID)
|
||||||
|
|
||||||
|
require.NoError(t, signature.SignServiceMessage(&actorPK.PrivateKey, req))
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
containerV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
|
||||||
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-api-go/v2/session"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
|
@ -14,6 +14,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -744,7 +745,11 @@ func (b Service) findRequestInfo(req MetaWithToken, idCnr cid.ID, op acl.Op) (in
|
||||||
info.operation = op
|
info.operation = op
|
||||||
info.cnrOwner = cnr.Value.Owner()
|
info.cnrOwner = cnr.Value.Owner()
|
||||||
info.idCnr = idCnr
|
info.idCnr = idCnr
|
||||||
info.cnrNamespace = cnr.Value.Attribute(containerV2.SysAttributeZone)
|
|
||||||
|
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
|
||||||
|
if hasNamespace {
|
||||||
|
info.cnrNamespace = cnrNamespace
|
||||||
|
}
|
||||||
|
|
||||||
// it is assumed that at the moment the key will be valid,
|
// it is assumed that at the moment the key will be valid,
|
||||||
// otherwise the request would not pass validation
|
// otherwise the request would not pass validation
|
||||||
|
|
Loading…
Reference in a new issue