Skip to content

Commit

Permalink
node/container: check session tokens on SN side
Browse files Browse the repository at this point in the history
Closes #2898.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
  • Loading branch information
carpawell committed Jul 30, 2024
1 parent 8448ff3 commit 9f67a9f
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Changelog for NeoFS Node

### Fixed
- Control service's Drop call does not clean metabase (#2822)
- Container session token's lifetime was not checked (#2898)

### Changed
- neofs-cli allows several objects deletion at a time (#2774)
Expand Down
2 changes: 1 addition & 1 deletion cmd/neofs-node/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func initContainerService(c *cfg) {
&c.key.PrivateKey,
containerService.NewResponseService(
&usedSpaceService{
Server: containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt)),
Server: containerService.NewExecutionService(containerMorph.NewExecutor(cnrRdr, cnrWrt, c.networkState)),
loadWriterProvider: loadRouter,
loadPlacementBuilder: loadPlacementBuilder,
routeBuilder: routeBuilder,
Expand Down
30 changes: 25 additions & 5 deletions pkg/services/container/morph/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
sessionV2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-api-go/v2/util/signature"
containercore "github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
containerSvc "github.com/nspcc-dev/neofs-node/pkg/services/container"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
Expand All @@ -22,8 +24,9 @@ import (
)

type morphExecutor struct {
rdr Reader
wrt Writer
rdr Reader
wrt Writer
epocher netmap.State
}

// Reader is an interface of read-only container storage.
Expand All @@ -47,10 +50,11 @@ type Writer interface {
PutEACL(containercore.EACL) error
}

func NewExecutor(rdr Reader, wrt Writer) containerSvc.ServiceExecutor {
func NewExecutor(rdr Reader, wrt Writer, epocher netmap.State) containerSvc.ServiceExecutor {
return &morphExecutor{
rdr: rdr,
wrt: wrt,
rdr: rdr,
wrt: wrt,
epocher: epocher,
}
}

Expand Down Expand Up @@ -337,6 +341,22 @@ func (s *morphExecutor) validateToken(t *sessionV2.Token, cIDV2 *refs.ContainerI
return fmt.Errorf("incorrect token signature: %w", err)
}

if lt := t.GetBody().GetLifetime(); lt != nil {
currEpoch := s.epocher.CurrentEpoch()
exp := t.GetBody().GetLifetime().GetExp()
iat := t.GetBody().GetLifetime().GetIat()
nbf := t.GetBody().GetLifetime().GetNbf()

switch {
case exp < currEpoch:
return apistatus.SessionTokenExpired{}
case iat > currEpoch:
return fmt.Errorf("token should not be issued yet: IAt: %d, current epoch: %d", iat, currEpoch)
case nbf > currEpoch:
return fmt.Errorf("token is not valid yet: NBf: %d, current epoch: %d", nbf, currEpoch)
}
}

if cIDV2 == nil { // can be nil for PUT
return nil
}
Expand Down
69 changes: 59 additions & 10 deletions pkg/services/container/morph/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
containerCore "github.com/nspcc-dev/neofs-node/pkg/core/container"
containerSvc "github.com/nspcc-dev/neofs-node/pkg/services/container"
containerSvcMorph "github.com/nspcc-dev/neofs-node/pkg/services/container/morph"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
containerSDK "github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
Expand All @@ -29,8 +30,19 @@ import (
"github.com/stretchr/testify/require"
)

const (
iat = 10
nbf = 20
exp = 30
)

type mock struct {
cnr containerSDK.Container
cnr containerSDK.Container
epoch uint64
}

func (m mock) CurrentEpoch() uint64 {
return m.epoch
}

func (m mock) Get(_ cid.ID) (*containerCore.Container, error) {
Expand Down Expand Up @@ -81,6 +93,10 @@ func TestExecutor(t *testing.T) {
}

tok := sessiontest.Container()
// sdk generates broken chronologic
tok.SetIat(iat)
tok.SetNbf(nbf)
tok.SetExp(exp)
tok.ApplyOnlyTo(cnr)
require.NoError(t, tok.Sign(signer))

Expand All @@ -90,8 +106,8 @@ func TestExecutor(t *testing.T) {
realContainer := containertest.Container(t)
realContainer.SetOwner(tok.Issuer())

m := mock{cnr: realContainer}
e := containerSvcMorph.NewExecutor(m, m)
m := mock{cnr: realContainer, epoch: 25}
e := containerSvcMorph.NewExecutor(m, m, m)

tests := []struct {
name string
Expand Down Expand Up @@ -173,6 +189,10 @@ func TestValidateToken(t *testing.T) {
signer := user.NewAutoIDSigner(priv.PrivateKey)

tok := sessiontest.Container()
// sdk generates broken chronologic
tok.SetIat(iat)
tok.SetNbf(nbf)
tok.SetExp(exp)
tok.ApplyOnlyTo(cID)
tok.ForVerb(sessionsdk.VerbContainerDelete)
require.NoError(t, tok.Sign(signer))
Expand All @@ -183,8 +203,8 @@ func TestValidateToken(t *testing.T) {
var cnrV2 container.Container
cnr.WriteToV2(&cnrV2)

m := mock{cnr: cnr}
e := containerSvcMorph.NewExecutor(m, m)
m := &mock{cnr: cnr}
e := containerSvcMorph.NewExecutor(m, m, m)

t.Run("non-container token", func(t *testing.T) {
var reqBody container.DeleteRequestBody
Expand Down Expand Up @@ -294,9 +314,9 @@ func TestValidateToken(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)

tok.SetExp(11)
tok.SetNbf(22)
tok.SetIat(33)
tok.SetIat(iat)
tok.SetNbf(nbf)
tok.SetExp(exp)
tok.ForVerb(sessionsdk.VerbContainerDelete)
tok.SetID(uuid.New())
tok.SetAuthKey((*neofsecdsa.PublicKey)(&priv.PublicKey))
Expand All @@ -305,8 +325,8 @@ func TestValidateToken(t *testing.T) {
var tokV2 session.Token
tok.WriteToV2(&tokV2)

m := &mock{cnr: cnr}
e := containerSvcMorph.NewExecutor(m, m)
m := &mock{cnr: cnr, epoch: 25}
e := containerSvcMorph.NewExecutor(m, m, m)

t.Run("wrong owner", func(t *testing.T) {
m.cnr = containertest.Container(t)
Expand All @@ -322,6 +342,35 @@ func TestValidateToken(t *testing.T) {
require.NoError(t, err)
})
})

t.Run("token lifetime", func(t *testing.T) {
var reqBody container.DeleteRequestBody
reqBody.SetContainerID(&cIDV2)

var tokV2 session.Token
tok.WriteToV2(&tokV2)

t.Run("IAt too big", func(t *testing.T) {
m.epoch = iat - 1

_, err = e.Delete(context.TODO(), &tokV2, &reqBody)
require.ErrorContains(t, err, "should not be issued yet")
})

t.Run("NBf too small", func(t *testing.T) {
m.epoch = nbf - 1

_, err = e.Delete(context.TODO(), &tokV2, &reqBody)
require.ErrorContains(t, err, "not valid yet")
})

t.Run("expired", func(t *testing.T) {
m.epoch = exp + 1

_, err = e.Delete(context.TODO(), &tokV2, &reqBody)
require.ErrorAs(t, err, new(apistatus.SessionTokenExpired))
})
})
}

func generateToken(t *testing.T, ctx session.TokenContext, signer user.Signer) *session.Token {
Expand Down

0 comments on commit 9f67a9f

Please sign in to comment.