[#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:
Airat Arifullin 2024-04-15 15:54:16 +03:00
parent 97e54066d0
commit 6772976657
2 changed files with 130 additions and 5 deletions

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
} }