forked from TrueCloudLab/frostfs-node
[#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 832 additions and 52 deletions
|
@ -449,6 +449,7 @@ type cfg struct {
|
|||
cfgMorph cfgMorph
|
||||
cfgAccounting cfgAccounting
|
||||
cfgContainer cfgContainer
|
||||
cfgFrostfsID cfgFrostfsID
|
||||
cfgNodeInfo cfgNodeInfo
|
||||
cfgNetmap cfgNetmap
|
||||
cfgControlService cfgControlService
|
||||
|
@ -569,6 +570,10 @@ type cfgContainer struct {
|
|||
workerPool util.WorkerPool // pool for asynchronous handlers
|
||||
}
|
||||
|
||||
type cfgFrostfsID struct {
|
||||
scriptHash neogoutil.Uint160
|
||||
}
|
||||
|
||||
type cfgNetmap struct {
|
||||
scriptHash neogoutil.Uint160
|
||||
wrapper *nmClient.Client
|
||||
|
@ -681,6 +686,8 @@ func initCfg(appCfg *config.Config) *cfg {
|
|||
}
|
||||
c.cfgContainer = initContainer(appCfg)
|
||||
|
||||
c.cfgFrostfsID = initFrostfsID(appCfg)
|
||||
|
||||
c.cfgNetmap = initNetmap(appCfg, netState, relayOnly)
|
||||
|
||||
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 {
|
||||
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
||||
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")
|
||||
}
|
||||
|
||||
func FrostfsID(c *config.Config) util.Uint160 {
|
||||
return contractAddress(c, "frostfsid")
|
||||
}
|
||||
|
||||
// Proxy returnsthe value of "proxy" config parameter
|
||||
// from "contracts" section.
|
||||
//
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
containerCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/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"
|
||||
containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container"
|
||||
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)
|
||||
|
||||
frostFSIDClient, err := frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0)
|
||||
fatalOnErr(err)
|
||||
|
||||
server := containerTransportGRPC.New(
|
||||
containerService.NewSignService(
|
||||
&c.key.PrivateKey,
|
||||
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),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -288,6 +288,7 @@ func lookupScriptHashesInNNS(c *cfg) {
|
|||
{&c.cfgNetmap.scriptHash, client.NNSNetmapContractName},
|
||||
{&c.cfgAccounting.scriptHash, client.NNSBalanceContractName},
|
||||
{&c.cfgContainer.scriptHash, client.NNSContainerContractName},
|
||||
{&c.cfgFrostfsID.scriptHash, client.NNSFrostFSIDContractName},
|
||||
{&c.cfgMorph.proxyScriptHash, client.NNSProxyContractName},
|
||||
{&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-contract v0.18.1-0.20240115082915-f2a82aa635aa
|
||||
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/policy-engine v0.0.0-20240122104724-06cbfe8691ad
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -6,8 +6,8 @@ git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSV
|
|||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 h1:PaZ8GpnUoXxUoNsc1qp36bT2u7FU+neU4Jn9cl8AWqI=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65/go.mod h1:6aAX80dvJ3r5fjN9CzzPglRptoiPgIC9KFGGsUA+1Hw=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240117145620-110b7e41706e h1:0t+3iEb2wFJjJryqGnh5wkr9DEiwRNb61KaxC27U+po=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240117145620-110b7e41706e/go.mod h1:t1akKcUH7iBrFHX8rSXScYMP17k2kYQXMbZooiL5Juw=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240126141009-65b4525b3bf0 h1:jp/Cg6CF3ip0IVRCjt48qKyv1cbMUmNYTn2z1jFLWqc=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240126141009-65b4525b3bf0/go.mod h1:t1akKcUH7iBrFHX8rSXScYMP17k2kYQXMbZooiL5Juw=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad h1:+D96Uu0Pw+55CrxP+srazthtZAh0Q19BtJo1Pm4hOP0=
|
||||
|
|
|
@ -33,16 +33,17 @@ func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error)
|
|||
return subj, nil
|
||||
}
|
||||
|
||||
// parseSubject from https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/dd5919348da9731f24504e7bc485516c2ba5f11c/frostfsid/client/client.go#L592
|
||||
func parseSubject(structArr []stackitem.Item) (*frostfsidclient.Subject, error) {
|
||||
if len(structArr) < 5 {
|
||||
return nil, errors.New("invalid response subject struct")
|
||||
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)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
subj frostfsidclient.Subject
|
||||
)
|
||||
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.Subject
|
||||
|
||||
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
|
||||
if err != nil {
|
||||
|
|
|
@ -8,14 +8,17 @@ import (
|
|||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||
"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"
|
||||
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
|
@ -25,6 +28,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
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"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -35,6 +39,7 @@ var (
|
|||
errInvalidSessionTokenOwner = errors.New("malformed request: invalid session token owner")
|
||||
errEmptyBodySignature = errors.New("malformed request: empty body signature")
|
||||
errMissingOwnerID = errors.New("malformed request: missing owner ID")
|
||||
errSubjectNotFound = errors.New("subject not found")
|
||||
|
||||
undefinedContainerID = cid.ID{}
|
||||
)
|
||||
|
@ -47,22 +52,29 @@ 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
|
||||
|
||||
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{
|
||||
router: router,
|
||||
reader: reader,
|
||||
ir: ir,
|
||||
next: srv,
|
||||
nm: nm,
|
||||
router: router,
|
||||
reader: reader,
|
||||
ir: ir,
|
||||
next: srv,
|
||||
nm: nm,
|
||||
frostFSIDClient: frostFSIDClient,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,9 +137,17 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
|
|||
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{
|
||||
resource: &apeResource{
|
||||
name: nativeschema.ResourceFormatRootContainers,
|
||||
name: resourceName(namespace, ""),
|
||||
props: make(map[string]string),
|
||||
},
|
||||
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,
|
||||
policyengine.NewRequestTargetWithNamespace(""),
|
||||
policyengine.NewRequestTargetWithNamespace(namespace),
|
||||
request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -162,9 +182,17 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
|
|||
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{
|
||||
resource: &apeResource{
|
||||
name: nativeschema.ResourceFormatRootContainers,
|
||||
name: resourceName(namespace, ""),
|
||||
props: make(map[string]string),
|
||||
},
|
||||
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,
|
||||
policyengine.NewRequestTargetWithNamespace(""),
|
||||
policyengine.NewRequestTargetWithNamespace(namespace),
|
||||
request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -251,9 +279,15 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
|
|||
return err
|
||||
}
|
||||
|
||||
namespace := ""
|
||||
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cont.Value).Zone(), ".ns")
|
||||
if hasNamespace {
|
||||
namespace = cntNamespace
|
||||
}
|
||||
|
||||
request := &apeRequest{
|
||||
resource: &apeResource{
|
||||
name: fmt.Sprintf(nativeschema.ResourceFormatRootContainer, id.EncodeToString()),
|
||||
name: resourceName(namespace, id.EncodeToString()),
|
||||
props: ac.getContainerProps(cont),
|
||||
},
|
||||
op: op,
|
||||
|
@ -261,7 +295,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
|
|||
}
|
||||
|
||||
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
||||
policyengine.NewRequestTargetWithContainer(id.EncodeToString()),
|
||||
policyengine.NewRequestTarget(cntNamespace, id.EncodeToString()),
|
||||
request)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -326,6 +360,19 @@ func (r *apeResource) Property(key string) string {
|
|||
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 {
|
||||
return map[string]string{
|
||||
nativeschema.PropertyKeyContainerOwnerID: c.Value.Owner().EncodeToString(),
|
||||
|
@ -514,3 +561,70 @@ func isContainerNode(nm *netmapSDK.NetMap, pk, binCnrID []byte, cont *containerc
|
|||
|
||||
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 (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
@ -9,10 +10,12 @@ import (
|
|||
"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/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-contract/frostfsid/client"
|
||||
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||
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"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/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"
|
||||
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"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testDomainName = "testdomainname"
|
||||
testDomainZone = "testdomainname.ns"
|
||||
)
|
||||
|
||||
func TestAPE(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
||||
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)
|
||||
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 by namespace invalidation", testDenyListContainersValidationNamespaceError)
|
||||
}
|
||||
|
||||
func testDenyGetContainerForOthers(t *testing.T) {
|
||||
|
@ -49,7 +61,10 @@ func testDenyGetContainerForOthers(t *testing.T) {
|
|||
keys: [][]byte{},
|
||||
}
|
||||
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()
|
||||
testContainer := containertest.Container()
|
||||
|
@ -122,7 +137,10 @@ func testDenySetContainerEACLForIR(t *testing.T) {
|
|||
keys: [][]byte{},
|
||||
}
|
||||
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()
|
||||
testContainer := containertest.Container()
|
||||
|
@ -197,7 +215,10 @@ func testDenyGetContainerEACLForIRSessionToken(t *testing.T) {
|
|||
keys: [][]byte{},
|
||||
}
|
||||
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()
|
||||
testContainer := containertest.Container()
|
||||
|
@ -283,7 +304,10 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
|
|||
keys: [][]byte{},
|
||||
}
|
||||
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()
|
||||
|
||||
|
@ -317,26 +341,7 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
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))
|
||||
req := initPutRequest(t, testContainer)
|
||||
|
||||
resp, err := apeSrv.Put(context.Background(), req)
|
||||
require.Nil(t, resp)
|
||||
|
@ -344,6 +349,139 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
|
|||
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) {
|
||||
t.Parallel()
|
||||
srv := &srvStub{
|
||||
|
@ -357,7 +495,10 @@ func testDenyListContainersForPK(t *testing.T) {
|
|||
keys: [][]byte{},
|
||||
}
|
||||
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.netmaps = map[uint64]*netmap.NetMap{}
|
||||
|
@ -409,6 +550,82 @@ func testDenyListContainersForPK(t *testing.T) {
|
|||
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 {
|
||||
calls map[string]int
|
||||
}
|
||||
|
@ -489,3 +706,424 @@ func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) {
|
|||
func (s *netmapStub) Epoch() (uint64, error) {
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
containerV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"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/util/logger"
|
||||
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"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/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.cnrOwner = cnr.Value.Owner()
|
||||
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,
|
||||
// otherwise the request would not pass validation
|
||||
|
|
Loading…
Reference in a new issue