ape, adm, container: Form APE requests properly #940

Merged
fyrchik merged 3 commits from aarifullin/frostfs-node:fix/934-invalid_ape_request into master 2024-02-01 17:38:27 +00:00
13 changed files with 852 additions and 58 deletions

View file

@ -151,11 +151,13 @@ func initFrostfsIDCreateNamespaceCmd() {
frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd) frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd)
frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create") frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create")
frostfsidCreateNamespaceCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDListNamespacesCmd() { func initFrostfsIDListNamespacesCmd() {
frostfsidCmd.AddCommand(frostfsidListNamespacesCmd) frostfsidCmd.AddCommand(frostfsidListNamespacesCmd)
frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidListNamespacesCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDCreateSubjectCmd() { func initFrostfsIDCreateSubjectCmd() {
@ -164,12 +166,14 @@ func initFrostfsIDCreateSubjectCmd() {
frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject") frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject")
frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace") frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace")
frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key") frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key")
frostfsidCreateSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDDeleteSubjectCmd() { func initFrostfsIDDeleteSubjectCmd() {
frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd) frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd)
frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address") frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address")
frostfsidDeleteSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDListSubjectsCmd() { func initFrostfsIDListSubjectsCmd() {
@ -177,6 +181,7 @@ func initFrostfsIDListSubjectsCmd() {
frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects") frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)") frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
frostfsidListSubjectsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDCreateGroupCmd() { func initFrostfsIDCreateGroupCmd() {
@ -184,6 +189,7 @@ func initFrostfsIDCreateGroupCmd() {
frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group") frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group")
frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace") frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace")
frostfsidCreateGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDDeleteGroupCmd() { func initFrostfsIDDeleteGroupCmd() {
@ -191,12 +197,14 @@ func initFrostfsIDDeleteGroupCmd() {
frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group") frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group")
frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
frostfsidDeleteGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDListGroupsCmd() { func initFrostfsIDListGroupsCmd() {
frostfsidCmd.AddCommand(frostfsidListGroupsCmd) frostfsidCmd.AddCommand(frostfsidListGroupsCmd)
frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups") frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups")
frostfsidListGroupsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDAddSubjectToGroupCmd() { func initFrostfsIDAddSubjectToGroupCmd() {
@ -204,6 +212,7 @@ func initFrostfsIDAddSubjectToGroupCmd() {
frostfsidAddSubjectToGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidAddSubjectToGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address") frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
frostfsidAddSubjectToGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDRemoveSubjectFromGroupCmd() { func initFrostfsIDRemoveSubjectFromGroupCmd() {
@ -211,6 +220,7 @@ func initFrostfsIDRemoveSubjectFromGroupCmd() {
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address") frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
frostfsidRemoveSubjectFromGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func initFrostfsIDListGroupSubjectsCmd() { func initFrostfsIDListGroupSubjectsCmd() {
@ -219,6 +229,7 @@ func initFrostfsIDListGroupSubjectsCmd() {
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name") frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id") frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)") frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
frostfsidListGroupSubjectsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
} }
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) { func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {

View file

@ -449,6 +449,7 @@ type cfg struct {
cfgMorph cfgMorph cfgMorph cfgMorph
cfgAccounting cfgAccounting cfgAccounting cfgAccounting
cfgContainer cfgContainer cfgContainer cfgContainer
cfgFrostfsID cfgFrostfsID
cfgNodeInfo cfgNodeInfo cfgNodeInfo cfgNodeInfo
cfgNetmap cfgNetmap cfgNetmap cfgNetmap
cfgControlService cfgControlService cfgControlService cfgControlService
@ -569,6 +570,10 @@ type cfgContainer struct {
workerPool util.WorkerPool // pool for asynchronous handlers workerPool util.WorkerPool // pool for asynchronous handlers
} }
type cfgFrostfsID struct {
scriptHash neogoutil.Uint160
}
type cfgNetmap struct { type cfgNetmap struct {
scriptHash neogoutil.Uint160 scriptHash neogoutil.Uint160
wrapper *nmClient.Client wrapper *nmClient.Client
@ -681,6 +686,8 @@ func initCfg(appCfg *config.Config) *cfg {
} }
c.cfgContainer = initContainer(appCfg) c.cfgContainer = initContainer(appCfg)
c.cfgFrostfsID = initFrostfsID(appCfg)
c.cfgNetmap = initNetmap(appCfg, netState, relayOnly) c.cfgNetmap = initNetmap(appCfg, netState, relayOnly)
c.cfgGRPC = initCfgGRPC() c.cfgGRPC = initCfgGRPC()
@ -779,6 +786,12 @@ func initContainer(appCfg *config.Config) cfgContainer {
} }
} }
func initFrostfsID(appCfg *config.Config) cfgFrostfsID {
return cfgFrostfsID{
scriptHash: contractsconfig.FrostfsID(appCfg),
}
}
func initCfgGRPC() cfgGRPC { func initCfgGRPC() cfgGRPC {
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
maxAddrAmount := uint64(maxChunkSize) / addressSize // each address is about 72 bytes maxAddrAmount := uint64(maxChunkSize) / addressSize // each address is about 72 bytes

View file

@ -38,6 +38,10 @@ func Container(c *config.Config) util.Uint160 {
return contractAddress(c, "container") return contractAddress(c, "container")
} }
func FrostfsID(c *config.Config) util.Uint160 {
return contractAddress(c, "frostfsid")
}
// Proxy returnsthe value of "proxy" config parameter // Proxy returnsthe value of "proxy" config parameter
// from "contracts" section. // from "contracts" section.
// //

View file

@ -9,6 +9,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
containerCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" containerCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container" containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container"
containerTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/container/grpc" containerTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/container/grpc"
@ -32,11 +33,14 @@ func initContainerService(_ context.Context, c *cfg) {
cnrRdr, cnrWrt := configureEACLAndContainerSources(c, wrap, cnrSrc) cnrRdr, cnrWrt := configureEACLAndContainerSources(c, wrap, cnrSrc)
frostFSIDClient, err := frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0)
fatalOnErr(err)
server := containerTransportGRPC.New( server := containerTransportGRPC.New(
containerService.NewSignService( containerService.NewSignService(
&c.key.PrivateKey, &c.key.PrivateKey,
containerService.NewAPEServer(c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine, cnrRdr, containerService.NewAPEServer(c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine, cnrRdr,
newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource, newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource, frostFSIDClient,
containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc), containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc),
), ),
), ),

View file

@ -288,6 +288,7 @@ func lookupScriptHashesInNNS(c *cfg) {
{&c.cfgNetmap.scriptHash, client.NNSNetmapContractName}, {&c.cfgNetmap.scriptHash, client.NNSNetmapContractName},
{&c.cfgAccounting.scriptHash, client.NNSBalanceContractName}, {&c.cfgAccounting.scriptHash, client.NNSBalanceContractName},
{&c.cfgContainer.scriptHash, client.NNSContainerContractName}, {&c.cfgContainer.scriptHash, client.NNSContainerContractName},
{&c.cfgFrostfsID.scriptHash, client.NNSFrostFSIDContractName},
{&c.cfgMorph.proxyScriptHash, client.NNSProxyContractName}, {&c.cfgMorph.proxyScriptHash, client.NNSProxyContractName},
{&c.cfgObject.cfgAccessPolicyEngine.policyContractHash, client.NNSPolicyContractName}, {&c.cfgObject.cfgAccessPolicyEngine.policyContractHash, client.NNSPolicyContractName},
} }

2
go.mod
View file

@ -6,7 +6,7 @@ require (
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240112150928-72885aae835c git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240112150928-72885aae835c
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240117145620-110b7e41706e git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240126141009-65b4525b3bf0
git.frostfs.info/TrueCloudLab/hrw v1.2.1 git.frostfs.info/TrueCloudLab/hrw v1.2.1
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 git.frostfs.info/TrueCloudLab/tzhash v1.8.0

4
go.sum
View file

@ -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-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 h1:PaZ8GpnUoXxUoNsc1qp36bT2u7FU+neU4Jn9cl8AWqI=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65/go.mod h1:6aAX80dvJ3r5fjN9CzzPglRptoiPgIC9KFGGsUA+1Hw= 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-20240126141009-65b4525b3bf0 h1:jp/Cg6CF3ip0IVRCjt48qKyv1cbMUmNYTn2z1jFLWqc=
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/go.mod h1:t1akKcUH7iBrFHX8rSXScYMP17k2kYQXMbZooiL5Juw=
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc= 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/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad h1:+D96Uu0Pw+55CrxP+srazthtZAh0Q19BtJo1Pm4hOP0= git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240122104724-06cbfe8691ad h1:+D96Uu0Pw+55CrxP+srazthtZAh0Q19BtJo1Pm4hOP0=

View file

@ -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) return nil, fmt.Errorf("%w: %w: %c", policyengine.ErrChainNotFound, ErrTargetTypeBucketNotFound, target.Type)
} }
if target.Type == policyengine.Namespace && target.Name == "" {
target.Name = "root"
Review

Such renaming is actual only for local overrides or also for rules in contract?

Such renaming is actual only for local overrides or also for rules in contract?
Review

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

**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
}
rbucket := typeBucket.Bucket([]byte(target.Name)) rbucket := typeBucket.Bucket([]byte(target.Name))
if rbucket == nil { if rbucket == nil {
return nil, fmt.Errorf("%w: %w: %s", policyengine.ErrChainNotFound, ErrTargetNameBucketNotFound, target.Name) 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)) rbucket := typeBucket.Bucket([]byte(target.Name))
if rbucket == nil { if rbucket == nil {
var err error var err error

View file

@ -33,16 +33,17 @@ func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error)
return subj, nil return subj, nil
} }
// parseSubject from https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/dd5919348da9731f24504e7bc485516c2ba5f11c/frostfsid/client/client.go#L592 func parseSubject(res []stackitem.Item) (*frostfsidclient.Subject, error) {
func parseSubject(structArr []stackitem.Item) (*frostfsidclient.Subject, error) { if ln := len(res); ln != 1 {
if len(structArr) < 5 { return nil, fmt.Errorf("unexpected stack item count (%s): %d", methodGetSubject, ln)
return nil, errors.New("invalid response subject struct")
} }
var ( structArr, err := client.ArrayFromStackItem(res[0])
err error if err != nil {
subj frostfsidclient.Subject return nil, fmt.Errorf("could not get item array of container (%s): %w", methodGetSubject, err)
) }
var subj frostfsidclient.Subject
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0])) subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil { if err != nil {

View file

@ -8,14 +8,17 @@ import (
"crypto/sha256" "crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
@ -25,6 +28,7 @@ import (
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
) )
var ( var (
@ -35,6 +39,7 @@ var (
errInvalidSessionTokenOwner = errors.New("malformed request: invalid session token owner") errInvalidSessionTokenOwner = errors.New("malformed request: invalid session token owner")
errEmptyBodySignature = errors.New("malformed request: empty body signature") errEmptyBodySignature = errors.New("malformed request: empty body signature")
errMissingOwnerID = errors.New("malformed request: missing owner ID") errMissingOwnerID = errors.New("malformed request: missing owner ID")
errSubjectNotFound = errors.New("subject not found")
undefinedContainerID = cid.ID{} undefinedContainerID = cid.ID{}
) )
@ -47,22 +52,29 @@ type containers interface {
Get(cid.ID) (*containercore.Container, error) Get(cid.ID) (*containercore.Container, error)
} }
type frostfsidSubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
}
type apeChecker struct { type apeChecker struct {
router policyengine.ChainRouter router policyengine.ChainRouter
reader containers reader containers
ir ir ir ir
nm netmap.Source nm netmap.Source
frostFSIDClient frostfsidSubjectProvider
next Server next Server
} }
func NewAPEServer(router policyengine.ChainRouter, reader containers, ir ir, nm netmap.Source, srv Server) Server { func NewAPEServer(router policyengine.ChainRouter, reader containers, ir ir, nm netmap.Source, frostFSIDClient frostfsidSubjectProvider, srv Server) Server {
return &apeChecker{ return &apeChecker{
router: router, router: router,
reader: reader, reader: reader,
ir: ir, ir: ir,
next: srv, next: srv,
nm: nm, nm: nm,
frostFSIDClient: frostFSIDClient,
} }
} }
@ -125,9 +137,17 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
nativeschema.PropertyKeyActorRole: role, nativeschema.PropertyKeyActorRole: role,
} }
namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID())
if err != nil {
return nil, fmt.Errorf("could not get owner namespace: %w", err)
}
if err := ac.validateNamespaceByPublicKey(pk, namespace); err != nil {
return nil, err
}
request := &apeRequest{ request := &apeRequest{
resource: &apeResource{ resource: &apeResource{
name: nativeschema.ResourceFormatRootContainers, name: resourceName(namespace, ""),
props: make(map[string]string), props: make(map[string]string),
}, },
op: nativeschema.MethodListContainers, op: nativeschema.MethodListContainers,
@ -135,7 +155,7 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
} }
s, found, err := ac.router.IsAllowed(apechain.Ingress, s, found, err := ac.router.IsAllowed(apechain.Ingress,
policyengine.NewRequestTargetWithNamespace(""), policyengine.NewRequestTargetWithNamespace(namespace),
request) request)
if err != nil { if err != nil {
return nil, err return nil, err
@ -162,9 +182,17 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
nativeschema.PropertyKeyActorRole: role, nativeschema.PropertyKeyActorRole: role,
} }
namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID())
if err != nil {
return nil, fmt.Errorf("get namespace error: %w", err)
}
if err = validateNamespace(req.GetBody().GetContainer(), namespace); err != nil {
return nil, err
}
request := &apeRequest{ request := &apeRequest{
resource: &apeResource{ resource: &apeResource{
name: nativeschema.ResourceFormatRootContainers, name: resourceName(namespace, ""),
props: make(map[string]string), props: make(map[string]string),
}, },
op: nativeschema.MethodPutContainer, op: nativeschema.MethodPutContainer,
@ -172,7 +200,7 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
} }
s, found, err := ac.router.IsAllowed(apechain.Ingress, s, found, err := ac.router.IsAllowed(apechain.Ingress,
policyengine.NewRequestTargetWithNamespace(""), policyengine.NewRequestTargetWithNamespace(namespace),
request) request)
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,9 +279,15 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
return err return err
} }
namespace := ""
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cont.Value).Zone(), ".ns")
if hasNamespace {
namespace = cntNamespace
}
request := &apeRequest{ request := &apeRequest{
resource: &apeResource{ resource: &apeResource{
name: fmt.Sprintf(nativeschema.ResourceFormatRootContainer, id.EncodeToString()), name: resourceName(namespace, id.EncodeToString()),
props: ac.getContainerProps(cont), props: ac.getContainerProps(cont),
}, },
op: op, op: op,
@ -261,7 +295,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
} }
s, found, err := ac.router.IsAllowed(apechain.Ingress, s, found, err := ac.router.IsAllowed(apechain.Ingress,
policyengine.NewRequestTargetWithContainer(id.EncodeToString()), policyengine.NewRequestTarget(cntNamespace, id.EncodeToString()),
request) request)
if err != nil { if err != nil {
return err return err
@ -326,6 +360,19 @@ func (r *apeResource) Property(key string) string {
return r.props[key] return r.props[key]
} }
func resourceName(namespace string, container string) string {
if namespace == "" && container == "" {
return nativeschema.ResourceFormatRootContainers
}
if namespace == "" && container != "" {
return fmt.Sprintf(nativeschema.ResourceFormatRootContainer, container)
}
if namespace != "" && container == "" {
return fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, namespace)
}
return fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, namespace, container)
}
func (ac *apeChecker) getContainerProps(c *containercore.Container) map[string]string { func (ac *apeChecker) getContainerProps(c *containercore.Container) map[string]string {
return map[string]string{ return map[string]string{
nativeschema.PropertyKeyContainerOwnerID: c.Value.Owner().EncodeToString(), nativeschema.PropertyKeyContainerOwnerID: c.Value.Owner().EncodeToString(),
@ -514,3 +561,70 @@ func isContainerNode(nm *netmapSDK.NetMap, pk, binCnrID []byte, cont *containerc
return false, nil return false, nil
} }
func (ac *apeChecker) namespaceByOwner(owner *refs.OwnerID) (string, error) {
var ownerSDK user.ID
if owner == nil {
return "", fmt.Errorf("owner id is not set")
}
if err := ownerSDK.ReadFromV2(*owner); err != nil {
return "", err
}
addr, err := ownerSDK.ScriptHash()
if err != nil {
return "", err
}
namespace := ""
subject, err := ac.frostFSIDClient.GetSubject(addr)
if err == nil {
namespace = subject.Namespace
} else {
if !strings.Contains(err.Error(), errSubjectNotFound.Error()) {
return "", fmt.Errorf("get subject error: %w", err)
}
}
return namespace, nil
}
// validateNamespace validates a namespace set in a container.
// If frostfs-id contract stores a namespace N1 for an owner ID and a container within a request
// is set with namespace N2 (via Zone() property), then N2 is invalid and the request is denied.
func validateNamespace(cnrV2 *container.Container, ownerIDNamespace string) error {
if cnrV2 == nil {
return nil
}
var cnr cnrSDK.Container
if err := cnr.ReadFromV2(*cnrV2); err != nil {
return err
}
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr).Zone(), ".ns")
if hasNamespace {
if cntNamespace != ownerIDNamespace {
if ownerIDNamespace == "" {
return fmt.Errorf("invalid domain zone: no namespace is expected")
}
return fmt.Errorf("invalid domain zone: expected namespace %s, but got %s", ownerIDNamespace, cntNamespace)
}
} else if ownerIDNamespace != "" {
return fmt.Errorf("invalid domain zone: expected namespace %s, but got invalid or empty", ownerIDNamespace)
}
return nil
}
// validateNamespace validates if a namespace of a request actor equals to owner's namespace.
// An actor's namespace is calculated by a public key.
func (ac *apeChecker) validateNamespaceByPublicKey(pk *keys.PublicKey, ownerIDNamespace string) error {
var actor user.ID
user.IDFromKey(&actor, (ecdsa.PublicKey)(*pk))
actorOwnerID := new(refs.OwnerID)
actor.WriteToV2(actorOwnerID)
actorNamespace, err := ac.namespaceByOwner(actorOwnerID)
if err != nil {
return fmt.Errorf("could not get actor namespace: %w", err)
}
if actorNamespace != ownerIDNamespace {
return fmt.Errorf("actor namespace %s differs from owner: %s", actorNamespace, ownerIDNamespace)
}
return nil
}

View file

@ -2,6 +2,7 @@ package container
import ( import (
"context" "context"
"crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"testing" "testing"
@ -9,10 +10,12 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test" containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
@ -24,16 +27,25 @@ import (
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
const (
testDomainName = "testdomainname"
testDomainZone = "testdomainname.ns"
)
func TestAPE(t *testing.T) { func TestAPE(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("deny get container for others", testDenyGetContainerForOthers) t.Run("deny get container for others", testDenyGetContainerForOthers)
t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR) t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR)
t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken) t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken) t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken)
t.Run("deny put container, read namespace from frostfsID", testDenyPutContainerReadNamespaceFromFrostfsID)
t.Run("deny put container with invlaid namespace", testDenyPutContainerInvalidNamespace)
t.Run("deny list containers for owner with PK", testDenyListContainersForPK) t.Run("deny list containers for owner with PK", testDenyListContainersForPK)
t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError)
} }
func testDenyGetContainerForOthers(t *testing.T) { func testDenyGetContainerForOthers(t *testing.T) {
@ -49,7 +61,10 @@ func testDenyGetContainerForOthers(t *testing.T) {
keys: [][]byte{}, keys: [][]byte{},
} }
nm := &netmapStub{} nm := &netmapStub{}
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv) frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
contID := cidtest.ID() contID := cidtest.ID()
testContainer := containertest.Container() testContainer := containertest.Container()
@ -122,7 +137,10 @@ func testDenySetContainerEACLForIR(t *testing.T) {
keys: [][]byte{}, keys: [][]byte{},
} }
nm := &netmapStub{} nm := &netmapStub{}
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv) frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
contID := cidtest.ID() contID := cidtest.ID()
testContainer := containertest.Container() testContainer := containertest.Container()
@ -197,7 +215,10 @@ func testDenyGetContainerEACLForIRSessionToken(t *testing.T) {
keys: [][]byte{}, keys: [][]byte{},
} }
nm := &netmapStub{} nm := &netmapStub{}
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv) frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
contID := cidtest.ID() contID := cidtest.ID()
testContainer := containertest.Container() testContainer := containertest.Container()
@ -283,7 +304,10 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
keys: [][]byte{}, keys: [][]byte{},
} }
nm := &netmapStub{} nm := &netmapStub{}
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv) frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
testContainer := containertest.Container() testContainer := containertest.Container()
@ -317,26 +341,7 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
req := &container.PutRequest{} req := initPutRequest(t, testContainer)
req.SetBody(&container.PutRequestBody{})
var reqCont container.Container
testContainer.WriteToV2(&reqCont)
req.GetBody().SetContainer(&reqCont)
sessionPK, err := keys.NewPrivateKey()
require.NoError(t, err)
sToken := sessiontest.ContainerSigned()
sToken.ApplyOnlyTo(cid.ID{})
require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
var sTokenV2 session.Token
sToken.WriteToV2(&sTokenV2)
metaHeader := new(session.RequestMetaHeader)
metaHeader.SetSessionToken(&sTokenV2)
req.SetMetaHeader(metaHeader)
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
resp, err := apeSrv.Put(context.Background(), req) resp, err := apeSrv.Put(context.Background(), req)
require.Nil(t, resp) require.Nil(t, resp)
@ -344,6 +349,139 @@ func testDenyPutContainerForOthersSessionToken(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied) require.ErrorAs(t, err, &errAccessDenied)
} }
func testDenyPutContainerReadNamespaceFromFrostfsID(t *testing.T) {
t.Parallel()
srv := &srvStub{
calls: map[string]int{},
}
router := inmemory.NewInMemory()
contRdr := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
nm := &netmapStub{}
cnrID, testContainer := initTestContainer(t, true)
contRdr.c[cnrID] = &containercore.Container{Value: testContainer}
nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{}
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodPutContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initPutRequest(t, testContainer)
ownerScriptHash := initOwnerIDScriptHash(t, testContainer)
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
}
func testDenyPutContainerInvalidNamespace(t *testing.T) {
t.Parallel()
srv := &srvStub{
calls: map[string]int{},
}
router := inmemory.NewInMemory()
contRdr := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
nm := &netmapStub{}
cnrID, testContainer := initTestContainer(t, false)
var domain cnrSDK.Domain
domain.SetName("incorrect" + testDomainName)
domain.SetZone("incorrect" + testDomainZone)
cnrSDK.WriteDomain(&testContainer, domain)
contRdr.c[cnrID] = &containercore.Container{Value: testContainer}
nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{}
_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodPutContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initPutRequest(t, testContainer)
ownerScriptHash := initOwnerIDScriptHash(t, testContainer)
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req)
require.Nil(t, resp)
require.ErrorContains(t, err, "invalid domain zone")
}
func testDenyListContainersForPK(t *testing.T) { func testDenyListContainersForPK(t *testing.T) {
t.Parallel() t.Parallel()
srv := &srvStub{ srv := &srvStub{
@ -357,7 +495,10 @@ func testDenyListContainersForPK(t *testing.T) {
keys: [][]byte{}, keys: [][]byte{},
} }
nm := &netmapStub{} nm := &netmapStub{}
apeSrv := NewAPEServer(router, contRdr, ir, nm, srv) frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
nm.currentEpoch = 100 nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{} nm.netmaps = map[uint64]*netmap.NetMap{}
@ -409,6 +550,82 @@ func testDenyListContainersForPK(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied) require.ErrorAs(t, err, &errAccessDenied)
} }
func testDenyListContainersValidationNamespaceError(t *testing.T) {
t.Parallel()
srv := &srvStub{
calls: map[string]int{},
}
router := inmemory.NewInMemory()
contRdr := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
nm := &netmapStub{}
actorPK, err := keys.NewPrivateKey()
require.NoError(t, err)
ownerPK, err := keys.NewPrivateKey()
require.NoError(t, err)
actorScriptHash, ownerScriptHash := initActorOwnerScriptHashes(t, actorPK, ownerPK)
const actorDomain = "actor" + testDomainName
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{
actorScriptHash: {
Namespace: actorDomain,
Name: actorDomain,
},
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
nm.currentEpoch = 100
nm.netmaps = map[uint64]*netmap.NetMap{}
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodListContainers,
},
},
Resources: chain.Resources{
Names: []string{
nativeschema.ResourceFormatRootContainers,
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorPublicKey,
Value: actorPK.PublicKey().String(),
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initListRequest(t, actorPK, ownerPK)
resp, err := apeSrv.List(context.Background(), req)
require.Nil(t, resp)
require.ErrorContains(t, err, "actor namespace "+actorDomain+" differs")
}
type srvStub struct { type srvStub struct {
calls map[string]int calls map[string]int
} }
@ -489,3 +706,424 @@ func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) {
func (s *netmapStub) Epoch() (uint64, error) { func (s *netmapStub) Epoch() (uint64, error) {
return s.currentEpoch, nil return s.currentEpoch, nil
} }
type frostfsidStub struct {
subjects map[util.Uint160]*client.Subject
}
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
s, ok := f.subjects[owner]
if !ok {
return nil, errSubjectNotFound
}
return s, nil
}
type testAPEServer struct {
engine engine.Engine
containerReader *containerStub
ir *irStub
netmap *netmapStub
frostfsIDSubjectReader *frostfsidStub
apeChecker *apeChecker
}
func newTestAPEServer() testAPEServer {
srv := &srvStub{
calls: map[string]int{},
}
engine := inmemory.NewInMemory()
containerReader := &containerStub{
c: map[cid.ID]*containercore.Container{},
}
ir := &irStub{
keys: [][]byte{},
}
netmap := &netmapStub{}
frostfsIDSubjectReader := &frostfsidStub{
subjects: map[util.Uint160]*client.Subject{},
}
apeChecker := &apeChecker{
router: engine,
reader: containerReader,
ir: ir,
nm: netmap,
frostFSIDClient: frostfsIDSubjectReader,
next: srv,
}
return testAPEServer{
engine: engine,
containerReader: containerReader,
ir: ir,
netmap: netmap,
frostfsIDSubjectReader: frostfsIDSubjectReader,
apeChecker: apeChecker,
}
}
func TestValidateContainerBoundedOperation(t *testing.T) {
t.Parallel()
t.Run("check root-defined container in root-defined container target rule", func(t *testing.T) {
t.Parallel()
components := newTestAPEServer()
contID, testContainer := initTestContainer(t, false)
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
initTestNetmap(components.netmap)
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
require.ErrorContains(t, err, aErr.Error())
})
t.Run("check root-defined container in testdomain-defined container target rule", func(t *testing.T) {
t.Parallel()
components := newTestAPEServer()
contID, testContainer := initTestContainer(t, false)
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
initTestNetmap(components.netmap)
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
require.NoError(t, err)
})
t.Run("check root-defined container in testdomain namespace target rule", func(t *testing.T) {
t.Parallel()
components := newTestAPEServer()
contID, testContainer := initTestContainer(t, false)
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
initTestNetmap(components.netmap)
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
require.NoError(t, err)
})
t.Run("check testdomain-defined container in root-defined container target rule", func(t *testing.T) {
t.Parallel()
components := newTestAPEServer()
contID, testContainer := initTestContainer(t, true)
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
initTestNetmap(components.netmap)
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
require.NoError(t, err)
})
t.Run("check testdomain-defined container in testdomain-defined container target rule", func(t *testing.T) {
t.Parallel()
components := newTestAPEServer()
contID, testContainer := initTestContainer(t, true)
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
initTestNetmap(components.netmap)
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
require.ErrorContains(t, err, aErr.Error())
})
t.Run("check testdomain-defined container in testdomain namespace target rule", func(t *testing.T) {
t.Parallel()
components := newTestAPEServer()
contID, testContainer := initTestContainer(t, true)
components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
initTestNetmap(components.netmap)
_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{
Names: []string{
nativeschema.MethodGetContainer,
},
},
Resources: chain.Resources{
Names: []string{
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
},
},
Condition: []chain.Condition{
{
Object: chain.ObjectRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
Op: chain.CondStringEquals,
},
},
},
},
})
require.NoError(t, err)
req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
require.ErrorContains(t, err, aErr.Error())
})
}
func initTestGetContainerRequest(t *testing.T, contID cid.ID) *container.GetRequest {
req := &container.GetRequest{}
req.SetBody(&container.GetRequestBody{})
var refContID refs.ContainerID
contID.WriteToV2(&refContID)
req.GetBody().SetContainerID(&refContID)
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
return req
}
func initTestNetmap(netmapStub *netmapStub) {
netmapStub.currentEpoch = 100
netmapStub.netmaps = map[uint64]*netmap.NetMap{}
var testNetmap netmap.NetMap
testNetmap.SetEpoch(netmapStub.currentEpoch)
testNetmap.SetNodes([]netmap.NodeInfo{{}})
netmapStub.netmaps[netmapStub.currentEpoch] = &testNetmap
netmapStub.netmaps[netmapStub.currentEpoch-1] = &testNetmap
}
func initTestContainer(t *testing.T, isDomainSet bool) (cid.ID, cnrSDK.Container) {
contID := cidtest.ID()
testContainer := containertest.Container()
pp := netmap.PlacementPolicy{}
require.NoError(t, pp.DecodeString("REP 1"))
testContainer.SetPlacementPolicy(pp)
if isDomainSet {
// no domain defined -> container is defined in root namespace
var domain cnrSDK.Domain
domain.SetName(testDomainName)
domain.SetZone(testDomainZone)
cnrSDK.WriteDomain(&testContainer, domain)
}
return contID, testContainer
}
func initPutRequest(t *testing.T, testContainer cnrSDK.Container) *container.PutRequest {
req := &container.PutRequest{}
req.SetBody(&container.PutRequestBody{})
var reqCont container.Container
testContainer.WriteToV2(&reqCont)
req.GetBody().SetContainer(&reqCont)
sessionPK, err := keys.NewPrivateKey()
require.NoError(t, err)
sToken := sessiontest.ContainerSigned()
sToken.ApplyOnlyTo(cid.ID{})
require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
var sTokenV2 session.Token
sToken.WriteToV2(&sTokenV2)
metaHeader := new(session.RequestMetaHeader)
metaHeader.SetSessionToken(&sTokenV2)
req.SetMetaHeader(metaHeader)
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
return req
}
func initOwnerIDScriptHash(t *testing.T, testContainer cnrSDK.Container) util.Uint160 {
var ownerSDK *user.ID
owner := testContainer.Owner()
ownerSDK = &owner
sc, err := ownerSDK.ScriptHash()
require.NoError(t, err)
return sc
}
func initActorOwnerScriptHashes(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) (actorScriptHash util.Uint160, ownerScriptHash util.Uint160) {
var actorUserID user.ID
user.IDFromKey(&actorUserID, ecdsa.PublicKey(*actorPK.PublicKey()))
var err error
actorScriptHash, err = actorUserID.ScriptHash()
require.NoError(t, err)
var ownerUserID user.ID
user.IDFromKey(&ownerUserID, ecdsa.PublicKey(*ownerPK.PublicKey()))
ownerScriptHash, err = ownerUserID.ScriptHash()
require.NoError(t, err)
require.NotEqual(t, ownerScriptHash.String(), actorScriptHash.String())
return
}
func initListRequest(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) *container.ListRequest {
var ownerUserID user.ID
user.IDFromKey(&ownerUserID, ownerPK.PrivateKey.PublicKey)
req := &container.ListRequest{}
req.SetBody(&container.ListRequestBody{})
var ownerID refs.OwnerID
ownerUserID.WriteToV2(&ownerID)
req.GetBody().SetOwnerID(&ownerID)
require.NoError(t, signature.SignServiceMessage(&actorPK.PrivateKey, req))
return req
}

View file

@ -17,12 +17,7 @@ func apeTarget(chainTarget *control.ChainTarget) (engine.Target, error) {
case control.ChainTarget_CONTAINER: case control.ChainTarget_CONTAINER:
return engine.ContainerTarget(chainTarget.GetName()), nil return engine.ContainerTarget(chainTarget.GetName()), nil
case control.ChainTarget_NAMESPACE: case control.ChainTarget_NAMESPACE:
namespace := chainTarget.GetName() return engine.NamespaceTarget(chainTarget.GetName()), nil
// If namespace is empty, we take it for root namespace.
if namespace == "" {
namespace = "root"
}
return engine.NamespaceTarget(namespace), nil
default: default:
} }
return engine.Target{}, status.Error(codes.InvalidArgument, return engine.Target{}, status.Error(codes.InvalidArgument,

View file

@ -4,8 +4,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
containerV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
@ -14,6 +14,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
@ -744,7 +745,11 @@ func (b Service) findRequestInfo(req MetaWithToken, idCnr cid.ID, op acl.Op) (in
info.operation = op info.operation = op
info.cnrOwner = cnr.Value.Owner() info.cnrOwner = cnr.Value.Owner()
info.idCnr = idCnr info.idCnr = idCnr
info.cnrNamespace = cnr.Value.Attribute(containerV2.SysAttributeZone)
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
if hasNamespace {
info.cnrNamespace = cnrNamespace
}
// it is assumed that at the moment the key will be valid, // it is assumed that at the moment the key will be valid,
// otherwise the request would not pass validation // otherwise the request would not pass validation