forked from TrueCloudLab/frostfs-node
[#1096] container: Make ape middleware fill request with user claim tags
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
97e54066d0
commit
6772976657
2 changed files with 130 additions and 5 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue