Fill APE Request proprties with source IP in services #1142

Merged
fyrchik merged 3 commits from aarifullin/frostfs-node:feat/ape_sourceip into master 2024-05-27 10:17:21 +00:00
8 changed files with 207 additions and 40 deletions

View file

@ -9,6 +9,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"net"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
@ -27,8 +28,10 @@ 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"
"google.golang.org/grpc/peer"
) )
var ( var (
@ -88,7 +91,7 @@ func (ac *apeChecker) Delete(ctx context.Context, req *container.DeleteRequest)
ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.Delete") ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.Delete")
defer span.End() defer span.End()
if err := ac.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), if err := ac.validateContainerBoundedOperation(ctx, req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(),
nativeschema.MethodDeleteContainer); err != nil { nativeschema.MethodDeleteContainer); err != nil {
return nil, err return nil, err
} }
@ -100,7 +103,7 @@ func (ac *apeChecker) Get(ctx context.Context, req *container.GetRequest) (*cont
ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.Get") ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.Get")
defer span.End() defer span.End()
if err := ac.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), if err := ac.validateContainerBoundedOperation(ctx, req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(),
nativeschema.MethodGetContainer); err != nil { nativeschema.MethodGetContainer); err != nil {
return nil, err return nil, err
} }
@ -112,7 +115,7 @@ func (ac *apeChecker) GetExtendedACL(ctx context.Context, req *container.GetExte
ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.GetExtendedACL") ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.GetExtendedACL")
defer span.End() defer span.End()
if err := ac.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), if err := ac.validateContainerBoundedOperation(ctx, req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(),
nativeschema.MethodGetContainerEACL); err != nil { nativeschema.MethodGetContainerEACL); err != nil {
return nil, err return nil, err
} }
@ -138,6 +141,11 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
if err != nil { if err != nil {
return nil, err return nil, err
} }
if p, ok := peer.FromContext(ctx); ok {
if tcpAddr, ok := p.Addr.(*net.TCPAddr); ok {
reqProps[commonschema.PropertyKeyFrostFSSourceIP] = tcpAddr.IP.String()
}
}
namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID()) namespace, err := ac.namespaceByOwner(req.GetBody().GetOwnerID())
if err != nil { if err != nil {
@ -191,6 +199,11 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
if err != nil { if err != nil {
return nil, err return nil, err
} }
if p, ok := peer.FromContext(ctx); ok {
if tcpAddr, ok := p.Addr.(*net.TCPAddr); ok {
reqProps[commonschema.PropertyKeyFrostFSSourceIP] = tcpAddr.IP.String()
}
}
namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID()) namespace, err := ac.namespaceByOwner(req.GetBody().GetContainer().GetOwnerID())
if err != nil { if err != nil {
@ -264,7 +277,7 @@ func (ac *apeChecker) SetExtendedACL(ctx context.Context, req *container.SetExte
ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.SetExtendedACL") ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.SetExtendedACL")
defer span.End() defer span.End()
if err := ac.validateContainerBoundedOperation(req.GetBody().GetEACL().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), if err := ac.validateContainerBoundedOperation(ctx, req.GetBody().GetEACL().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(),
nativeschema.MethodSetContainerEACL); err != nil { nativeschema.MethodSetContainerEACL); err != nil {
return nil, err return nil, err
} }
@ -272,7 +285,7 @@ func (ac *apeChecker) SetExtendedACL(ctx context.Context, req *container.SetExte
return ac.next.SetExtendedACL(ctx, req) return ac.next.SetExtendedACL(ctx, req)
} }
func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.ContainerID, mh *session.RequestMetaHeader, vh *session.RequestVerificationHeader, op string) error { func (ac *apeChecker) validateContainerBoundedOperation(ctx context.Context, containerID *refs.ContainerID, mh *session.RequestMetaHeader, vh *session.RequestVerificationHeader, op string) error {
if vh == nil { if vh == nil {
return errMissingVerificationHeader return errMissingVerificationHeader
} }
@ -287,7 +300,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai
return err return err
} }
reqProps, pk, err := ac.getRequestProps(mh, vh, cont, id) reqProps, pk, err := ac.getRequestProps(ctx, mh, vh, cont, id)
if err != nil { if err != nil {
return err return err
} }
@ -358,7 +371,7 @@ func (ac *apeChecker) getContainerProps(c *containercore.Container) map[string]s
} }
} }
func (ac *apeChecker) getRequestProps(mh *session.RequestMetaHeader, vh *session.RequestVerificationHeader, func (ac *apeChecker) getRequestProps(ctx context.Context, mh *session.RequestMetaHeader, vh *session.RequestVerificationHeader,
cont *containercore.Container, cnrID cid.ID, cont *containercore.Container, cnrID cid.ID,
) (map[string]string, *keys.PublicKey, error) { ) (map[string]string, *keys.PublicKey, error) {
actor, pk, err := ac.getActorAndPublicKey(mh, vh, cnrID) actor, pk, err := ac.getActorAndPublicKey(mh, vh, cnrID)
@ -377,6 +390,11 @@ func (ac *apeChecker) getRequestProps(mh *session.RequestMetaHeader, vh *session
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if p, ok := peer.FromContext(ctx); ok {
if tcpAddr, ok := p.Addr.(*net.TCPAddr); ok {
reqProps[commonschema.PropertyKeyFrostFSSourceIP] = tcpAddr.IP.String()
}
}
return reqProps, pk, nil return reqProps, pk, nil
} }

View file

@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"net"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
@ -32,6 +33,7 @@ import (
"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"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc/peer"
) )
const ( const (
@ -45,6 +47,7 @@ func TestAPE(t *testing.T) {
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 get container by user claim tag", testDenyGetContainerByUserClaimTag)
t.Run("deny get container by IP", testDenyGetContainerByIP)
t.Run("deny get container by group id", testDenyGetContainerByGroupID) t.Run("deny get container by group id", testDenyGetContainerByGroupID)
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)
@ -55,6 +58,19 @@ func TestAPE(t *testing.T) {
t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError) t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError)
} }
const (
incomingIP = "192.92.33.1"
)
func ctxWithPeerInfo() context.Context {
return peer.NewContext(context.Background(), &peer.Peer{
Addr: &net.TCPAddr{
IP: net.ParseIP(incomingIP),
Port: 41111,
},
})
}
func testAllowThenDenyGetContainerRuleDefined(t *testing.T) { func testAllowThenDenyGetContainerRuleDefined(t *testing.T) {
t.Parallel() t.Parallel()
srv := &srvStub{ srv := &srvStub{
@ -353,6 +369,105 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) {
require.ErrorAs(t, err, &errAccessDenied) require.ErrorAs(t, err, &errAccessDenied)
} }
func testDenyGetContainerByIP(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",
},
},
},
subjectsExt: map[util.Uint160]*client.SubjectExtended{
pk.PublicKey().GetScriptHash(): {
KV: map[string]string{
"tag-attr1": "value1",
"tag-attr2": "value2",
},
Groups: []*client.Group{
{
ID: 19888,
},
},
},
},
}
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{
{
Kind: chain.KindRequest,
Key: commonschema.PropertyKeyFrostFSSourceIP,
Value: incomingIP + "/16",
Op: chain.CondIPAddress,
},
},
},
},
})
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(ctxWithPeerInfo(), req)
require.Nil(t, resp)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied)
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
}
func testDenyGetContainerByGroupID(t *testing.T) { func testDenyGetContainerByGroupID(t *testing.T) {
t.Parallel() t.Parallel()
srv := &srvStub{ srv := &srvStub{
@ -1208,7 +1323,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied) aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
require.ErrorContains(t, err, aErr.Error()) require.ErrorContains(t, err, aErr.Error())
}) })
@ -1252,7 +1367,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -1295,7 +1410,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -1338,7 +1453,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -1381,7 +1496,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied) aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
require.ErrorContains(t, err, aErr.Error()) require.ErrorContains(t, err, aErr.Error())
}) })
@ -1423,7 +1538,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) {
req := initTestGetContainerRequest(t, contID) req := initTestGetContainerRequest(t, contID)
err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer) err = components.apeChecker.validateContainerBoundedOperation(ctxWithPeerInfo(), req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied) aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
require.ErrorContains(t, err, aErr.Error()) require.ErrorContains(t, err, aErr.Error())
}) })

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"net"
"strconv" "strconv"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
@ -15,8 +16,10 @@ 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" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"google.golang.org/grpc/peer"
) )
var defaultRequest = aperequest.Request{} var defaultRequest = aperequest.Request{}
@ -129,6 +132,12 @@ func (c *checkerImpl) newAPERequest(ctx context.Context, prm Prm) (aperequest.Re
return defaultRequest, err return defaultRequest, err
} }
if p, ok := peer.FromContext(ctx); ok {
if tcpAddr, ok := p.Addr.(*net.TCPAddr); ok {
reqProps[commonschema.PropertyKeyFrostFSSourceIP] = tcpAddr.IP.String()
}
}
return aperequest.NewRequest( return aperequest.NewRequest(
prm.Method, prm.Method,
aperequest.NewResource( aperequest.NewResource(

View file

@ -3,6 +3,7 @@ package ape
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"testing" "testing"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
@ -14,12 +15,24 @@ import (
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" 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"
"google.golang.org/grpc/peer"
) )
const ( const (
testOwnerID = "FPPtmAi9TCX329" testOwnerID = "FPPtmAi9TCX329"
incomingIP = "192.92.33.1"
) )
func ctxWithPeerInfo() context.Context {
return peer.NewContext(context.Background(), &peer.Peer{
Addr: &net.TCPAddr{
IP: net.ParseIP(incomingIP),
Port: 41111,
},
})
}
func TestObjectProperties(t *testing.T) { func TestObjectProperties(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
name string name string
@ -253,7 +266,7 @@ func TestNewAPERequest(t *testing.T) {
frostFSIDClient: ffidProvider, frostFSIDClient: ffidProvider,
} }
r, err := c.newAPERequest(context.TODO(), prm) r, err := c.newAPERequest(ctxWithPeerInfo(), prm)
if test.expectErr != nil { if test.expectErr != nil {
require.Error(t, err) require.Error(t, err)
require.ErrorIs(t, err, test.expectErr) require.ErrorIs(t, err, test.expectErr)
@ -276,6 +289,7 @@ func TestNewAPERequest(t *testing.T) {
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1", fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1",
fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2", fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2",
commonschema.PropertyKeyFrostFSIDGroupID: "1", commonschema.PropertyKeyFrostFSIDGroupID: "1",
commonschema.PropertyKeyFrostFSSourceIP: incomingIP,
}, },
) )

View file

@ -1,8 +1,10 @@
package tree package tree
import ( import (
"context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/converter" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/converter"
@ -14,11 +16,13 @@ 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"
"google.golang.org/grpc/peer"
) )
func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey) error { func (s *Service) checkAPE(ctx context.Context, 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")
if hasNamespace { if hasNamespace {
@ -41,6 +45,11 @@ func (s *Service) checkAPE(container *core.Container, cid cid.ID, operation acl.
if err != nil { if err != nil {
return apeErr(err) return apeErr(err)
} }
if p, ok := peer.FromContext(ctx); ok {
if tcpAddr, ok := p.Addr.(*net.TCPAddr); ok {
reqProps[commonschema.PropertyKeyFrostFSSourceIP] = tcpAddr.IP.String()
}
}
var resourceName string var resourceName string
if namespace == "root" || namespace == "" { if namespace == "root" || namespace == "" {

View file

@ -105,7 +105,7 @@ func (s *Service) Add(ctx context.Context, req *AddRequest) (*AddResponse, error
return nil, err return nil, err
} }
err := s.verifyClient(req, cid, b.GetBearerToken(), acl.OpObjectPut) err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -157,7 +157,7 @@ func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByP
return nil, err return nil, err
} }
err := s.verifyClient(req, cid, b.GetBearerToken(), acl.OpObjectPut) err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -221,7 +221,7 @@ func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveRespon
return nil, err return nil, err
} }
err := s.verifyClient(req, cid, b.GetBearerToken(), acl.OpObjectPut) err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -274,7 +274,7 @@ func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, er
return nil, err return nil, err
} }
err := s.verifyClient(req, cid, b.GetBearerToken(), acl.OpObjectPut) err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -326,7 +326,7 @@ func (s *Service) GetNodeByPath(ctx context.Context, req *GetNodeByPathRequest)
return nil, err return nil, err
} }
err := s.verifyClient(req, cid, b.GetBearerToken(), acl.OpObjectGet) err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectGet)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -408,7 +408,7 @@ func (s *Service) GetSubTree(req *GetSubTreeRequest, srv TreeService_GetSubTreeS
return err return err
} }
err := s.verifyClient(req, cid, b.GetBearerToken(), acl.OpObjectGet) err := s.verifyClient(srv.Context(), req, cid, b.GetBearerToken(), acl.OpObjectGet)
if err != nil { if err != nil {
return err return err
} }

