diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b46745..b76c77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog This is the changelog for NeoFS Proto +## [0.2.8] - 2019-12-21 + +### Added +- Container access control type definitions + +### Changed +- Used sync.Pool for Sign/VerifyRequestHeader +- VerifiableRequest.Marshal method replace with MarshalTo and Size + ## [0.2.7] - 2019-12-17 ### Fixed @@ -78,3 +87,4 @@ Initial public release [0.2.5]: https://github.com/nspcc-dev/neofs-proto/compare/v0.2.4...v0.2.5 [0.2.6]: https://github.com/nspcc-dev/neofs-proto/compare/v0.2.5...v0.2.6 [0.2.7]: https://github.com/nspcc-dev/neofs-proto/compare/v0.2.6...v0.2.7 +[0.2.8]: https://github.com/nspcc-dev/neofs-proto/compare/v0.2.7...v0.2.8 diff --git a/container/service.pb.go b/container/service.pb.go index d9febc0..cac8764 100644 Binary files a/container/service.pb.go and b/container/service.pb.go differ diff --git a/container/service.proto b/container/service.proto index 8a3f56c..9bd02c9 100644 --- a/container/service.proto +++ b/container/service.proto @@ -41,6 +41,9 @@ message PutRequest { // Rules define storage policy for the object inside the container. netmap.PlacementRule rules = 4 [(gogoproto.nullable) = false]; + // Container ACL. + AccessGroup Group = 5 [(gogoproto.nullable) = false]; + // RequestMetaHeader contains information about request meta headers (should be embedded into message) service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; // RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) diff --git a/container/types.go b/container/types.go index 9269f30..d5d5a77 100644 --- a/container/types.go +++ b/container/types.go @@ -11,6 +11,19 @@ import ( "github.com/pkg/errors" ) +// AccessMode is a container access mode type. +type AccessMode uint32 + +const ( + // AccessModeRead is a read access mode. + AccessModeRead AccessMode = 1 << iota + // AccessModeWrite is a write access mode. + AccessModeWrite +) + +// AccessModeReadWrite is a read/write container access mode. +const AccessModeReadWrite = AccessModeRead | AccessModeWrite + var ( _ internal.Custom = (*Container)(nil) diff --git a/container/types.pb.go b/container/types.pb.go index 7bf9a03..7a454de 100644 Binary files a/container/types.pb.go and b/container/types.pb.go differ diff --git a/container/types.proto b/container/types.proto index a601edd..4600046 100644 --- a/container/types.proto +++ b/container/types.proto @@ -17,4 +17,18 @@ message Container { uint64 Capacity = 3; // Rules define storage policy for the object inside the container. netmap.PlacementRule Rules = 4 [(gogoproto.nullable) = false]; + // Container ACL. + AccessControlList List = 5 [(gogoproto.nullable) = false]; +} + +message AccessGroup { + // Group access mode. + uint32 AccessMode = 1; + // Group members. + repeated bytes UserGroup = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; +} + +message AccessControlList { + // List of access groups. + repeated AccessGroup List = 1 [(gogoproto.nullable) = false]; } diff --git a/container/types_test.go b/container/types_test.go index c7dbbf8..cfd5f52 100644 --- a/container/types_test.go +++ b/container/types_test.go @@ -55,3 +55,23 @@ func TestCID(t *testing.T) { require.Equal(t, cid1, cid2) }) } + +func TestAccessMode(t *testing.T) { + t.Run("read access to read/write mode", func(t *testing.T) { + require.Equal(t, AccessModeRead, AccessModeReadWrite&AccessModeRead) + }) + + t.Run("write access to read/write mode", func(t *testing.T) { + require.Equal(t, AccessModeWrite, AccessModeReadWrite&AccessModeWrite) + }) + + t.Run("read(write) access to write(read) mode", func(t *testing.T) { + require.Zero(t, AccessModeRead&AccessModeWrite) + }) + + t.Run("access to same mode", func(t *testing.T) { + require.Equal(t, AccessModeWrite, AccessModeWrite&AccessModeWrite) + require.Equal(t, AccessModeRead, AccessModeRead&AccessModeRead) + require.Equal(t, AccessModeReadWrite, AccessModeReadWrite&AccessModeReadWrite) + }) +} diff --git a/go.mod b/go.mod index fd114d3..bb8c4ff 100644 --- a/go.mod +++ b/go.mod @@ -18,3 +18,6 @@ require ( golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 google.golang.org/grpc v1.24.0 ) + +// Used for debug reasons +// replace github.com/nspcc-dev/neofs-crypto => ../neofs-crypto diff --git a/service/verify.go b/service/verify.go index 8571459..2bd1661 100644 --- a/service/verify.go +++ b/service/verify.go @@ -2,6 +2,7 @@ package service import ( "crypto/ecdsa" + "sync" crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-proto/internal" @@ -12,7 +13,8 @@ import ( type ( // VerifiableRequest adds possibility to sign and verify request header. VerifiableRequest interface { - Marshal() ([]byte, error) + Size() int + MarshalTo([]byte) (int, error) AddSignature(*RequestVerificationHeader_Signature) GetSignatures() []*RequestVerificationHeader_Signature SetSignatures([]*RequestVerificationHeader_Signature) @@ -133,6 +135,10 @@ func newSignature(key *ecdsa.PrivateKey, data []byte) (*RequestVerificationHeade }, nil } +var bytesPool = sync.Pool{New: func() interface{} { + return make([]byte, 4.5*1024*1024) // 4.5MB +}} + // SignRequestHeader receives private key and request with RequestVerificationHeader, // tries to marshal and sign request with passed PrivateKey, after that adds // new signature to headers. If something went wrong, returns error. @@ -146,12 +152,23 @@ func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error { }() } - data, err := msg.Marshal() + data := bytesPool.Get().([]byte) + defer func() { + bytesPool.Put(data) + }() + + if size := msg.Size(); size <= cap(data) { + data = data[:size] + } else { + data = make([]byte, size) + } + + size, err := msg.MarshalTo(data) if err != nil { return err } - signature, err := newSignature(key, data) + signature, err := newSignature(key, data[:size]) if err != nil { return err } @@ -174,8 +191,10 @@ func VerifyRequestHeader(msg VerifiableRequest) error { }() } + data := bytesPool.Get().([]byte) signatures := msg.GetSignatures() defer func() { + bytesPool.Put(data) msg.SetSignatures(signatures) }() @@ -189,9 +208,15 @@ func VerifyRequestHeader(msg VerifiableRequest) error { return errors.Wrapf(ErrCannotLoadPublicKey, "%d: %02x", i, peer) } - if data, err := msg.Marshal(); err != nil { + if size := msg.Size(); size <= cap(data) { + data = data[:size] + } else { + data = make([]byte, size) + } + + if size, err := msg.MarshalTo(data); err != nil { return errors.Wrapf(err, "%d: %02x", i, peer) - } else if err := crypto.Verify(key, data, sign); err != nil { + } else if err := crypto.Verify(key, data[:size], sign); err != nil { return errors.Wrapf(err, "%d: %02x", i, peer) } } diff --git a/service/verify_test.go b/service/verify_test.go index 237e362..44542c4 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -14,6 +14,57 @@ import ( "github.com/stretchr/testify/require" ) +func BenchmarkSignRequestHeader(b *testing.B) { + key := test.DecodeKey(0) + + custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} + + some := &TestRequest{ + IntField: math.MaxInt32, + StringField: "TestRequestStringField", + BytesField: make([]byte, 1<<22), + CustomField: &custom, + RequestMetaHeader: RequestMetaHeader{ + TTL: math.MaxInt32 - 8, + Epoch: math.MaxInt64 - 12, + }, + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + require.NoError(b, SignRequestHeader(key, some)) + } +} + +func BenchmarkVerifyRequestHeader(b *testing.B) { + custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} + + some := &TestRequest{ + IntField: math.MaxInt32, + StringField: "TestRequestStringField", + BytesField: make([]byte, 1<<22), + CustomField: &custom, + RequestMetaHeader: RequestMetaHeader{ + TTL: math.MaxInt32 - 8, + Epoch: math.MaxInt64 - 12, + }, + } + + for i := 0; i < 10; i++ { + key := test.DecodeKey(i) + require.NoError(b, SignRequestHeader(key, some)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + require.NoError(b, VerifyRequestHeader(some)) + } +} + func TestSignRequestHeader(t *testing.T) { req := &TestRequest{ IntField: math.MaxInt32,