ape: fill request properties with user claim tags #1096

Merged
fyrchik merged 3 commits from aarifullin/frostfs-node:feat/ape_user_claim_tag into master 2024-09-04 19:51:08 +00:00
12 changed files with 284 additions and 22 deletions

View file

@ -46,6 +46,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
containerClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" 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" nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
netmap2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap" netmap2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap"
@ -415,6 +416,8 @@ type shared struct {
cnrClient *containerClient.Client cnrClient *containerClient.Client
frostfsidClient *frostfsidClient.Client
respSvc *response.Service respSvc *response.Service
replicator *replicator.Replicator replicator *replicator.Replicator

View file

@ -35,12 +35,13 @@ func initContainerService(_ context.Context, c *cfg) {
frostFSIDClient, err := frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0) frostFSIDClient, err := frostfsid.NewFromMorph(c.cfgMorph.client, c.cfgFrostfsID.scriptHash, 0)
fatalOnErr(err) fatalOnErr(err)
c.shared.frostfsidClient = frostFSIDClient
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, frostFSIDClient, newCachedIRFetcher(createInnerRingFetcher(c)), c.netMapSource, c.shared.frostfsidClient,
containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc), containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt), c.respSvc),
), ),
), ),

View file

@ -444,6 +444,7 @@ func createAPEService(c *cfg, splitSvc *objectService.TransportSplitter) *object
objectAPE.NewChecker( objectAPE.NewChecker(
c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.chainRouter, c.cfgObject.cfgAccessPolicyEngine.accessPolicyEngine.chainRouter,
objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage), objectAPE.NewStorageEngineHeaderProvider(c.cfgObject.cfgLocalStorage.localStorage),
c.shared.frostfsidClient,
), ),
splitSvc, splitSvc,
) )

View file

@ -53,6 +53,7 @@ func initTreeService(c *cfg) {
src: c.cfgObject.cnrSource, src: c.cfgObject.cnrSource,
cli: c.shared.cnrClient, cli: c.shared.cnrClient,
}), }),
tree.WithFrostfsidSubjectProvider(c.shared.frostfsidClient),
tree.WithEACLSource(c.cfgObject.eaclSource), tree.WithEACLSource(c.cfgObject.eaclSource),
tree.WithNetmapSource(c.netMapSource), tree.WithNetmapSource(c.netMapSource),
tree.WithPrivateKey(&c.key.PrivateKey), tree.WithPrivateKey(&c.key.PrivateKey),

View file

