ape: fill request properties with user claim tags #1096
12 changed files with 284 additions and 22 deletions
|
@ -46,6 +46,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
containerClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||
frostfsidClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
|
||||
nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
|
||||
netmap2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap"
|
||||
|
@ -415,6 +416,8 @@ type shared struct {
|
|||
|
||||
cnrClient *containerClient.Client
|
||||
|
||||
frostfsidClient *frostfsidClient.Client
|
||||
|
||||
respSvc *response.Service
|
||||
|
||||
replicator *replicator.Replicator
|
||||
|
|
|
@ -35,12 +35,13 @@ func initContainerService(_ context.Context, c *cfg) {
|
|||
|
||||
frostFSIDClient, err := frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0)
|
||||
fatalOnErr(err)
|
||||
c.shared.frostfsidClient = frostFSIDClient
|
||||
|
||||
server := containerTransportGRPC.New(
|
||||
containerService.NewSignService(
|
||||
&c.key.PrivateKey,
|
||||
containerService.NewAPEServer(c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine, cnrRdr,
|
||||
newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource, frostFSIDClient,
|
||||
newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource, c.shared.frostfsidClient,
|
||||
containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -444,6 +444,7 @@ func createAPEService(c *cfg, splitSvc *objectService.TransportSplitter) *object
|
|||
objectAPE.NewChecker(
|
||||
c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.chainRouter,
|
||||
objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage),
|
||||
c.shared.frostfsidClient,
|
||||
),
|
||||
splitSvc,
|
||||
)
|
||||
|
|
|
@ -53,6 +53,7 @@ func initTreeService(c *cfg) {
|
|||
src: c.cfgObject.cnrSource,
|
||||
cli: c.shared.cnrClient,
|
||||
}),
|
||||
tree.WithFrostfsidSubjectProvider(c.shared.frostfsidClient),
|
||||
tree.WithEACLSource(c.cfgObject.eaclSource),
|
||||
tree.WithNetmapSource(c.netMapSource),
|
||||
tree.WithPrivateKey(&c.key.PrivateKey),
|
||||
|
|
|
@ -27,11 +27,16 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
subjectNotFoundErrorMessage = "subject not found"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingContainerID = errors.New("missing container ID")
|
||||
errSessionContainerMissmatch = errors.New("requested container is not related to the session")
|
||||
|
@ -40,7 +45,6 @@ var (
|
|||
errInvalidSessionTokenOwner = errors.New("malformed request: invalid session token owner")
|
||||
errEmptyBodySignature = errors.New("malformed request: empty body signature")
|
||||
errMissingOwnerID = errors.New("malformed request: missing owner ID")
|
||||
errSubjectNotFound = errors.New("subject not found")
|
||||
errOwnerIDIsNotSet = errors.New("owner id is not set")
|
||||
errInvalidDomainZone = errors.New("invalid domain zone: no namespace is expected")
|
||||
|
||||
|
@ -140,6 +144,11 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
|
|||
nativeschema.PropertyKeyActorRole: role,
|
||||
}
|
||||
|
||||
reqProps, err = ac.fillWithUserClaimTags(reqProps, pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get owner namespace: %w", err)
|
||||
|
@ -188,6 +197,11 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
|
|||
nativeschema.PropertyKeyActorRole: role,
|
||||
}
|
||||
|
||||
reqProps, err = ac.fillWithUserClaimTags(reqProps, pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get namespace error: %w", err)
|
||||
|
@ -365,10 +379,15 @@ func (ac *apeChecker) getRequestProps(mh *session.RequestMetaHeader, vh *session
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return map[string]string{
|
||||
reqProps := map[string]string{
|
||||
nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(pk.Bytes()),
|
||||
nativeschema.PropertyKeyActorRole: role,
|
||||
}, pk, nil
|
||||
}
|
||||
reqProps, err = ac.fillWithUserClaimTags(reqProps, pk)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return reqProps, pk, nil
|
||||
}
|
||||
|
||||
func (ac *apeChecker) getRole(actor *user.ID, pk *keys.PublicKey, cont *containercore.Container, cnrID cid.ID) (string, error) {
|
||||
|
@ -555,7 +574,7 @@ func (ac *apeChecker) namespaceByOwner(owner *refs.OwnerID) (string, error) {
|
|||
if err == nil {
|
||||
namespace = subject.Namespace
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), errSubjectNotFound.Error()) {
|
||||
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
|
||||
return "", fmt.Errorf("get subject error: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -603,3 +622,22 @@ func (ac *apeChecker) validateNamespaceByPublicKey(pk *keys.PublicKey, ownerIDNa
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillWithUserClaimTags fills ape request properties with user claim tags getting them from frostfsid contract by actor public key.
|
||||
func (ac *apeChecker) fillWithUserClaimTags(reqProps map[string]string, pk *keys.PublicKey) (map[string]string, error) {
|
||||
if reqProps == nil {
|
||||
reqProps = make(map[string]string)
|
||||
}
|
||||
subj, err := ac.frostFSIDClient.GetSubject(pk.GetScriptHash())
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
|
||||
return nil, fmt.Errorf("get subject error: %w", err)
|
||||
}
|
||||
return reqProps, nil
|
||||
}
|
||||
for k, v := range subj.KV {
|
||||
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
|
||||
reqProps[properyKey] = v
|
||||
}
|
||||
return reqProps, nil
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
||||
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -42,6 +43,7 @@ func TestAPE(t *testing.T) {
|
|||
t.Run("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
|
||||
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
|
||||
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
||||
t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag)
|
||||
t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR)
|
||||
t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
|
||||
t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken)
|
||||
|
@ -251,6 +253,91 @@ func testDenyGetContainerForOthers(t *testing.T) {
|
|||
require.ErrorAs(t, err, &errAccessDenied)
|
||||
}
|
||||
|
||||
func testDenyGetContainerByUserClaimTag(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{}
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
frostfsIDSubjectReader := &frostfsidStub{
|
||||
subjects: map[util.Uint160]*client.Subject{
|
||||
pk.PublicKey().GetScriptHash(): {
|
||||
KV: map[string]string{
|
||||
"tag-attr1": "value1",
|
||||
"tag-attr2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||
|
||||
contID := cidtest.ID()
|
||||
testContainer := containertest.Container()
|
||||
pp := netmap.PlacementPolicy{}
|
||||
require.NoError(t, pp.DecodeString("REP 1"))
|
||||
testContainer.SetPlacementPolicy(pp)
|
||||
contRdr.c[contID] = &containercore.Container{Value: testContainer}
|
||||
|
||||
nm.currentEpoch = 100
|
||||
nm.netmaps = map[uint64]*netmap.NetMap{}
|
||||
var testNetmap netmap.NetMap
|
||||
testNetmap.SetEpoch(nm.currentEpoch)
|
||||
testNetmap.SetNodes([]netmap.NodeInfo{{}})
|
||||
nm.netmaps[nm.currentEpoch] = &testNetmap
|
||||
nm.netmaps[nm.currentEpoch-1] = &testNetmap
|
||||
|
||||
_, _, err = router.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: fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"),
|
||||
Value: "value100",
|
||||
Op: chain.CondStringNotEquals,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &container.GetRequest{}
|
||||
req.SetBody(&container.GetRequestBody{})
|
||||
var refContID refs.ContainerID
|
||||
contID.WriteToV2(&refContID)
|
||||
req.GetBody().SetContainerID(&refContID)
|
||||
|
||||
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
||||
|
||||
resp, err := apeSrv.Get(context.Background(), req)
|
||||
require.Nil(t, resp)
|
||||
var errAccessDenied *apistatus.ObjectAccessDenied
|
||||
require.ErrorAs(t, err, &errAccessDenied)
|
||||
}
|
||||
|
||||
func testDenySetContainerEACLForIR(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv := &srvStub{
|
||||
|
@ -841,7 +928,7 @@ type frostfsidStub struct {
|
|||
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
|
||||
s, ok := f.subjects[owner]
|
||||
if !ok {
|
||||
return nil, errSubjectNotFound
|
||||
return nil, fmt.Errorf("%s", subjectNotFoundErrorMessage)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
|
@ -13,19 +14,28 @@ import (
|
|||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
type checkerImpl struct {
|
||||
chainRouter policyengine.ChainRouter
|
||||
|
||||
headerProvider HeaderProvider
|
||||
|
||||
frostFSIDClient frostfsidSubjectProvider
|
||||
}
|
||||
|
||||
func NewChecker(chainRouter policyengine.ChainRouter, headerProvider HeaderProvider) Checker {
|
||||
type frostfsidSubjectProvider interface {
|
||||
GetSubject(util.Uint160) (*client.Subject, error)
|
||||
}
|
||||
|
||||
func NewChecker(chainRouter policyengine.ChainRouter, headerProvider HeaderProvider, frostFSIDClient frostfsidSubjectProvider) Checker {
|
||||
return &checkerImpl{
|
||||
chainRouter: chainRouter,
|
||||
|
||||
headerProvider: headerProvider,
|
||||
|
||||
frostFSIDClient: frostFSIDClient,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -153,6 +155,41 @@ var (
|
|||
senderKey = hex.EncodeToString(senderPrivateKey.PublicKey().Bytes())
|
||||
)
|
||||
|
||||
type frostfsIDProviderMock struct {
|
||||
m map[util.Uint160]*client.Subject
|
||||
}
|
||||
|
||||
var _ frostfsidSubjectProvider = (*frostfsIDProviderMock)(nil)
|
||||
|
||||
func newFrostfsIDProviderMock(t *testing.T) *frostfsIDProviderMock {
|
||||
return &frostfsIDProviderMock{
|
||||
m: map[util.Uint160]*client.Subject{
|
||||
scriptHashFromSenderKey(t, senderKey): {
|
||||
Namespace: "testnamespace",
|
||||
Name: "test",
|
||||
KV: map[string]string{
|
||||
"tag-attr1": "value1",
|
||||
"tag-attr2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func scriptHashFromSenderKey(t *testing.T, senderKey string) util.Uint160 {
|
||||
pk, err := keys.NewPublicKeyFromString(senderKey)
|
||||
require.NoError(t, err)
|
||||
return pk.GetScriptHash()
|
||||
}
|
||||
|
||||
func (f *frostfsIDProviderMock) GetSubject(key util.Uint160) (*client.Subject, error) {
|
||||
v, ok := f.m[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s", subjectNotFoundErrorMessage)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func TestAPECheck(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
@ -321,6 +358,7 @@ func TestAPECheck(t *testing.T) {
|
|||
for _, method := range test.methods {
|
||||
t.Run(method, func(t *testing.T) {
|
||||
headerProvider := newHeaderProviderMock()
|
||||
frostfsidProvider := newFrostfsIDProviderMock(t)
|
||||
|
||||
cnr := newContainerIDSDK(t, test.container)
|
||||
obj := newObjectIDSDK(t, test.object)
|
||||
|
@ -335,7 +373,7 @@ func TestAPECheck(t *testing.T) {
|
|||
|
||||
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
|
||||
|
||||
checker := NewChecker(router, headerProvider)
|
||||
checker := NewChecker(router, headerProvider, frostfsidProvider)
|
||||
|
||||
prm := Prm{
|
||||
Method: method,
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
|
||||
|
@ -12,7 +13,13 @@ import (
|
|||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
const (
|
||||
subjectNotFoundErrorMessage = "subject not found"
|
||||
)
|
||||
|
||||
var defaultRequest = aperequest.Request{}
|
||||
|
@ -115,15 +122,45 @@ func (c *checkerImpl) newAPERequest(ctx context.Context, prm Prm) (aperequest.Re
|
|||
}
|
||||
}
|
||||
|
||||
reqProps := map[string]string{
|
||||
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
|
||||
nativeschema.PropertyKeyActorRole: prm.Role,
|
||||
}
|
||||
var err error
|
||||
reqProps, err = c.fillWithUserClaimTags(reqProps, prm)
|
||||
if err != nil {
|
||||
return defaultRequest, err
|
||||
}
|
||||
|
||||
return aperequest.NewRequest(
|
||||
prm.Method,
|
||||
aperequest.NewResource(
|
||||
resourceName(prm.Container, prm.Object, prm.Namespace),
|
||||
objectProperties(prm.Container, prm.Object, prm.ContainerOwner, header),
|
||||
),
|
||||
map[string]string{
|
||||
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
|
||||
nativeschema.PropertyKeyActorRole: prm.Role,
|
||||
},
|
||||
reqProps,
|
||||
), nil
|
||||
}
|
||||
|
||||
// fillWithUserClaimTags fills ape request properties with user claim tags getting them from frostfsid contract by actor public key.
|
||||
func (c *checkerImpl) fillWithUserClaimTags(reqProps map[string]string, prm Prm) (map[string]string, error) {
|
||||
if reqProps == nil {
|
||||
reqProps = make(map[string]string)
|
||||
}
|
||||
pk, err := keys.NewPublicKeyFromString(prm.SenderKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subj, err := c.frostFSIDClient.GetSubject(pk.GetScriptHash())
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
|
||||
return nil, fmt.Errorf("get subject error: %w", err)
|
||||
}
|
||||
return reqProps, nil
|
||||
}
|
||||
for k, v := range subj.KV {
|
||||
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
|
||||
reqProps[properyKey] = v
|
||||
}
|
||||
return reqProps, nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -234,6 +235,7 @@ func TestNewAPERequest(t *testing.T) {
|
|||
}
|
||||
|
||||
headerSource := newHeaderProviderMock()
|
||||
ffidProvider := newFrostfsIDProviderMock(t)
|
||||
|
||||
var headerObjSDK *objectSDK.Object
|
||||
if test.header.headerObjSDK != nil {
|
||||
|
@ -247,7 +249,8 @@ func TestNewAPERequest(t *testing.T) {
|
|||
}
|
||||
|
||||
c := checkerImpl{
|
||||
headerProvider: headerSource,
|
||||
headerProvider: headerSource,
|
||||
frostFSIDClient: ffidProvider,
|
||||
}
|
||||
|
||||
r, err := c.newAPERequest(context.TODO(), prm)
|
||||
|
@ -268,8 +271,10 @@ func TestNewAPERequest(t *testing.T) {
|
|||
return prm.Header
|
||||
}())),
|
||||
map[string]string{
|
||||
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
|
||||
nativeschema.PropertyKeyActorRole: prm.Role,
|
||||
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
|
||||
nativeschema.PropertyKeyActorRole: prm.Role,
|
||||
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1",
|
||||
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -14,10 +14,15 @@ import (
|
|||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
var (
|
||||
subjectNotFoundErrorMessage = "subject not found"
|
||||
)
|
||||
|
||||
func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey) error {
|
||||
namespace := ""
|
||||
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns")
|
||||
|
@ -37,6 +42,10 @@ func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.
|
|||
nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(publicKey.Bytes()),
|
||||
nativeschema.PropertyKeyActorRole: schemaRole,
|
||||
}
|
||||
reqProps, err = s.fillWithUserClaimTags(reqProps, publicKey)
|
||||
if err != nil {
|
||||
return apeErr(err)
|
||||
}
|
||||
|
||||
var resourceName string
|
||||
if namespace == "root" || namespace == "" {
|
||||
|
@ -68,3 +77,22 @@ func apeErr(err error) error {
|
|||
errAccessDenied.WriteReason(err.Error())
|
||||
return errAccessDenied
|
||||
}
|
||||
|
||||
// fillWithUserClaimTags fills ape request properties with user claim tags getting them from frostfsid contract by actor public key.
|
||||
func (s *Service) fillWithUserClaimTags(reqProps map[string]string, publicKey *keys.PublicKey) (map[string]string, error) {
|
||||
if reqProps == nil {
|
||||
reqProps = make(map[string]string)
|
||||
}
|
||||
subj, err := s.frostfsidSubjectProvider.GetSubject(publicKey.GetScriptHash())
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
|
||||
return nil, fmt.Errorf("get subject error: %w", err)
|
||||
}
|
||||
return reqProps, nil
|
||||
}
|
||||
for k, v := range subj.KV {
|
||||
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
|
||||
reqProps[properyKey] = v
|
||||
}
|
||||
return reqProps, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"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/local_object_storage/pilorama"
|
||||
|
@ -11,8 +12,13 @@ import (
|
|||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
type FrostfsidSubjectProvider interface {
|
||||
GetSubject(util.Uint160) (*client.Subject, error)
|
||||
}
|
||||
|
||||
type ContainerSource interface {
|
||||
container.Source
|
||||
|
||||
|
@ -25,13 +31,14 @@ type ContainerSource interface {
|
|||
}
|
||||
|
||||
type cfg struct {
|
||||
log *logger.Logger
|
||||
key *ecdsa.PrivateKey
|
||||
rawPub []byte
|
||||
nmSource netmap.Source
|
||||
cnrSource ContainerSource
|
||||
eaclSource container.EACLSource
|
||||
forest pilorama.Forest
|
||||
log *logger.Logger
|
||||
key *ecdsa.PrivateKey
|
||||
rawPub []byte
|
||||
nmSource netmap.Source
|
||||
cnrSource ContainerSource
|
||||
frostfsidSubjectProvider FrostfsidSubjectProvider
|
||||
eaclSource container.EACLSource
|
||||
forest pilorama.Forest
|
||||
// replication-related parameters
|
||||
replicatorChannelCapacity int
|
||||
replicatorWorkerCount int
|
||||
|
@ -55,6 +62,12 @@ func WithContainerSource(src ContainerSource) Option {
|
|||
}
|
||||
}
|
||||
|
||||
func WithFrostfsidSubjectProvider(provider FrostfsidSubjectProvider) Option {
|
||||
return func(c *cfg) {
|
||||
c.frostfsidSubjectProvider = provider
|
||||
}
|
||||
}
|
||||
|
||||
// WithEACLSource sets a eACL table source for a tree service.
|
||||
// This option is required.
|
||||
func WithEACLSource(src container.EACLSource) Option {
|
||||
|
|
Loading…
Add table
Reference in a new issue