[#937] ape: Validate chain resource name

Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
Anton Nikiforov 2024-01-30 17:07:05 +03:00
parent e3573de6db
commit 483a67b170
6 changed files with 259 additions and 18 deletions

View file

@ -0,0 +1,97 @@
package ape
import (
"errors"
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
)
var (
ErrInvalidResource = errors.New("invalid resource name")
ErrUnsupportedPrefix = errors.New("unsupported resource name prefix")
ErrInvalidContainerID = errors.New("invalid container id")
ErrInvalidObjectID = errors.New("invalid object id")
ErrInvalidNamespace = fmt.Errorf("namespace must match regexp: %s", ape.NamespaceNameRegexp.String())
)
// ValidateResourceName validates resource name components - container and object id, namespace.
// Also validates matching resource name to templates of policy engine's native scheme.
func ValidateResourceName(name string) error {
if after, found := strings.CutPrefix(name, native.ObjectPrefix+"/"); found {
return validateObjectResourceName(after)
} else if after, found = strings.CutPrefix(name, native.ContainerPrefix+"/"); found {
return validateContainerResourceName(after)
}
return ErrUnsupportedPrefix
}
// validateObjectResourceName validate name for object.
// Name should be without prefix `native.ObjectPrefix`.
func validateObjectResourceName(name string) error {
if name == "*" {
return nil
}
lexems := strings.Split(name, "/")
if len(lexems) == 1 && lexems[0] == "*" {
return nil
} else if len(lexems) == 2 {
// len == 2 means format `namespace(root_namespace)/*`
if lexems[0] != "" && !ape.NamespaceNameRegexp.MatchString(lexems[0]) {
return ErrInvalidNamespace
}
if lexems[1] == "*" {
return nil
}
} else if len(lexems) == 3 {
// len == 3 means format `namespace(root_namespace)/CID/OID(*)`
if lexems[0] != "" && !ape.NamespaceNameRegexp.MatchString(lexems[0]) {
return ErrInvalidNamespace
}
var cnr cid.ID
err := cnr.DecodeString(lexems[1])
if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidContainerID, err)
}
if lexems[2] == "*" {
return nil
}
var objID oid.ID
err = objID.DecodeString(lexems[2])
if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidObjectID, err)
}
return nil
}
return ErrInvalidResource
}
// validateContainerResourceName validate resource name for container.
// Name should be without prefix `native.ContainerPrefix`.
func validateContainerResourceName(name string) error {
if name == "*" {
return nil
}
lexems := strings.Split(name, "/")
if len(lexems) == 1 && lexems[0] == "*" {
return nil
} else if len(lexems) == 2 {
// len == 2 means format `namespace(root_namespace)/CID(*)`
if lexems[0] != "" && !ape.NamespaceNameRegexp.MatchString(lexems[0]) {
return ErrInvalidNamespace
}
if lexems[1] != "*" {
var cnr cid.ID
err := cnr.DecodeString(lexems[1])
if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidContainerID, err)
}
}
return nil
}
return ErrInvalidResource
}

View file

@ -0,0 +1,132 @@
package ape
import (
"testing"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/stretchr/testify/require"
)
func TestValidationOfChainResources(t *testing.T) {
tests := [...]struct {
testName string
resourceName string
expectErr error
}{
{
testName: "native object: all objects",
resourceName: native.ObjectPrefix + "/*",
},
{
testName: "native object: all objects in namespace",
resourceName: native.ObjectPrefix + "/ns/*",
},
{
testName: "native object: all objects in root namespace",
resourceName: native.ObjectPrefix + "//*",
},
{
testName: "native object: all objects in namespace/container",
resourceName: native.ObjectPrefix + "/ns/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH/*",
},
{
testName: "native object: all objects in root namespace/container",
resourceName: native.ObjectPrefix + "//SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH/*",
},
{
testName: "native object: object in namespace/container",
resourceName: native.ObjectPrefix + "/ns/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH/BCGsUu6o92oG1UALVox1sV6YbBUKUL2xSCtAFkrsuvWY",
},
{
testName: "native object: object in root namespace/container",
resourceName: native.ObjectPrefix + "//SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH/BCGsUu6o92oG1UALVox1sV6YbBUKUL2xSCtAFkrsuvWY",
},
{
testName: "native object: invalid all objects",
resourceName: native.ObjectPrefix + "/*12313",
expectErr: ErrInvalidResource,
},
{
testName: "native object: all objects in invalid namespace",
resourceName: native.ObjectPrefix + "/qwe_123123/*",
expectErr: ErrInvalidNamespace,
},
{
testName: "native object: invalid all objects in root namespace",
resourceName: native.ObjectPrefix + "//qwe",
expectErr: ErrInvalidResource,
},
{
testName: "native object: invalid cid in all objects in root namespace",
resourceName: native.ObjectPrefix + "//SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytHqwe/*",
expectErr: ErrInvalidContainerID,
},
{
testName: "native object: invalid cid in all objects in namespace",
resourceName: native.ObjectPrefix + "/ns/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytHqwe/*",
expectErr: ErrInvalidContainerID,
},
{
testName: "native object: invalid object in namespace/container",
resourceName: native.ObjectPrefix + "/ns/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH/BCGsUu6o92oG1UALVox1sV6YbBUKUL2xSCtAFkrsuvWY111",
expectErr: ErrInvalidObjectID,
},
{
testName: "native object: invalid resource",
resourceName: native.ObjectPrefix + "/ns/SeHNpifD/AFkrsuvWY111/AFkrsuvWY222",
expectErr: ErrInvalidResource,
},
{
testName: "native container: all containers",
resourceName: native.ContainerPrefix + "/*",
},
{
testName: "native container: all containers in namespace",
resourceName: native.ContainerPrefix + "/ns/*",
},
{
testName: "native container: all containers in root namespace",
resourceName: native.ContainerPrefix + "//*",
},
{
testName: "native container: container in namespace",
resourceName: native.ContainerPrefix + "/ns/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH",
},
{
testName: "native container: container in root namespace",
resourceName: native.ContainerPrefix + "//SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH",
},
{
testName: "native container: invalid all containers",
resourceName: native.ContainerPrefix + "/*asd",
expectErr: ErrInvalidResource,
},
{
testName: "native container: invalid resource",
resourceName: native.ContainerPrefix + "/ns/cid/cid",
expectErr: ErrInvalidResource,
},
{
testName: "native container: invalid container in root namespace",
resourceName: native.ContainerPrefix + "//*asd",
expectErr: ErrInvalidContainerID,
},
{
testName: "native container: container in invalid namespace",
resourceName: native.ContainerPrefix + "/ns_111/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH",
expectErr: ErrInvalidNamespace,
},
{
testName: "unsupported prefix",
resourceName: "native:test/ns_111/SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH",
expectErr: ErrUnsupportedPrefix,
},
}
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
err := ValidateResourceName(test.resourceName)
require.ErrorIs(t, err, test.expectErr)
})
}
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/server/ape"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"google.golang.org/grpc/codes"
@ -38,6 +39,13 @@ func (s *Server) AddChainLocalOverride(_ context.Context, req *control.AddChainL
if err := chain.DecodeBytes(req.GetBody().GetChain()); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
for _, rule := range chain.Rules {
for _, name := range rule.Resources.Names {
if err := ape.ValidateResourceName(name); err != nil {
return nil, status.Error(codes.InvalidArgument, fmt.Errorf("invalid resource: %w", err).Error())
}
}
}
s.apeChainCounter.Add(1)
// TODO (aarifullin): the such chain id is not well-designed yet.