[#1142] container: Fill APE-request property with source IP

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2024-05-20 16:11:44 +03:00 committed by Evgenii Stratonikov
parent 2b02f52cd9
commit 43625e7536
2 changed files with 146 additions and 13 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())
}) })