[#1117] ape: Introduce FormFrostfsIDRequestProperties method

* `FormFrostfsIDRequestProperties` gets user claim tags and group id and sets them
  as ape request properties.
* Make tree, container and object service use the method.
* Fix unit-tests.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2024-05-02 21:15:58 +03:00 committed by Evgenii Stratonikov
parent 6c76c9b457
commit b60a51b862
6 changed files with 219 additions and 30 deletions

View file

@ -0,0 +1,36 @@
package request
import (
"fmt"
"strconv"
"strings"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
// FormFrostfsIDRequestProperties forms frostfsid specific request properties like user-claim tags and group ID.
func FormFrostfsIDRequestProperties(frostFSIDClient frostfsidcore.SubjectProvider, pk *keys.PublicKey) (map[string]string, error) {
reqProps := make(map[string]string)
subj, err := frostFSIDClient.GetSubjectExtended(pk.GetScriptHash())
if err != nil {
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
}
return reqProps, nil
}
for k, v := range subj.KV {
propertyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[propertyKey] = v
}
groups := make([]string, len(subj.Groups))
for i, group := range subj.Groups {
groups[i] = strconv.FormatInt(group.ID, 10)
}
reqProps[commonschema.PropertyKeyFrostFSIDGroupID] = apechain.FormCondSliceContainsValue(groups)
return reqProps, nil
}

View file

@ -27,7 +27,6 @@ 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"
)
@ -619,16 +618,12 @@ func (ac *apeChecker) fillWithUserClaimTags(reqProps map[string]string, pk *keys
if reqProps == nil {
reqProps = make(map[string]string)
}
subj, err := ac.frostFSIDClient.GetSubject(pk.GetScriptHash())
props, err := aperequest.FormFrostfsIDRequestProperties(ac.frostFSIDClient, pk)
if err != nil {
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
return reqProps, err
}
return reqProps, nil
}
for k, v := range subj.KV {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[properyKey] = v
for propertyName, properyValue := range props {
reqProps[propertyName] = properyValue
}
return reqProps, nil
}

View file

@ -45,6 +45,7 @@ func TestAPE(t *testing.T) {
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 get container by group id", testDenyGetContainerByGroupID)
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)
@ -279,6 +280,19 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) {
},
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
@ -339,6 +353,104 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied)
}
func testDenyGetContainerByGroupID(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",
},
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
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: commonschema.PropertyKeyFrostFSIDGroupID,
Value: "19888",
Op: chain.CondStringEquals,
},
},
},
},
})
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{
@ -621,6 +733,21 @@ func testDenyPutContainerReadNamespaceFromFrostfsID(t *testing.T) {
Name: testDomainName,
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req)
@ -690,6 +817,21 @@ func testDenyPutContainerInvalidNamespace(t *testing.T) {
Name: testDomainName,
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
resp, err := apeSrv.Put(context.Background(), req)
@ -800,6 +942,34 @@ func testDenyListContainersValidationNamespaceError(t *testing.T) {
Name: testDomainName,
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
actorScriptHash: {
Namespace: actorDomain,
Name: actorDomain,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19777,
},
},
},
ownerScriptHash: {
Namespace: testDomainName,
Name: testDomainName,
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

View file

@ -4,17 +4,14 @@ 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"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
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"
)
@ -148,16 +145,12 @@ func (c *checkerImpl) fillWithUserClaimTags(reqProps map[string]string, prm Prm)
if err != nil {
return nil, err
}
subj, err := c.frostFSIDClient.GetSubject(pk.GetScriptHash())
props, err := aperequest.FormFrostfsIDRequestProperties(c.frostFSIDClient, pk)
if err != nil {
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
return reqProps, err
}
return reqProps, nil
}
for k, v := range subj.KV {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[properyKey] = v
for propertyName, properyValue := range props {
reqProps[propertyName] = properyValue
}
return reqProps, nil
}

View file

@ -275,6 +275,7 @@ func TestNewAPERequest(t *testing.T) {
nativeschema.PropertyKeyActorRole: prm.Role,
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1",
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2",
commonschema.PropertyKeyFrostFSIDGroupID: "1",
},
)

View file

@ -8,14 +8,12 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/converter"
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
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"
)
@ -80,16 +78,12 @@ func (s *Service) fillWithUserClaimTags(reqProps map[string]string, publicKey *k
if reqProps == nil {
reqProps = make(map[string]string)
}
subj, err := s.frostfsidSubjectProvider.GetSubject(publicKey.GetScriptHash())
props, err := aperequest.FormFrostfsIDRequestProperties(s.frostfsidSubjectProvider, publicKey)
if err != nil {
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
return nil, fmt.Errorf("get subject error: %w", err)
return reqProps, err
}
return reqProps, nil
}
for k, v := range subj.KV {
properyKey := fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, k)
reqProps[properyKey] = v
for propertyName, properyValue := range props {
reqProps[propertyName] = properyValue
}
return reqProps, nil
}