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/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
|
||||||
|
|
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue