ape, adm, container: Form APE requests properly #940
13 changed files with 850 additions and 56 deletions
|
@ -151,11 +151,13 @@ func initFrostfsIDCreateNamespaceCmd() {
|
|||
frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd)
|
||||
frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create")
|
||||
frostfsidCreateNamespaceCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListNamespacesCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidListNamespacesCmd)
|
||||
frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidListNamespacesCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateSubjectCmd() {
|
||||
|
@ -164,12 +166,14 @@ func initFrostfsIDCreateSubjectCmd() {
|
|||
frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject")
|
||||
frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace")
|
||||
frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key")
|
||||
frostfsidCreateSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteSubjectCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd)
|
||||
frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidDeleteSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListSubjectsCmd() {
|
||||
|
@ -177,6 +181,7 @@ func initFrostfsIDListSubjectsCmd() {
|
|||
frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
||||
frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListSubjectsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateGroupCmd() {
|
||||
|
@ -184,6 +189,7 @@ func initFrostfsIDCreateGroupCmd() {
|
|||
frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group")
|
||||
frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace")
|
||||
frostfsidCreateGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteGroupCmd() {
|
||||
|
@ -191,12 +197,14 @@ func initFrostfsIDDeleteGroupCmd() {
|
|||
frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group")
|
||||
frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidDeleteGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListGroupsCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidListGroupsCmd)
|
||||
frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups")
|
||||
frostfsidListGroupsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDAddSubjectToGroupCmd() {
|
||||
|
@ -204,6 +212,7 @@ func initFrostfsIDAddSubjectToGroupCmd() {
|
|||
frostfsidAddSubjectToGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDRemoveSubjectFromGroupCmd() {
|
||||
|
@ -211,6 +220,7 @@ func initFrostfsIDRemoveSubjectFromGroupCmd() {
|
|||
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListGroupSubjectsCmd() {
|
||||
|
@ -219,6 +229,7 @@ func initFrostfsIDListGroupSubjectsCmd() {
|
|||
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
|
@ -115,6 +115,10 @@ func getTargetBucket(tx *bbolt.Tx, name chain.Name, target policyengine.Target)
|
|||
return nil, fmt.Errorf("%w: %w: %c", policyengine.ErrChainNotFound, ErrTargetTypeBucketNotFound, target.Type)
|
||||
}
|
||||
|
||||
if target.Type == policyengine.Namespace && target.Name == "" {
|
||||
target.Name = "root"
|
||||
|
||||
}
|
||||
|
||||
rbucket := typeBucket.Bucket([]byte(target.Name))
|
||||
if rbucket == nil {
|
||||
return nil, fmt.Errorf("%w: %w: %s", policyengine.ErrChainNotFound, ErrTargetNameBucketNotFound, target.Name)
|
||||
|
@ -146,6 +150,10 @@ func getTargetBucketCreateIfEmpty(tx *bbolt.Tx, name chain.Name, target policyen
|
|||
}
|
||||
}
|
||||
|
||||
if target.Type == policyengine.Namespace && target.Name == "" {
|
||||
target.Name = "root"
|
||||
}
|
||||
|
||||
rbucket := typeBucket.Bucket([]byte(target.Name))
|
||||
if rbucket == nil {
|
||||
var err error
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
}
|
||||
|
|
|
@ -17,12 +17,7 @@ func apeTarget(chainTarget *control.ChainTarget) (engine.Target, error) {
|
|||
case control.ChainTarget_CONTAINER:
|
||||
return engine.ContainerTarget(chainTarget.GetName()), nil
|
||||
case control.ChainTarget_NAMESPACE:
|
||||
namespace := chainTarget.GetName()
|
||||
// If namespace is empty, we take it for root namespace.
|
||||
if namespace == "" {
|
||||
namespace = "root"
|
||||
}
|
||||
return engine.NamespaceTarget(namespace), nil
|
||||
return engine.NamespaceTarget(chainTarget.GetName()), nil
|
||||
default:
|
||||
}
|
||||
return engine.Target{}, status.Error(codes.InvalidArgument,
|
||||
|
|
|
@ -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
Such renaming is actual only for local overrides or also for rules in contract?
Only for local overrides (this is because boltdb does not allow to create buckets with empty names)
I also would like to highlight that listing with empty namespace does not behave the same way as in Policy contract