From 38727c2930fc07f859ed2656a1d2639a1d9bd2a3 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 15 Feb 2021 11:28:42 +0300 Subject: [PATCH] [#368] object: Reject expired objects The lifetime of an object can be limited by specifying a correspondin well-known attribute. Node should refuse to save expired objects. Checking objects in FormatValidator is extended with an expiration attribute parsing step. Signed-off-by: Leonard Lyubich --- pkg/core/object/fmt.go | 42 ++++++++++++++++++++++++ pkg/core/object/fmt_test.go | 52 +++++++++++++++++++++++++++++- pkg/services/object/put/service.go | 1 + 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/pkg/core/object/fmt.go b/pkg/core/object/fmt.go index dacf79b42..9440dd465 100644 --- a/pkg/core/object/fmt.go +++ b/pkg/core/object/fmt.go @@ -2,11 +2,14 @@ package object import ( "bytes" + "strconv" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/storagegroup" + objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/pkg/errors" ) @@ -20,6 +23,8 @@ type FormatValidatorOption func(*cfg) type cfg struct { deleteHandler DeleteHandler + + netState netmap.State } // DeleteHandler is an interface of delete queue processor. @@ -69,6 +74,11 @@ func (v *FormatValidator) Validate(obj *Object) error { return errors.Wrapf(err, "(%T) could not validate signature key", v) } + // TODO: combine small checks + if err := v.checkExpiration(obj); err != nil { + return errors.Wrapf(err, "object did not pass expiration check") + } + if err := object.CheckHeaderVerificationFields(obj.SDK()); err != nil { return errors.Wrapf(err, "(%T) could not validate header fields", v) } @@ -164,6 +174,38 @@ func (v *FormatValidator) ValidateContent(o *Object) error { return nil } +var errExpired = errors.New("object has expired") + +func (v *FormatValidator) checkExpiration(obj *Object) error { + for _, a := range obj.Attributes() { + if a.Key() != objectV2.SysAttributeExpEpoch { + continue + } + + exp, err := strconv.ParseUint(a.Value(), 10, 64) + if err != nil { + return err + } + + if exp < v.netState.CurrentEpoch() { + return errExpired + } + + break + } + + return nil +} + +// WithNetState returns options to set network state interface. +// +// FIXME: network state is a required parameter. +func WithNetState(netState netmap.State) FormatValidatorOption { + return func(c *cfg) { + c.netState = netState + } +} + // WithDeleteHandler returns option to set delete queue processor. func WithDeleteHandler(v DeleteHandler) FormatValidatorOption { return func(c *cfg) { diff --git a/pkg/core/object/fmt_test.go b/pkg/core/object/fmt_test.go index 6ce4b4f14..a2c00aea1 100644 --- a/pkg/core/object/fmt_test.go +++ b/pkg/core/object/fmt_test.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/rand" "crypto/sha256" + "strconv" "testing" "github.com/nspcc-dev/neofs-api-go/pkg/container" @@ -11,6 +12,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/storagegroup" "github.com/nspcc-dev/neofs-api-go/pkg/token" + objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-node/pkg/util/test" "github.com/pkg/errors" @@ -54,8 +56,22 @@ func blankValidObject(t *testing.T, key *ecdsa.PrivateKey) *RawObject { return obj } +type testNetState struct { + epoch uint64 +} + +func (s testNetState) CurrentEpoch() uint64 { + return s.epoch +} + func TestFormatValidator_Validate(t *testing.T) { - v := NewFormatValidator() + const curEpoch = 13 + + v := NewFormatValidator( + WithNetState(testNetState{ + epoch: curEpoch, + }), + ) ownerKey := test.DecodeKey(-1) @@ -156,4 +172,38 @@ func TestFormatValidator_Validate(t *testing.T) { require.NoError(t, v.ValidateContent(obj.Object())) }) + + t.Run("expiration", func(t *testing.T) { + fn := func(val string) *Object { + obj := blankValidObject(t, ownerKey) + + a := object.NewAttribute() + a.SetKey(objectV2.SysAttributeExpEpoch) + a.SetValue(val) + + obj.SetAttributes(a) + + require.NoError(t, object.SetIDWithSignature(ownerKey, obj.SDK())) + + return obj.Object() + } + + t.Run("invalid attribute value", func(t *testing.T) { + val := "text" + err := v.Validate(fn(val)) + require.Error(t, err) + }) + + t.Run("expired object", func(t *testing.T) { + val := strconv.FormatUint(curEpoch-1, 10) + err := v.Validate(fn(val)) + require.True(t, errors.Is(err, errExpired)) + }) + + t.Run("alive object", func(t *testing.T) { + val := strconv.FormatUint(curEpoch, 10) + err := v.Validate(fn(val)) + require.NoError(t, err) + }) + }) } diff --git a/pkg/services/object/put/service.go b/pkg/services/object/put/service.go index 73faad8f5..207db1c2c 100644 --- a/pkg/services/object/put/service.go +++ b/pkg/services/object/put/service.go @@ -137,6 +137,7 @@ func WithFormatValidatorOpts(v ...object.FormatValidatorOption) Option { func WithNetworkState(v netmap.State) Option { return func(c *cfg) { c.networkState = v + c.fmtValidatorOpts = append(c.fmtValidatorOpts, object.WithNetState(v)) } }