Compare commits
1 commit
master
...
feat/trees
Author | SHA1 | Date | |
---|---|---|---|
7f88e81dd4 |
6 changed files with 282 additions and 288 deletions
2
go.mod
2
go.mod
|
@ -2,6 +2,8 @@ module git.frostfs.info/TrueCloudLab/frostfs-node
|
||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
|
replace git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240513163744-1f6f4163d40d => git.frostfs.info/aarifullin/policy-engine v0.0.0-20240617153626-ce600902773a
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.17.1
|
code.gitea.io/sdk/gitea v0.17.1
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240530152826-2f6d3209e1d3
|
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240530152826-2f6d3209e1d3
|
||||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
|
@ -9,7 +9,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/converter"
|
|
||||||
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
|
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/router"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/router"
|
||||||
core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
|
@ -18,7 +17,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
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"
|
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"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
|
@ -39,20 +37,13 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) newAPERequest(ctx context.Context, namespace string,
|
func (s *Service) newAPERequest(ctx context.Context, namespace string,
|
||||||
cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
|
cid cid.ID, treeID, schemaMethod, schemaRole string, publicKey *keys.PublicKey,
|
||||||
) (aperequest.Request, error) {
|
) (aperequest.Request, error) {
|
||||||
schemaMethod, err := converter.SchemaMethodFromACLOperation(operation)
|
|
||||||
if err != nil {
|
|
||||||
return aperequest.Request{}, err
|
|
||||||
}
|
|
||||||
schemaRole, err := converter.SchemaRoleFromACLRole(role)
|
|
||||||
if err != nil {
|
|
||||||
return aperequest.Request{}, err
|
|
||||||
}
|
|
||||||
reqProps := map[string]string{
|
reqProps := map[string]string{
|
||||||
nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(publicKey.Bytes()),
|
nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(publicKey.Bytes()),
|
||||||
nativeschema.PropertyKeyActorRole: schemaRole,
|
nativeschema.PropertyKeyActorRole: schemaRole,
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
reqProps, err = s.fillWithUserClaimTags(reqProps, publicKey)
|
reqProps, err = s.fillWithUserClaimTags(reqProps, publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return aperequest.Request{}, err
|
return aperequest.Request{}, err
|
||||||
|
@ -65,9 +56,9 @@ func (s *Service) newAPERequest(ctx context.Context, namespace string,
|
||||||
|
|
||||||
var resourceName string
|
var resourceName string
|
||||||
if namespace == "root" || namespace == "" {
|
if namespace == "root" || namespace == "" {
|
||||||
resourceName = fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cid.EncodeToString())
|
resourceName = fmt.Sprintf(nativeschema.ResourceFormatRootContainerTree, cid.EncodeToString(), treeID)
|
||||||
} else {
|
} else {
|
||||||
resourceName = fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObjects, namespace, cid.EncodeToString())
|
resourceName = fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerTree, namespace, cid.EncodeToString(), treeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return aperequest.NewRequest(
|
return aperequest.NewRequest(
|
||||||
|
@ -126,29 +117,37 @@ func isValidBearer(token *bearer.Token, ownerCnr user.ID, cntID cid.ID, publicKe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
|
type checkAPEPrm struct {
|
||||||
container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
|
bearerToken *bearer.Token
|
||||||
) error {
|
container *core.Container
|
||||||
|
cid cid.ID
|
||||||
|
method string
|
||||||
|
role string
|
||||||
|
treeID string
|
||||||
|
publicKey *keys.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) checkAPE(ctx context.Context, prm checkAPEPrm) error {
|
||||||
namespace := ""
|
namespace := ""
|
||||||
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns")
|
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(prm.container.Value).Zone(), ".ns")
|
||||||
if hasNamespace {
|
if hasNamespace {
|
||||||
namespace = cntNamespace
|
namespace = cntNamespace
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := s.newAPERequest(ctx, namespace, cid, operation, role, publicKey)
|
request, err := s.newAPERequest(ctx, namespace, prm.cid, prm.treeID, prm.method, prm.role, prm.publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apeErr(err)
|
return apeErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bt != nil && !bt.Impersonate() {
|
if prm.bearerToken != nil && !prm.bearerToken.Impersonate() {
|
||||||
if err := isValidBearer(bt, container.Value.Owner(), cid, publicKey, s.state); err != nil {
|
if err := isValidBearer(prm.bearerToken, prm.container.Value.Owner(), prm.cid, prm.publicKey, s.state); err != nil {
|
||||||
return fmt.Errorf("bearer validation error: %w", err)
|
return fmt.Errorf("bearer validation error: %w", err)
|
||||||
}
|
}
|
||||||
btRouter, err := router.SingleUseRouterWithBearerTokenChains([]bearer.APEOverride{bt.APEOverride()})
|
btRouter, err := router.SingleUseRouterWithBearerTokenChains([]bearer.APEOverride{prm.bearerToken.APEOverride()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apeErr(err)
|
return apeErr(err)
|
||||||
}
|
}
|
||||||
status, found, err := btRouter.IsAllowed(apechain.Ingress, engine.NewRequestTargetWithContainer(cid.EncodeToString()), request)
|
status, found, err := btRouter.IsAllowed(apechain.Ingress, engine.NewRequestTargetWithContainer(prm.cid.EncodeToString()), request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apeErr(err)
|
return apeErr(err)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +160,7 @@ func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := engine.NewRequestTargetExtended(namespace, cid.EncodeToString(), fmt.Sprintf("%s:%s", namespace, publicKey.Address()), nil)
|
rt := engine.NewRequestTargetExtended(namespace, prm.cid.EncodeToString(), fmt.Sprintf("%s:%s", namespace, prm.publicKey.Address()), nil)
|
||||||
status, found, err := s.router.IsAllowed(apechain.Ingress, rt, request)
|
status, found, err := s.router.IsAllowed(apechain.Ingress, rt, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apeErr(err)
|
return apeErr(err)
|
||||||
|
|
|
@ -11,9 +11,9 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
||||||
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cidSDK "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"
|
||||||
|
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||||
"github.com/panjf2000/ants/v2"
|
"github.com/panjf2000/ants/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -105,7 +105,7 @@ func (s *Service) Add(ctx context.Context, req *AddRequest) (*AddResponse, error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), nativeschema.MethodAddTreeNode, b.GetTreeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByP
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), nativeschema.MethodAddTreeNodeByPath, b.GetTreeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveRespon
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), nativeschema.MethodRemoveTreeNode, b.GetTreeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, er
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), nativeschema.MethodMoveTreeNode, b.GetTreeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ func (s *Service) GetNodeByPath(ctx context.Context, req *GetNodeByPathRequest)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectGet)
|
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), nativeschema.MethodGetTreeNodeByPath, b.GetTreeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -408,7 +408,7 @@ func (s *Service) GetSubTree(req *GetSubTreeRequest, srv TreeService_GetSubTreeS
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(srv.Context(), req, cid, b.GetBearerToken(), acl.OpObjectGet)
|
err := s.verifyClient(srv.Context(), req, cid, b.GetBearerToken(), nativeschema.MethodGetSubtree, b.GetTreeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
|
||||||
core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
||||||
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
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"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type message interface {
|
type message interface {
|
||||||
|
@ -30,32 +26,18 @@ type message interface {
|
||||||
SetSignature(*Signature)
|
SetSignature(*Signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
func basicACLErr(op acl.Op) error {
|
|
||||||
return fmt.Errorf("access to operation %s is denied by basic ACL check", op)
|
|
||||||
}
|
|
||||||
|
|
||||||
func eACLErr(op eacl.Operation, err error) error {
|
|
||||||
return fmt.Errorf("access to operation %s is denied by extended ACL check: %w", op, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errBearerWrongOwner = errors.New("bearer token must be signed by the container owner")
|
|
||||||
errBearerWrongContainer = errors.New("bearer token is created for another container")
|
|
||||||
errBearerSignature = errors.New("invalid bearer token signature")
|
|
||||||
)
|
|
||||||
|
|
||||||
// verifyClient verifies if the request for a client operation
|
// verifyClient verifies if the request for a client operation
|
||||||
// was signed by a key allowed by (e)ACL rules.
|
// was signed by a key allowed by (e)ACL rules.
|
||||||
// Operation must be one of:
|
// Operation must be one of:
|
||||||
// - 1. ObjectPut;
|
// - 1. ObjectPut;
|
||||||
// - 2. ObjectGet.
|
// - 2. ObjectGet.
|
||||||
func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID, rawBearer []byte, op acl.Op) error {
|
func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID, rawBearer []byte, method, treeID string) error {
|
||||||
err := verifyMessage(req)
|
err := verifyMessage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
isAuthorized, err := s.isAuthorized(req, op)
|
isAuthorized, err := s.isAuthorized(req, method)
|
||||||
if isAuthorized || err != nil {
|
if isAuthorized || err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -65,75 +47,36 @@ func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID,
|
||||||
return fmt.Errorf("can't get container %s: %w", cid, err)
|
return fmt.Errorf("can't get container %s: %w", cid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
eaclOp := eACLOp(op)
|
bt, err := parseBearer(rawBearer)
|
||||||
|
|
||||||
bt, err := parseBearer(rawBearer, cid, eaclOp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
role, pubKey, err := roleAndPubKeyFromReq(cnr, req, bt)
|
role, pubKey, err := roleAndPubKeyFromReq(cnr, req, bt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get request role: %w", err)
|
return fmt.Errorf("can't get role and public key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
basicACL := cnr.Value.BasicACL()
|
prm := checkAPEPrm{
|
||||||
// Basic ACL mask can be unset, if a container operations are performed
|
bearerToken: bt,
|
||||||
// with strict APE checks only.
|
container: cnr,
|
||||||
//
|
cid: cid,
|
||||||
// FIXME(@aarifullin): tree service temporiraly performs APE checks on
|
method: method,
|
||||||
// object verbs, because tree verbs have not been introduced yet.
|
role: role,
|
||||||
if basicACL == 0x0 {
|
treeID: treeID,
|
||||||
return s.checkAPE(ctx, bt, cnr, cid, op, role, pubKey)
|
publicKey: pubKey,
|
||||||
}
|
}
|
||||||
|
return s.checkAPE(ctx, prm)
|
||||||
if !basicACL.IsOpAllowed(op, role) {
|
|
||||||
return basicACLErr(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !basicACL.Extendable() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var useBearer bool
|
|
||||||
if len(rawBearer) != 0 {
|
|
||||||
if !basicACL.AllowedBearerRules(op) {
|
|
||||||
s.log.Debug(logs.TreeBearerPresentedButNotAllowedByACL,
|
|
||||||
zap.String("cid", cid.EncodeToString()),
|
|
||||||
zap.Stringer("op", op),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
useBearer = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tb eacl.Table
|
|
||||||
signer := req.GetSignature().GetKey()
|
|
||||||
if useBearer && !bt.Impersonate() {
|
|
||||||
if !bearer.ResolveIssuer(*bt).Equals(cnr.Value.Owner()) {
|
|
||||||
return eACLErr(eaclOp, errBearerWrongOwner)
|
|
||||||
}
|
|
||||||
tb = bt.EACLTable()
|
|
||||||
} else {
|
|
||||||
tbCore, err := s.eaclSource.GetEACL(cid)
|
|
||||||
if err != nil {
|
|
||||||
return handleGetEACLError(err)
|
|
||||||
}
|
|
||||||
tb = *tbCore.Value
|
|
||||||
|
|
||||||
if useBearer && bt.Impersonate() {
|
|
||||||
signer = bt.SigningKeyBytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkEACL(tb, signer, eACLRole(role), eaclOp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true iff the operation is read-only and request was signed
|
// Returns true if the operation is read-only and request was signed
|
||||||
// with one of the authorized keys.
|
// with one of the authorized keys.
|
||||||
func (s *Service) isAuthorized(req message, op acl.Op) (bool, error) {
|
func (s *Service) isAuthorized(req message, method string) (bool, error) {
|
||||||
if op != acl.OpObjectGet {
|
switch method {
|
||||||
|
default:
|
||||||
|
// Mutable tree operations are never authorized and thus must be checked anyway.
|
||||||
return false, nil
|
return false, nil
|
||||||
|
case nativeschema.MethodGetTreeNodeByPath, nativeschema.MethodGetSubtree, nativeschema.MethodListTrees:
|
||||||
}
|
}
|
||||||
|
|
||||||
sign := req.GetSignature()
|
sign := req.GetSignature()
|
||||||
|
@ -150,32 +93,18 @@ func (s *Service) isAuthorized(req message, op acl.Op) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBearer(rawBearer []byte, cid cidSDK.ID, eaclOp eacl.Operation) (*bearer.Token, error) {
|
func parseBearer(rawBearer []byte) (*bearer.Token, error) {
|
||||||
if len(rawBearer) == 0 {
|
if len(rawBearer) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bt := new(bearer.Token)
|
bt := new(bearer.Token)
|
||||||
if err := bt.Unmarshal(rawBearer); err != nil {
|
if err := bt.Unmarshal(rawBearer); err != nil {
|
||||||
return nil, eACLErr(eaclOp, fmt.Errorf("invalid bearer token: %w", err))
|
return nil, fmt.Errorf("invalid bearer token: %w", err)
|
||||||
}
|
|
||||||
if !bt.AssertContainer(cid) {
|
|
||||||
return nil, eACLErr(eaclOp, errBearerWrongContainer)
|
|
||||||
}
|
|
||||||
if !bt.VerifySignature() {
|
|
||||||
return nil, eACLErr(eaclOp, errBearerSignature)
|
|
||||||
}
|
}
|
||||||
return bt, nil
|
return bt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetEACLError(err error) error {
|
|
||||||
if client.IsErrEACLNotFound(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("get eACL table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyMessage(m message) error {
|
func verifyMessage(m message) error {
|
||||||
binBody, err := m.ReadSignedData(nil)
|
binBody, err := m.ReadSignedData(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -226,8 +155,8 @@ func SignMessage(m message, key *ecdsa.PrivateKey) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func roleAndPubKeyFromReq(cnr *core.Container, req message, bt *bearer.Token) (acl.Role, *keys.PublicKey, error) {
|
func roleAndPubKeyFromReq(cnr *core.Container, req message, bt *bearer.Token) (string, *keys.PublicKey, error) {
|
||||||
role := acl.RoleOthers
|
role := nativeschema.PropertyValueContainerRoleOthers
|
||||||
owner := cnr.Value.Owner()
|
owner := cnr.Value.Owner()
|
||||||
|
|
||||||
rawKey := req.GetSignature().GetKey()
|
rawKey := req.GetSignature().GetKey()
|
||||||
|
@ -244,89 +173,8 @@ func roleAndPubKeyFromReq(cnr *core.Container, req message, bt *bearer.Token) (a
|
||||||
user.IDFromKey(&reqSigner, (ecdsa.PublicKey)(*pub))
|
user.IDFromKey(&reqSigner, (ecdsa.PublicKey)(*pub))
|
||||||
|
|
||||||
if reqSigner.Equals(owner) {
|
if reqSigner.Equals(owner) {
|
||||||
role = acl.RoleOwner
|
role = nativeschema.PropertyValueContainerRoleOwner
|
||||||
}
|
}
|
||||||
|
|
||||||
return role, pub, nil
|
return role, pub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func eACLOp(op acl.Op) eacl.Operation {
|
|
||||||
switch op {
|
|
||||||
case acl.OpObjectGet:
|
|
||||||
return eacl.OperationGet
|
|
||||||
case acl.OpObjectPut:
|
|
||||||
return eacl.OperationPut
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unexpected tree service ACL operation: %s", op))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eACLRole(role acl.Role) eacl.Role {
|
|
||||||
switch role {
|
|
||||||
case acl.RoleOwner:
|
|
||||||
return eacl.RoleUser
|
|
||||||
case acl.RoleOthers:
|
|
||||||
return eacl.RoleOthers
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unexpected tree service ACL role: %s", role))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errDENY = errors.New("DENY eACL rule")
|
|
||||||
errNoAllowRules = errors.New("not found allowing rules for the request")
|
|
||||||
)
|
|
||||||
|
|
||||||
// checkEACL searches for the eACL rules that could be applied to the request
|
|
||||||
// (a tuple of a signer key, his FrostFS role and a request operation).
|
|
||||||
// It does not filter the request by the filters of the eACL table since tree
|
|
||||||
// requests do not contain any "object" information that could be filtered and,
|
|
||||||
// therefore, filtering leads to unexpected results.
|
|
||||||
// The code was copied with the minor updates from the SDK repo:
|
|
||||||
// https://github.com/nspcc-dev/frostfs-sdk-go/blob/43a57d42dd50dc60465bfd3482f7f12bcfcf3411/eacl/validator.go#L28.
|
|
||||||
func checkEACL(tb eacl.Table, signer []byte, role eacl.Role, op eacl.Operation) error {
|
|
||||||
for _, record := range tb.Records() {
|
|
||||||
// check type of operation
|
|
||||||
if record.Operation() != op {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check target
|
|
||||||
if !targetMatches(record, role, signer) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch a := record.Action(); a {
|
|
||||||
case eacl.ActionAllow:
|
|
||||||
return nil
|
|
||||||
case eacl.ActionDeny:
|
|
||||||
return eACLErr(op, errDENY)
|
|
||||||
default:
|
|
||||||
return eACLErr(op, fmt.Errorf("unexpected action: %s", a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return eACLErr(op, errNoAllowRules)
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetMatches(rec eacl.Record, role eacl.Role, signer []byte) bool {
|
|
||||||
for _, target := range rec.Targets() {
|
|
||||||
// check public key match
|
|
||||||
if pubs := target.BinaryKeys(); len(pubs) != 0 {
|
|
||||||
for _, key := range pubs {
|
|
||||||
if bytes.Equal(key, signer) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check target group match
|
|
||||||
if role == target.Role() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,22 +4,31 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
aclV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
aclV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||||
|
"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"
|
||||||
|
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
"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"
|
||||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||||
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,6 +90,25 @@ func testContainer(owner user.ID) container.Container {
|
||||||
return cnt
|
return cnt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyFrostfsIDSubjProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ frostfsidcore.SubjectProvider = (*dummyFrostfsIDSubjProvider)(nil)
|
||||||
|
|
||||||
|
func (p *dummyFrostfsIDSubjProvider) GetSubject(addr util.Uint160) (*client.Subject, error) {
|
||||||
|
return &client.Subject{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyFrostfsIDSubjProvider) GetSubjectExtended(addr util.Uint160) (*client.SubjectExtended, error) {
|
||||||
|
return &client.SubjectExtended{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyState struct{}
|
||||||
|
|
||||||
|
func (d *dummyState) CurrentEpoch() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func TestMessageSign(t *testing.T) {
|
func TestMessageSign(t *testing.T) {
|
||||||
privs := make([]*keys.PrivateKey, 4)
|
privs := make([]*keys.PrivateKey, 4)
|
||||||
for i := range privs {
|
for i := range privs {
|
||||||
|
@ -99,6 +127,9 @@ func TestMessageSign(t *testing.T) {
|
||||||
Value: testContainer(ownerID),
|
Value: testContainer(ownerID),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStorageChainMock := inmemory.NewInmemoryLocalStorage()
|
||||||
|
morphStorageChainMock := inmemory.NewInmemoryMorphRuleChainStorage()
|
||||||
|
|
||||||
s := &Service{
|
s := &Service{
|
||||||
cfg: cfg{
|
cfg: cfg{
|
||||||
log: test.NewLogger(t),
|
log: test.NewLogger(t),
|
||||||
|
@ -107,11 +138,10 @@ func TestMessageSign(t *testing.T) {
|
||||||
cnrSource: dummyContainerSource{
|
cnrSource: dummyContainerSource{
|
||||||
cid1.String(): cnr,
|
cid1.String(): cnr,
|
||||||
},
|
},
|
||||||
eaclSource: dummyEACLSource{
|
state: &dummyState{},
|
||||||
cid1.String(): &containercore.EACL{
|
eaclSource: dummyEACLSource{},
|
||||||
Value: testTable(cid1, privs[0].PublicKey(), privs[1].PublicKey()),
|
router: policyengine.NewDefaultChainRouterWithLocalOverrides(morphStorageChainMock, localStorageChainMock),
|
||||||
},
|
frostfsidSubjectProvider: &dummyFrostfsIDSubjProvider{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,61 +159,103 @@ func TestMessageSign(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
op := acl.OpObjectPut
|
const treeID = "version"
|
||||||
cnr.Value.SetBasicACL(acl.PublicRW)
|
|
||||||
|
|
||||||
t.Run("missing signature, no panic", func(t *testing.T) {
|
t.Run("missing signature, no panic", func(t *testing.T) {
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
|
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, nativeschema.MethodMoveTreeNode, treeID))
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, nil, op))
|
|
||||||
|
|
||||||
t.Run("invalid CID", func(t *testing.T) {
|
t.Run("deprecated basic ACL scheme", func(t *testing.T) {
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
|
t.Run("zero-filled bACL", func(t *testing.T) {
|
||||||
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, nil, nativeschema.MethodMoveTreeNode, treeID))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-zero-filled bACL", func(t *testing.T) {
|
||||||
|
cnr.Value.SetBasicACL(acl.PublicRW)
|
||||||
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, nil, nativeschema.MethodMoveTreeNode, treeID))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
cnr.Value.SetBasicACL(acl.Private)
|
localStorageChainMock.AddOverride(chain.Ingress, policyengine.ContainerTarget(cid1.EncodeToString()), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.Allow,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{nativeschema.MethodMoveTreeNode},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.ResourceFormatRootTrees,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOwner,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||||
|
Value: hex.EncodeToString(privs[0].PublicKey().Bytes()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Status: chain.Allow,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{nativeschema.MethodGetSubtree},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.ResourceFormatRootTrees,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("extension disabled", func(t *testing.T) {
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, nil, nativeschema.MethodMoveTreeNode, treeID))
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
|
t.Run("invalid CID", func(t *testing.T) {
|
||||||
|
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, nativeschema.MethodMoveTreeNode, treeID))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid key", func(t *testing.T) {
|
t.Run("invalid key", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, nil, op))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, nil, nativeschema.MethodMoveTreeNode, treeID))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bearer", func(t *testing.T) {
|
t.Run("bearer", func(t *testing.T) {
|
||||||
bACL := acl.PrivateExtended
|
|
||||||
bACL.AllowBearerRules(op)
|
|
||||||
cnr.Value.SetBasicACL(bACL)
|
|
||||||
|
|
||||||
bACL.DisableExtension()
|
|
||||||
|
|
||||||
t.Run("invalid bearer", func(t *testing.T) {
|
t.Run("invalid bearer", func(t *testing.T) {
|
||||||
req.Body.BearerToken = []byte{0xFF}
|
req.Body.BearerToken = []byte{0xDE, 0xAD, 0xBE, 0xEF}
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.ErrorContains(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID), "cannot parse invalid wire-format data")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid bearer CID", func(t *testing.T) {
|
t.Run("invalid bearer CID", func(t *testing.T) {
|
||||||
bt := testBearerToken(cid2, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid2, privs[1].PublicKey(), privs[2].PublicKey()) //testBearerToken(cid2, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
require.NoError(t, bt.Sign(privs[0].PrivateKey))
|
require.NoError(t, bt.Sign(privs[0].PrivateKey))
|
||||||
req.Body.BearerToken = bt.Marshal()
|
req.Body.BearerToken = bt.Marshal()
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.ErrorContains(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID), errBearerInvalidContainerID.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid bearer owner", func(t *testing.T) {
|
t.Run("invalid bearer owner", func(t *testing.T) {
|
||||||
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey()) //testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
require.NoError(t, bt.Sign(privs[1].PrivateKey))
|
require.NoError(t, bt.Sign(privs[1].PrivateKey))
|
||||||
req.Body.BearerToken = bt.Marshal()
|
req.Body.BearerToken = bt.Marshal()
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.ErrorContains(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID), errBearerNotSignedByOwner.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid bearer signature", func(t *testing.T) {
|
t.Run("invalid bearer signature", func(t *testing.T) {
|
||||||
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
require.NoError(t, bt.Sign(privs[0].PrivateKey))
|
require.NoError(t, bt.Sign(privs[0].PrivateKey))
|
||||||
|
@ -194,20 +266,20 @@ func TestMessageSign(t *testing.T) {
|
||||||
req.Body.BearerToken = bv2.StableMarshal(nil)
|
req.Body.BearerToken = bv2.StableMarshal(nil)
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.ErrorContains(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID), errBearerInvalidSignature.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("impersonate", func(t *testing.T) {
|
t.Run("impersonate", func(t *testing.T) {
|
||||||
cnr.Value.SetBasicACL(acl.PublicRWExtended)
|
|
||||||
var bt bearer.Token
|
var bt bearer.Token
|
||||||
|
|
||||||
bt.SetImpersonate(true)
|
bt.SetImpersonate(true)
|
||||||
|
|
||||||
require.NoError(t, bt.Sign(privs[1].PrivateKey))
|
require.NoError(t, bt.Sign(privs[1].PrivateKey))
|
||||||
req.Body.BearerToken = bt.Marshal()
|
|
||||||
|
|
||||||
|
req.Body.BearerToken = bt.Marshal()
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.ErrorContains(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID), "access to object operation denied")
|
||||||
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodGetSubtree, treeID))
|
||||||
})
|
})
|
||||||
|
|
||||||
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
|
@ -217,64 +289,137 @@ func TestMessageSign(t *testing.T) {
|
||||||
|
|
||||||
t.Run("put and get", func(t *testing.T) {
|
t.Run("put and get", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodGetSubtree, treeID))
|
||||||
})
|
})
|
||||||
t.Run("only get", func(t *testing.T) {
|
t.Run("only get", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[2].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[2].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodGetSubtree, treeID))
|
||||||
})
|
})
|
||||||
t.Run("none", func(t *testing.T) {
|
t.Run("none", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[3].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[3].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodMoveTreeNode, treeID))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), nativeschema.MethodGetSubtree, treeID))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBearerToken(cid cid.ID, forPutGet, forGet *keys.PublicKey) bearer.Token {
|
func testBearerToken(cid cid.ID, forPutGet, forGet *keys.PublicKey) bearer.Token {
|
||||||
var b bearer.Token
|
var b bearer.Token
|
||||||
b.SetEACLTable(*testTable(cid, forPutGet, forGet))
|
b.SetExp(100)
|
||||||
|
|
||||||
|
allowsGetOpForAnyRoleForBothKeys := chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.Allow,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{nativeschema.MethodGetTreeNodeByPath,
|
||||||
|
nativeschema.MethodGetSubtree,
|
||||||
|
nativeschema.MethodListTrees},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.ResourceFormatRootTrees,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Op: chain.CondSliceContains,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||||
|
Value: fmt.Sprintf("%s\x00%s", hex.EncodeToString(forPutGet.Bytes()), hex.EncodeToString(forGet.Bytes())),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
allowsPutOpForAnyRoleForOneKeyOnly := chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.Allow,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{nativeschema.MethodAddTreeNode,
|
||||||
|
nativeschema.MethodAddTreeNodeByPath,
|
||||||
|
nativeschema.MethodRemoveTreeNode,
|
||||||
|
nativeschema.MethodMoveTreeNode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.ResourceFormatRootTrees,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||||
|
Value: hex.EncodeToString(forPutGet.Bytes()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deniesGetPutOpForOthers := chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.AccessDenied,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{nativeschema.MethodAddTreeNode,
|
||||||
|
nativeschema.MethodAddTreeNodeByPath,
|
||||||
|
nativeschema.MethodRemoveTreeNode,
|
||||||
|
nativeschema.MethodMoveTreeNode,
|
||||||
|
nativeschema.MethodGetTreeNodeByPath,
|
||||||
|
nativeschema.MethodGetSubtree,
|
||||||
|
nativeschema.MethodListTrees},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{nativeschema.ResourceFormatRootTrees},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorRole,
|
||||||
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: chain.CondStringNotEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||||
|
Value: hex.EncodeToString(forPutGet.Bytes()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: chain.CondStringNotEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: nativeschema.PropertyKeyActorPublicKey,
|
||||||
|
Value: hex.EncodeToString(forGet.Bytes()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SetAPEOverride(bearer.APEOverride{
|
||||||
|
Target: ape.ChainTarget{
|
||||||
|
TargetType: ape.TargetTypeContainer,
|
||||||
|
Name: cid.EncodeToString(),
|
||||||
|
},
|
||||||
|
Chains: []ape.Chain{
|
||||||
|
{
|
||||||
|
Raw: allowsGetOpForAnyRoleForBothKeys.Bytes(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Raw: allowsPutOpForAnyRoleForOneKeyOnly.Bytes(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Raw: deniesGetPutOpForOthers.Bytes(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTable(cid cid.ID, forPutGet, forGet *keys.PublicKey) *eaclSDK.Table {
|
|
||||||
tgtGet := eaclSDK.NewTarget()
|
|
||||||
tgtGet.SetRole(eaclSDK.RoleUnknown)
|
|
||||||
tgtGet.SetBinaryKeys([][]byte{forPutGet.Bytes(), forGet.Bytes()})
|
|
||||||
|
|
||||||
rGet := eaclSDK.NewRecord()
|
|
||||||
rGet.SetAction(eaclSDK.ActionAllow)
|
|
||||||
rGet.SetOperation(eaclSDK.OperationGet)
|
|
||||||
rGet.SetTargets(*tgtGet)
|
|
||||||
|
|
||||||
tgtPut := eaclSDK.NewTarget()
|
|
||||||
tgtPut.SetRole(eaclSDK.RoleUnknown)
|
|
||||||
tgtPut.SetBinaryKeys([][]byte{forPutGet.Bytes()})
|
|
||||||
|
|
||||||
rPut := eaclSDK.NewRecord()
|
|
||||||
rPut.SetAction(eaclSDK.ActionAllow)
|
|
||||||
rPut.SetOperation(eaclSDK.OperationPut)
|
|
||||||
rPut.SetTargets(*tgtPut)
|
|
||||||
|
|
||||||
tb := eaclSDK.NewTable()
|
|
||||||
tb.AddRecord(rGet)
|
|
||||||
tb.AddRecord(rPut)
|
|
||||||
|
|
||||||
tgt := eaclSDK.NewTarget()
|
|
||||||
tgt.SetRole(eaclSDK.RoleOthers)
|
|
||||||
|
|
||||||
for _, op := range []eaclSDK.Operation{eaclSDK.OperationGet, eaclSDK.OperationPut} {
|
|
||||||
r := eaclSDK.NewRecord()
|
|
||||||
r.SetAction(eaclSDK.ActionDeny)
|
|
||||||
r.SetTargets(*tgt)
|
|
||||||
r.SetOperation(op)
|
|
||||||
tb.AddRecord(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
tb.SetCID(cid)
|
|
||||||
|
|
||||||
return tb
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue