diff --git a/cmd/frostfs-node/config.go b/cmd/frostfs-node/config.go index c5ed8b50..066d63e6 100644 --- a/cmd/frostfs-node/config.go +++ b/cmd/frostfs-node/config.go @@ -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 diff --git a/cmd/frostfs-node/container.go b/cmd/frostfs-node/container.go index d566c0af..e006648e 100644 --- a/cmd/frostfs-node/container.go +++ b/cmd/frostfs-node/container.go @@ -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), ), ), diff --git a/cmd/frostfs-node/object.go b/cmd/frostfs-node/object.go index 7f1d094f..6160bbc7 100644 --- a/cmd/frostfs-node/object.go +++ b/cmd/frostfs-node/object.go @@ -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, ) diff --git a/pkg/services/object/ape/checker.go b/pkg/services/object/ape/checker.go index 1063bd90..17078731 100644 --- a/pkg/services/object/ape/checker.go +++ b/pkg/services/object/ape/checker.go @@ -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, } } diff --git a/pkg/services/object/ape/checker_test.go b/pkg/services/object/ape/checker_test.go index fc915715..9510bf03 100644 --- a/pkg/services/object/ape/checker_test.go +++ b/pkg/services/object/ape/checker_test.go @@ -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, diff --git a/pkg/services/object/ape/request.go b/pkg/services/object/ape/request.go index 7bbee31c..5daf8751 100644 --- a/pkg/services/object/ape/request.go +++ b/pkg/services/object/ape/request.go @@ -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 +} diff --git a/pkg/services/object/ape/request_test.go b/pkg/services/object/ape/request_test.go index fdb7af21..cda78e69 100644 --- a/pkg/services/object/ape/request_test.go +++ b/pkg/services/object/ape/request_test.go @@ -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", }, )