forked from TrueCloudLab/frostfs-node
[#1286] services/object: Validate object when created with sesssion token
All fields set by a user should be verified. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
c6a9c5cd8c
commit
5736b834c3
4 changed files with 89 additions and 71 deletions
|
@ -83,12 +83,13 @@ func NewFormatValidator(opts ...FormatValidatorOption) *FormatValidator {
|
||||||
// Validate validates object format.
|
// Validate validates object format.
|
||||||
//
|
//
|
||||||
// Does not validate payload checksum and content.
|
// Does not validate payload checksum and content.
|
||||||
|
// If unprepared is true, only fields set by user are validated.
|
||||||
//
|
//
|
||||||
// Returns nil error if object has valid structure.
|
// Returns nil error if object has valid structure.
|
||||||
func (v *FormatValidator) Validate(obj *object.Object) error {
|
func (v *FormatValidator) Validate(obj *object.Object, unprepared bool) error {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return errNilObject
|
return errNilObject
|
||||||
} else if obj.ID() == nil {
|
} else if !unprepared && obj.ID() == nil {
|
||||||
return errNilID
|
return errNilID
|
||||||
} else if obj.ContainerID() == nil {
|
} else if obj.ContainerID() == nil {
|
||||||
return errNilCID
|
return errNilCID
|
||||||
|
@ -102,20 +103,23 @@ func (v *FormatValidator) Validate(obj *object.Object) error {
|
||||||
return fmt.Errorf("invalid attributes: %w", err)
|
return fmt.Errorf("invalid attributes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := v.validateSignatureKey(obj); err != nil {
|
if !unprepared {
|
||||||
return fmt.Errorf("(%T) could not validate signature key: %w", v, err)
|
if err := v.validateSignatureKey(obj); err != nil {
|
||||||
}
|
return fmt.Errorf("(%T) could not validate signature key: %w", v, err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := v.checkExpiration(obj); err != nil {
|
if err := v.checkExpiration(obj); err != nil {
|
||||||
return fmt.Errorf("object did not pass expiration check: %w", err)
|
return fmt.Errorf("object did not pass expiration check: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := object.CheckHeaderVerificationFields(obj); err != nil {
|
if err := object.CheckHeaderVerificationFields(obj); err != nil {
|
||||||
return fmt.Errorf("(%T) could not validate header fields: %w", v, err)
|
return fmt.Errorf("(%T) could not validate header fields: %w", v, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj = obj.Parent(); obj != nil {
|
if obj = obj.Parent(); obj != nil {
|
||||||
return v.Validate(obj)
|
// Parent object already exists.
|
||||||
|
return v.Validate(obj, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -64,20 +64,20 @@ func TestFormatValidator_Validate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("nil input", func(t *testing.T) {
|
t.Run("nil input", func(t *testing.T) {
|
||||||
require.Error(t, v.Validate(nil))
|
require.Error(t, v.Validate(nil, true))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("nil identifier", func(t *testing.T) {
|
t.Run("nil identifier", func(t *testing.T) {
|
||||||
obj := object.New()
|
obj := object.New()
|
||||||
|
|
||||||
require.True(t, errors.Is(v.Validate(obj), errNilID))
|
require.True(t, errors.Is(v.Validate(obj, false), errNilID))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("nil container identifier", func(t *testing.T) {
|
t.Run("nil container identifier", func(t *testing.T) {
|
||||||
obj := object.New()
|
obj := object.New()
|
||||||
obj.SetID(testObjectID(t))
|
obj.SetID(testObjectID(t))
|
||||||
|
|
||||||
require.True(t, errors.Is(v.Validate(obj), errNilCID))
|
require.True(t, errors.Is(v.Validate(obj, true), errNilCID))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("unsigned object", func(t *testing.T) {
|
t.Run("unsigned object", func(t *testing.T) {
|
||||||
|
@ -85,7 +85,7 @@ func TestFormatValidator_Validate(t *testing.T) {
|
||||||
obj.SetContainerID(cidtest.ID())
|
obj.SetContainerID(cidtest.ID())
|
||||||
obj.SetID(testObjectID(t))
|
obj.SetID(testObjectID(t))
|
||||||
|
|
||||||
require.Error(t, v.Validate(obj))
|
require.Error(t, v.Validate(obj, true))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("correct w/ session token", func(t *testing.T) {
|
t.Run("correct w/ session token", func(t *testing.T) {
|
||||||
|
@ -101,7 +101,7 @@ func TestFormatValidator_Validate(t *testing.T) {
|
||||||
|
|
||||||
require.NoError(t, object.SetIDWithSignature(&ownerKey.PrivateKey, obj))
|
require.NoError(t, object.SetIDWithSignature(&ownerKey.PrivateKey, obj))
|
||||||
|
|
||||||
require.NoError(t, v.Validate(obj))
|
require.NoError(t, v.Validate(obj, false))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("correct w/o session token", func(t *testing.T) {
|
t.Run("correct w/o session token", func(t *testing.T) {
|
||||||
|
@ -109,7 +109,7 @@ func TestFormatValidator_Validate(t *testing.T) {
|
||||||
|
|
||||||
require.NoError(t, object.SetIDWithSignature(&ownerKey.PrivateKey, obj))
|
require.NoError(t, object.SetIDWithSignature(&ownerKey.PrivateKey, obj))
|
||||||
|
|
||||||
require.NoError(t, v.Validate(obj))
|
require.NoError(t, v.Validate(obj, false))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("tombstone content", func(t *testing.T) {
|
t.Run("tombstone content", func(t *testing.T) {
|
||||||
|
@ -197,19 +197,19 @@ func TestFormatValidator_Validate(t *testing.T) {
|
||||||
|
|
||||||
t.Run("invalid attribute value", func(t *testing.T) {
|
t.Run("invalid attribute value", func(t *testing.T) {
|
||||||
val := "text"
|
val := "text"
|
||||||
err := v.Validate(fn(val))
|
err := v.Validate(fn(val), false)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("expired object", func(t *testing.T) {
|
t.Run("expired object", func(t *testing.T) {
|
||||||
val := strconv.FormatUint(curEpoch-1, 10)
|
val := strconv.FormatUint(curEpoch-1, 10)
|
||||||
err := v.Validate(fn(val))
|
err := v.Validate(fn(val), false)
|
||||||
require.True(t, errors.Is(err, errExpired))
|
require.True(t, errors.Is(err, errExpired))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("alive object", func(t *testing.T) {
|
t.Run("alive object", func(t *testing.T) {
|
||||||
val := strconv.FormatUint(curEpoch, 10)
|
val := strconv.FormatUint(curEpoch, 10)
|
||||||
err := v.Validate(fn(val))
|
err := v.Validate(fn(val), true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -94,18 +94,21 @@ func (p *Streamer) initTarget(prm *PutInitPrm) error {
|
||||||
if sToken == nil && !prm.hdr.OwnerID().Equal(owner.NewIDFromPublicKey(&sessionKey.PublicKey)) {
|
if sToken == nil && !prm.hdr.OwnerID().Equal(owner.NewIDFromPublicKey(&sessionKey.PublicKey)) {
|
||||||
return fmt.Errorf("(%T) session token is missing but object owner id is different from the default key", p)
|
return fmt.Errorf("(%T) session token is missing but object owner id is different from the default key", p)
|
||||||
}
|
}
|
||||||
|
p.target = &validatingTarget{
|
||||||
p.target = transformer.NewPayloadSizeLimiter(
|
fmt: p.fmtValidator,
|
||||||
p.maxPayloadSz,
|
unpreparedObject: true,
|
||||||
func() transformer.ObjectTarget {
|
nextTarget: transformer.NewPayloadSizeLimiter(
|
||||||
return transformer.NewFormatTarget(&transformer.FormatterParams{
|
p.maxPayloadSz,
|
||||||
Key: sessionKey,
|
func() transformer.ObjectTarget {
|
||||||
NextTarget: p.newCommonTarget(prm),
|
return transformer.NewFormatTarget(&transformer.FormatterParams{
|
||||||
SessionToken: sToken,
|
Key: sessionKey,
|
||||||
NetworkState: p.networkState,
|
NextTarget: p.newCommonTarget(prm),
|
||||||
})
|
SessionToken: sToken,
|
||||||
},
|
NetworkState: p.networkState,
|
||||||
)
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,14 @@ import (
|
||||||
"github.com/nspcc-dev/tzhash/tz"
|
"github.com/nspcc-dev/tzhash/tz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// validatingTarget validates object format and content.
|
||||||
type validatingTarget struct {
|
type validatingTarget struct {
|
||||||
nextTarget transformer.ObjectTarget
|
nextTarget transformer.ObjectTarget
|
||||||
|
|
||||||
fmt *object.FormatValidator
|
fmt *object.FormatValidator
|
||||||
|
|
||||||
|
unpreparedObject bool
|
||||||
|
|
||||||
hash hash.Hash
|
hash hash.Hash
|
||||||
|
|
||||||
checksum []byte
|
checksum []byte
|
||||||
|
@ -40,29 +43,31 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error {
|
||||||
t.payloadSz = obj.PayloadSize()
|
t.payloadSz = obj.PayloadSize()
|
||||||
chunkLn := uint64(len(obj.Payload()))
|
chunkLn := uint64(len(obj.Payload()))
|
||||||
|
|
||||||
// check chunk size
|
if !t.unpreparedObject {
|
||||||
if chunkLn > t.payloadSz {
|
// check chunk size
|
||||||
return ErrWrongPayloadSize
|
if chunkLn > t.payloadSz {
|
||||||
|
return ErrWrongPayloadSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// check payload size limit
|
||||||
|
if t.payloadSz > t.maxPayloadSz {
|
||||||
|
return ErrExceedingMaxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := obj.PayloadChecksum()
|
||||||
|
switch typ := cs.Type(); typ {
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("(%T) unsupported payload checksum type %v", t, typ)
|
||||||
|
case checksum.SHA256:
|
||||||
|
t.hash = sha256.New()
|
||||||
|
case checksum.TZ:
|
||||||
|
t.hash = tz.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.checksum = cs.Sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
// check payload size limit
|
if err := t.fmt.Validate(obj, t.unpreparedObject); err != nil {
|
||||||
if t.payloadSz > t.maxPayloadSz {
|
|
||||||
return ErrExceedingMaxSize
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := obj.PayloadChecksum()
|
|
||||||
switch typ := cs.Type(); typ {
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("(%T) unsupported payload checksum type %v", t, typ)
|
|
||||||
case checksum.SHA256:
|
|
||||||
t.hash = sha256.New()
|
|
||||||
case checksum.TZ:
|
|
||||||
t.hash = tz.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
t.checksum = cs.Sum()
|
|
||||||
|
|
||||||
if err := t.fmt.Validate(obj); err != nil {
|
|
||||||
return fmt.Errorf("(%T) coult not validate object format: %w", t, err)
|
return fmt.Errorf("(%T) coult not validate object format: %w", t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +76,13 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update written bytes
|
if !t.unpreparedObject {
|
||||||
//
|
// update written bytes
|
||||||
// Note: we MUST NOT add obj.PayloadSize() since obj
|
//
|
||||||
// can carry only the chunk of the full payload
|
// Note: we MUST NOT add obj.PayloadSize() since obj
|
||||||
t.writtenPayload += chunkLn
|
// can carry only the chunk of the full payload
|
||||||
|
t.writtenPayload += chunkLn
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -83,14 +90,16 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error {
|
||||||
func (t *validatingTarget) Write(p []byte) (n int, err error) {
|
func (t *validatingTarget) Write(p []byte) (n int, err error) {
|
||||||
chunkLn := uint64(len(p))
|
chunkLn := uint64(len(p))
|
||||||
|
|
||||||
// check if new chunk will overflow payload size
|
if !t.unpreparedObject {
|
||||||
if t.writtenPayload+chunkLn > t.payloadSz {
|
// check if new chunk will overflow payload size
|
||||||
return 0, ErrWrongPayloadSize
|
if t.writtenPayload+chunkLn > t.payloadSz {
|
||||||
}
|
return 0, ErrWrongPayloadSize
|
||||||
|
}
|
||||||
|
|
||||||
_, err = t.hash.Write(p)
|
_, err = t.hash.Write(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = t.nextTarget.Write(p)
|
n, err = t.nextTarget.Write(p)
|
||||||
|
@ -102,13 +111,15 @@ func (t *validatingTarget) Write(p []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *validatingTarget) Close() (*transformer.AccessIdentifiers, error) {
|
func (t *validatingTarget) Close() (*transformer.AccessIdentifiers, error) {
|
||||||
// check payload size correctness
|
if !t.unpreparedObject {
|
||||||
if t.payloadSz != t.writtenPayload {
|
// check payload size correctness
|
||||||
return nil, ErrWrongPayloadSize
|
if t.payloadSz != t.writtenPayload {
|
||||||
}
|
return nil, ErrWrongPayloadSize
|
||||||
|
}
|
||||||
|
|
||||||
if !bytes.Equal(t.hash.Sum(nil), t.checksum) {
|
if !bytes.Equal(t.hash.Sum(nil), t.checksum) {
|
||||||
return nil, fmt.Errorf("(%T) incorrect payload checksum", t)
|
return nil, fmt.Errorf("(%T) incorrect payload checksum", t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.nextTarget.Close()
|
return t.nextTarget.Close()
|
||||||
|
|
Loading…
Reference in a new issue