diff --git a/pkg/services/container/ape.go b/pkg/services/container/ape.go index 22440ab80..fb1cc8121 100644 --- a/pkg/services/container/ape.go +++ b/pkg/services/container/ape.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "errors" "fmt" + "net" "strings" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" @@ -27,8 +28,10 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" 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" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "google.golang.org/grpc/peer" ) var ( @@ -88,7 +91,7 @@ func (ac *apeChecker) Delete(ctx context.Context, req *container.DeleteRequest) ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.Delete") 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 { 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") 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 { return nil, err } @@ -112,7 +115,7 @@ func (ac *apeChecker) GetExtendedACL(ctx context.Context, req *container.GetExte ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.GetExtendedACL") 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 { return nil, err } @@ -138,6 +141,11 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co if err != nil { 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()) if err != nil { @@ -191,6 +199,11 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont if err != nil { 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()) if err != nil { @@ -264,7 +277,7 @@ func (ac *apeChecker) SetExtendedACL(ctx context.Context, req *container.SetExte ctx, span := tracing.StartSpanFromContext(ctx, "apeChecker.SetExtendedACL") 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 { return nil, err } @@ -272,7 +285,7 @@ func (ac *apeChecker) SetExtendedACL(ctx context.Context, req *container.SetExte 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 { return errMissingVerificationHeader } @@ -287,7 +300,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(containerID *refs.Contai return err } - reqProps, pk, err := ac.getRequestProps(mh, vh, cont, id) + reqProps, pk, err := ac.getRequestProps(ctx, mh, vh, cont, id) if err != nil { 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, ) (map[string]string, *keys.PublicKey, error) { actor, pk, err := ac.getActorAndPublicKey(mh, vh, cnrID) @@ -377,6 +390,11 @@ func (ac *apeChecker) getRequestProps(mh *session.RequestMetaHeader, vh *session if err != nil { 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 } diff --git a/pkg/services/container/ape_test.go b/pkg/services/container/ape_test.go index 01a0fa4d2..90c0225dd 100644 --- a/pkg/services/container/ape_test.go +++ b/pkg/services/container/ape_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "net" "testing" "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/util" "github.com/stretchr/testify/require" + "google.golang.org/grpc/peer" ) const ( @@ -45,6 +47,7 @@ func TestAPE(t *testing.T) { t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound) t.Run("deny get container for others", testDenyGetContainerForOthers) 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 set container eACL for IR", testDenySetContainerEACLForIR) 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) } +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) { t.Parallel() srv := &srvStub{ @@ -353,6 +369,105 @@ func testDenyGetContainerByUserClaimTag(t *testing.T) { 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) { t.Parallel() srv := &srvStub{ @@ -1208,7 +1323,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) { 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) require.ErrorContains(t, err, aErr.Error()) }) @@ -1252,7 +1367,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) { 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) }) @@ -1295,7 +1410,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) { 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) }) @@ -1338,7 +1453,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) { 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) }) @@ -1381,7 +1496,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) { 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) require.ErrorContains(t, err, aErr.Error()) }) @@ -1423,7 +1538,7 @@ func TestValidateContainerBoundedOperation(t *testing.T) { 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) require.ErrorContains(t, err, aErr.Error()) })