View file

@ -2,6 +2,7 @@ package tree
import ( import (
"bytes" "bytes"
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"errors" "errors"
@ -48,7 +49,7 @@ var (
// Operation must be one of: // Operation must be one of:
// - 1. ObjectPut; // - 1. ObjectPut;
// - 2. ObjectGet. // - 2. ObjectGet.
func (s *Service) verifyClient(req message, cid cidSDK.ID, rawBearer []byte, op acl.Op) error { func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID, rawBearer []byte, op acl.Op) error {
err := verifyMessage(req) err := verifyMessage(req)
if err != nil { if err != nil {
return err return err
@ -83,7 +84,7 @@ func (s *Service) verifyClient(req message, cid cidSDK.ID, rawBearer []byte, op
// FIXME(@aarifullin): tree service temporiraly performs APE checks on // FIXME(@aarifullin): tree service temporiraly performs APE checks on
// object verbs, because tree verbs have not been introduced yet. // object verbs, because tree verbs have not been introduced yet.
if basicACL == 0x0 { if basicACL == 0x0 {
return s.checkAPE(cnr, cid, op, role, pubKey) return s.checkAPE(ctx, cnr, cid, op, role, pubKey)
} }
if !basicACL.IsOpAllowed(op, role) { if !basicACL.IsOpAllowed(op, role) {

View file

@ -1,6 +1,7 @@
package tree package tree
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/sha256" "crypto/sha256"
"errors" "errors"
@ -132,26 +133,26 @@ func TestMessageSign(t *testing.T) {
cnr.Value.SetBasicACL(acl.PublicRW) cnr.Value.SetBasicACL(acl.PublicRW)
t.Run("missing signature, no panic", func(t *testing.T) { t.Run("missing signature, no panic", func(t *testing.T) {
require.Error(t, s.verifyClient(req, cid2, nil, op)) require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
}) })
require.NoError(t, SignMessage(req, &privs[0].PrivateKey)) require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
require.NoError(t, s.verifyClient(req, cid1, nil, op)) require.NoError(t, s.verifyClient(context.Background(), req, cid1, nil, op))
t.Run("invalid CID", func(t *testing.T) { t.Run("invalid CID", func(t *testing.T) {
require.Error(t, s.verifyClient(req, cid2, nil, op)) require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
}) })
cnr.Value.SetBasicACL(acl.Private) cnr.Value.SetBasicACL(acl.Private)
t.Run("extension disabled", func(t *testing.T) { t.Run("extension disabled", func(t *testing.T) {
require.NoError(t, SignMessage(req, &privs[0].PrivateKey)) require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
require.Error(t, s.verifyClient(req, cid2, nil, op)) require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
}) })
t.Run("invalid key", func(t *testing.T) { t.Run("invalid key", func(t *testing.T) {
require.NoError(t, SignMessage(req, &privs[1].PrivateKey)) require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, nil, op)) require.Error(t, s.verifyClient(context.Background(), req, cid1, nil, op))
}) })
t.Run("bearer", func(t *testing.T) { t.Run("bearer", func(t *testing.T) {
@ -164,7 +165,7 @@ func TestMessageSign(t *testing.T) {
t.Run("invalid bearer", func(t *testing.T) { t.Run("invalid bearer", func(t *testing.T) {
req.Body.BearerToken = []byte{0xFF} req.Body.BearerToken = []byte{0xFF}
require.NoError(t, SignMessage(req, &privs[0].PrivateKey)) require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
}) })
t.Run("invalid bearer CID", func(t *testing.T) { t.Run("invalid bearer CID", func(t *testing.T) {
@ -173,7 +174,7 @@ func TestMessageSign(t *testing.T) {
req.Body.BearerToken = bt.Marshal() req.Body.BearerToken = bt.Marshal()
require.NoError(t, SignMessage(req, &privs[1].PrivateKey)) require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
}) })
t.Run("invalid bearer owner", func(t *testing.T) { t.Run("invalid bearer owner", func(t *testing.T) {
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey()) bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
@ -181,7 +182,7 @@ func TestMessageSign(t *testing.T) {
req.Body.BearerToken = bt.Marshal() req.Body.BearerToken = bt.Marshal()
require.NoError(t, SignMessage(req, &privs[1].PrivateKey)) require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
}) })
t.Run("invalid bearer signature", func(t *testing.T) { t.Run("invalid bearer signature", func(t *testing.T) {
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey()) bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
@ -193,7 +194,7 @@ func TestMessageSign(t *testing.T) {
req.Body.BearerToken = bv2.StableMarshal(nil) req.Body.BearerToken = bv2.StableMarshal(nil)
require.NoError(t, SignMessage(req, &privs[1].PrivateKey)) require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
}) })
t.Run("impersonate", func(t *testing.T) { t.Run("impersonate", func(t *testing.T) {
@ -205,8 +206,8 @@ func TestMessageSign(t *testing.T) {
req.Body.BearerToken = bt.Marshal() req.Body.BearerToken = bt.Marshal()
require.NoError(t, SignMessage(req, &privs[0].PrivateKey)) require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
require.NoError(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet)) require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
}) })
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey()) bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
@ -216,18 +217,18 @@ func TestMessageSign(t *testing.T) {
t.Run("put and get", func(t *testing.T) { t.Run("put and get", func(t *testing.T) {
require.NoError(t, SignMessage(req, &privs[1].PrivateKey)) require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
require.NoError(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
require.NoError(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet)) require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
}) })
t.Run("only get", func(t *testing.T) { t.Run("only get", func(t *testing.T) {
require.NoError(t, SignMessage(req, &privs[2].PrivateKey)) require.NoError(t, SignMessage(req, &privs[2].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
require.NoError(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet)) require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
}) })
t.Run("none", func(t *testing.T) { t.Run("none", func(t *testing.T) {
require.NoError(t, SignMessage(req, &privs[3].PrivateKey)) require.NoError(t, SignMessage(req, &privs[3].PrivateKey))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
require.Error(t, s.verifyClient(req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet)) require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
}) })
}) })
} }