@ -27,11 +27,16 @@ import (
"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"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" 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" 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/nspcc-dev/neo-go/pkg/util"
) )
const (
subjectNotFoundErrorMessage = "subject not found"
)
var ( var (
errMissingContainerID = errors.New("missing container ID") errMissingContainerID = errors.New("missing container ID")
errSessionContainerMissmatch = errors.New("requested container is not related to the session") 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") 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")
errOwnerIDIsNotSet = errors.New("owner id is not set") errOwnerIDIsNotSet = errors.New("owner id is not set")
errInvalidDomainZone = errors.New("invalid domain zone: no namespace is expected") 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, nativeschema.PropertyKeyActorRole: role,
} }
reqProps, err = ac.fillWithUserClaimTags(reqProps, pk)
if err != nil {
return nil, err
}
namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID()) namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID())
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get owner namespace: %w", err) 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, nativeschema.PropertyKeyActorRole: role,
} }
reqProps, err = ac.fillWithUserClaimTags(reqProps, pk)
if err != nil {
return nil, err
}
namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID()) namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID())
if err != nil { if err != nil {
return nil, fmt.Errorf("get namespace error: %w", err) 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
return map[string]string{ reqProps := map[string]string{
nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(pk.Bytes()), nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(pk.Bytes()),
nativeschema.PropertyKeyActorRole: role, 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) { 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 { if err == nil {
namespace = subject.Namespace namespace = subject.Namespace
} else { } else {
if !strings.Contains(err.Error(), errSubjectNotFound.Error()) { if !strings.Contains(err.Error(), subjectNotFoundErrorMessage) {
return "", fmt.Errorf("get subject error: %w", err) return "", fmt.Errorf("get subject error: %w", err)
} }
} }
@ -603,3 +622,22 @@ func (ac *apeChecker) validateNamespaceByPublicKey(pk *keys.PublicKey, ownerIDNa
} }
return nil 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
}

View file

@ -26,6 +26,7 @@ import (
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory" "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" 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/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("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound) t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
t.Run("deny get container for others", testDenyGetContainerForOthers) 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 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)
@ -251,6 +253,91 @@ func testDenyGetContainerForOthers(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied) 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) { func testDenySetContainerEACLForIR(t *testing.T) {
t.Parallel() t.Parallel()
srv := &srvStub{ srv := &srvStub{
@ -841,7 +928,7 @@ type frostfsidStub struct {
func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) { func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
s, ok := f.subjects[owner] s, ok := f.subjects[owner]
if !ok { if !ok {
return nil, errSubjectNotFound return nil, fmt.Errorf("%s", subjectNotFoundErrorMessage)
} }
return s, nil return s, nil
} }

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
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-contract/frostfsid/client"
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"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
@ -13,19 +14,28 @@ import (
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
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"
) )
type checkerImpl struct { type checkerImpl struct {
chainRouter policyengine.ChainRouter chainRouter policyengine.ChainRouter
headerProvider HeaderProvider 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{ return &checkerImpl{
chainRouter: chainRouter, chainRouter: chainRouter,
headerProvider: headerProvider, headerProvider: headerProvider,
frostFSIDClient: frostFSIDClient,
} }
} }

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
@ -17,6 +18,7 @@ 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"
) )
@ -153,6 +155,41 @@ var (
senderKey = hex.EncodeToString(senderPrivateKey.PublicKey().Bytes()) 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) { func TestAPECheck(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
name string name string
@ -321,6 +358,7 @@ func TestAPECheck(t *testing.T) {
for _, method := range test.methods { for _, method := range test.methods {
t.Run(method, func(t *testing.T) { t.Run(method, func(t *testing.T) {
headerProvider := newHeaderProviderMock() headerProvider := newHeaderProviderMock()
frostfsidProvider := newFrostfsIDProviderMock(t)
cnr := newContainerIDSDK(t, test.container) cnr := newContainerIDSDK(t, test.container)
obj := newObjectIDSDK(t, test.object) obj := newObjectIDSDK(t, test.object)
@ -335,7 +373,7 @@ func TestAPECheck(t *testing.T) {
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls) router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
checker := NewChecker(router, headerProvider) checker := NewChecker(router, headerProvider, frostfsidProvider)
prm := Prm{ prm := Prm{
Method: method, Method: method,

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strconv" "strconv"
"strings"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request" aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
@ -12,7 +13,13 @@ import (
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "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" 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{} 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( return aperequest.NewRequest(
prm.Method, prm.Method,
aperequest.NewResource( aperequest.NewResource(
resourceName(prm.Container, prm.Object, prm.Namespace), resourceName(prm.Container, prm.Object, prm.Namespace),
objectProperties(prm.Container, prm.Object, prm.ContainerOwner, header), objectProperties(prm.Container, prm.Object, prm.ContainerOwner, header),
), ),
map[string]string{ reqProps,
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
nativeschema.PropertyKeyActorRole: prm.Role,
},
), nil ), 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
}

View file

@ -11,6 +11,7 @@ import (
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" 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" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -234,6 +235,7 @@ func TestNewAPERequest(t *testing.T) {
} }
headerSource := newHeaderProviderMock() headerSource := newHeaderProviderMock()
ffidProvider := newFrostfsIDProviderMock(t)
var headerObjSDK *objectSDK.Object var headerObjSDK *objectSDK.Object
if test.header.headerObjSDK != nil { if test.header.headerObjSDK != nil {
@ -247,7 +249,8 @@ func TestNewAPERequest(t *testing.T) {
} }
c := checkerImpl{ c := checkerImpl{
headerProvider: headerSource, headerProvider: headerSource,
frostFSIDClient: ffidProvider,
} }
r, err := c.newAPERequest(context.TODO(), prm) r, err := c.newAPERequest(context.TODO(), prm)
@ -268,8 +271,10 @@ func TestNewAPERequest(t *testing.T) {
return prm.Header return prm.Header
}())), }())),
map[string]string{ map[string]string{
nativeschema.PropertyKeyActorPublicKey: prm.SenderKey, nativeschema.PropertyKeyActorPublicKey: prm.SenderKey,
nativeschema.PropertyKeyActorRole: prm.Role, nativeschema.PropertyKeyActorRole: prm.Role,
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1",
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2",
}, },
) )

View file

@ -14,10 +14,15 @@ import (
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "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" 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"
) )
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 { func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey) error {
namespace := "" namespace := ""
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns") 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.PropertyKeyActorPublicKey: hex.EncodeToString(publicKey.Bytes()),
nativeschema.PropertyKeyActorRole: schemaRole, nativeschema.PropertyKeyActorRole: schemaRole,
} }
reqProps, err = s.fillWithUserClaimTags(reqProps, publicKey)
if err != nil {
return apeErr(err)
}
var resourceName string var resourceName string
if namespace == "root" || namespace == "" { if namespace == "root" || namespace == "" {
@ -68,3 +77,22 @@ func apeErr(err error) error {
errAccessDenied.WriteReason(err.Error()) errAccessDenied.WriteReason(err.Error())
return errAccessDenied 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
}

View file

@ -4,6 +4,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"time" "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/container"
"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/local_object_storage/pilorama" "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" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" 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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
) )
type FrostfsidSubjectProvider interface {
GetSubject(util.Uint160) (*client.Subject, error)
}
type ContainerSource interface { type ContainerSource interface {
container.Source container.Source
@ -25,13 +31,14 @@ type ContainerSource interface {
} }
type cfg struct { type cfg struct {
log *logger.Logger log *logger.Logger
key *ecdsa.PrivateKey key *ecdsa.PrivateKey
rawPub []byte rawPub []byte
nmSource netmap.Source nmSource netmap.Source
cnrSource ContainerSource cnrSource ContainerSource
eaclSource container.EACLSource frostfsidSubjectProvider FrostfsidSubjectProvider
forest pilorama.Forest eaclSource container.EACLSource
forest pilorama.Forest
// replication-related parameters // replication-related parameters
replicatorChannelCapacity int replicatorChannelCapacity int
replicatorWorkerCount 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. // WithEACLSource sets a eACL table source for a tree service.
// This option is required. // This option is required.
func WithEACLSource(src container.EACLSource) Option { func WithEACLSource(src container.EACLSource) Option {