From 5ea215a0fa93035fbf89e0b371c070d936e33628 Mon Sep 17 00:00:00 2001 From: Jamie Isaacs Date: Mon, 10 May 2021 15:53:47 -0700 Subject: [PATCH 1/4] #20: Added Encrypted Store decorator --- .../cryptotest/failing_event_encryptor.go | 21 ++ .../decrypting_record_iterator.go | 50 ++++ .../decrypting_record_iterator_test.go | 106 ++++++++ .../decrypting_record_subscriber.go | 26 ++ .../decrypting_record_subscriber_test.go | 94 +++++++ provider/encryptedstore/encrypted_store.go | 70 ++++++ .../encryptedstore/encrypted_store_test.go | 229 ++++++++++++++++++ 7 files changed, 596 insertions(+) create mode 100644 pkg/crypto/cryptotest/failing_event_encryptor.go create mode 100644 provider/encryptedstore/decrypting_record_iterator.go create mode 100644 provider/encryptedstore/decrypting_record_iterator_test.go create mode 100644 provider/encryptedstore/decrypting_record_subscriber.go create mode 100644 provider/encryptedstore/decrypting_record_subscriber_test.go create mode 100644 provider/encryptedstore/encrypted_store.go create mode 100644 provider/encryptedstore/encrypted_store_test.go diff --git a/pkg/crypto/cryptotest/failing_event_encryptor.go b/pkg/crypto/cryptotest/failing_event_encryptor.go new file mode 100644 index 0000000..1eb51c7 --- /dev/null +++ b/pkg/crypto/cryptotest/failing_event_encryptor.go @@ -0,0 +1,21 @@ +package cryptotest + +import ( + "fmt" + + "github.com/inklabs/rangedb" +) + +type failingEventEncryptor struct{} + +func NewFailingEventEncryptor() *failingEventEncryptor { + return &failingEventEncryptor{} +} + +func (f *failingEventEncryptor) Encrypt(_ rangedb.Event) error { + return fmt.Errorf("failingEventEncryptor:Encrypt") +} + +func (f *failingEventEncryptor) Decrypt(_ rangedb.Event) error { + return fmt.Errorf("failingEventEncryptor:Decrypt") +} diff --git a/provider/encryptedstore/decrypting_record_iterator.go b/provider/encryptedstore/decrypting_record_iterator.go new file mode 100644 index 0000000..19234c6 --- /dev/null +++ b/provider/encryptedstore/decrypting_record_iterator.go @@ -0,0 +1,50 @@ +package encryptedstore + +import ( + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto" +) + +type decryptingRecordIterator struct { + parent rangedb.RecordIterator + eventEncryptor crypto.EventEncryptor + currentErr error +} + +// NewDecryptingRecordIterator constructs a new rangedb.Record iterator that decrypts events +func NewDecryptingRecordIterator(parent rangedb.RecordIterator, eventEncryptor crypto.EventEncryptor) *decryptingRecordIterator { + return &decryptingRecordIterator{ + parent: parent, + eventEncryptor: eventEncryptor, + } +} + +func (i *decryptingRecordIterator) Next() bool { + if i.currentErr != nil { + return false + } + + return i.parent.Next() +} + +func (i *decryptingRecordIterator) Record() *rangedb.Record { + record := i.parent.Record() + + if rangedbEvent, ok := record.Data.(rangedb.Event); ok { + err := i.eventEncryptor.Decrypt(rangedbEvent) + if err != nil { + i.currentErr = err + return nil + } + } + + return record +} + +func (i *decryptingRecordIterator) Err() error { + if i.currentErr != nil { + return i.currentErr + } + + return i.parent.Err() +} diff --git a/provider/encryptedstore/decrypting_record_iterator_test.go b/provider/encryptedstore/decrypting_record_iterator_test.go new file mode 100644 index 0000000..31d6774 --- /dev/null +++ b/provider/encryptedstore/decrypting_record_iterator_test.go @@ -0,0 +1,106 @@ +package encryptedstore_test + +import ( + "testing" + + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto/aes" + "github.com/inklabs/rangedb/pkg/crypto/cryptotest" + "github.com/inklabs/rangedb/pkg/crypto/eventencryptor" + "github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore" + "github.com/inklabs/rangedb/provider/encryptedstore" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewDecryptingRecordIterator(t *testing.T) { + t.Run("decrypts one record: succeeds", func(t *testing.T) { + // Given + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + const email = "john@example.com" + encryptedEvent := &cryptotest.CustomerSignedUp{ + ID: "fa14d796bab84c9f9c2026a5324d6a34", + Name: "John Doe", + Email: email, + Status: "active", + } + require.NoError(t, eventEncryptor.Encrypt(encryptedEvent)) + record := &rangedb.Record{ + AggregateType: encryptedEvent.AggregateType(), + AggregateID: encryptedEvent.AggregateID(), + GlobalSequenceNumber: 0, + StreamSequenceNumber: 0, + EventType: encryptedEvent.EventType(), + InsertTimestamp: 0, + Data: encryptedEvent, + Metadata: nil, + } + + resultRecords := make(chan rangedb.ResultRecord, 1) + resultRecords <- rangedb.ResultRecord{ + Record: record, + Err: nil, + } + close(resultRecords) + recordIterator := rangedb.NewRecordIterator(resultRecords) + decryptingIterator := encryptedstore.NewDecryptingRecordIterator(recordIterator, eventEncryptor) + + // When + assert.True(t, decryptingIterator.Next()) + + // Then + require.NoError(t, decryptingIterator.Err()) + actualRecord := decryptingIterator.Record() + assert.Equal(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email) + assert.False(t, decryptingIterator.Next()) + }) + + t.Run("decrypt two records: fails on first record", func(t *testing.T) { + // Given + failingEventEncryptor := cryptotest.NewFailingEventEncryptor() + const email = "john@example.com" + encryptedEvent := &cryptotest.CustomerSignedUp{ + ID: "fa14d796bab84c9f9c2026a5324d6a34", + Name: "John Doe", + Email: email, + Status: "active", + } + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + require.NoError(t, eventEncryptor.Encrypt(encryptedEvent)) + record := &rangedb.Record{ + AggregateType: encryptedEvent.AggregateType(), + AggregateID: encryptedEvent.AggregateID(), + GlobalSequenceNumber: 0, + StreamSequenceNumber: 0, + EventType: encryptedEvent.EventType(), + InsertTimestamp: 0, + Data: encryptedEvent, + Metadata: nil, + } + + resultRecords := make(chan rangedb.ResultRecord, 2) + resultRecords <- rangedb.ResultRecord{ + Record: record, + Err: nil, + } + resultRecords <- rangedb.ResultRecord{ + Record: record, + Err: nil, + } + close(resultRecords) + recordIterator := rangedb.NewRecordIterator(resultRecords) + decryptingIterator := encryptedstore.NewDecryptingRecordIterator(recordIterator, failingEventEncryptor) + + // When + assert.True(t, decryptingIterator.Next()) + + // Then + assert.Nil(t, decryptingIterator.Record()) + require.EqualError(t, decryptingIterator.Err(), "failingEventEncryptor:Decrypt") + assert.False(t, decryptingIterator.Next()) + }) +} diff --git a/provider/encryptedstore/decrypting_record_subscriber.go b/provider/encryptedstore/decrypting_record_subscriber.go new file mode 100644 index 0000000..1ca9f70 --- /dev/null +++ b/provider/encryptedstore/decrypting_record_subscriber.go @@ -0,0 +1,26 @@ +package encryptedstore + +import ( + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto" +) + +type decryptingRecordSubscriber struct { + parent rangedb.RecordSubscriber + eventEncryptor crypto.EventEncryptor +} + +func NewDecryptingRecordSubscriber(parent rangedb.RecordSubscriber, eventEncryptor crypto.EventEncryptor) *decryptingRecordSubscriber { + return &decryptingRecordSubscriber{ + parent: parent, + eventEncryptor: eventEncryptor, + } +} + +func (d *decryptingRecordSubscriber) Accept(record *rangedb.Record) { + if rangedbEvent, ok := record.Data.(rangedb.Event); ok { + _ = d.eventEncryptor.Decrypt(rangedbEvent) + } + + d.parent.Accept(record) +} diff --git a/provider/encryptedstore/decrypting_record_subscriber_test.go b/provider/encryptedstore/decrypting_record_subscriber_test.go new file mode 100644 index 0000000..189c097 --- /dev/null +++ b/provider/encryptedstore/decrypting_record_subscriber_test.go @@ -0,0 +1,94 @@ +package encryptedstore_test + +import ( + "testing" + + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto/aes" + "github.com/inklabs/rangedb/pkg/crypto/cryptotest" + "github.com/inklabs/rangedb/pkg/crypto/eventencryptor" + "github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore" + "github.com/inklabs/rangedb/provider/encryptedstore" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewDecryptingRecordSubscriber(t *testing.T) { + t.Run("accept succeeds", func(t *testing.T) { + // Given + const email = "john@example.com" + encryptedEvent := &cryptotest.CustomerSignedUp{ + ID: "fa14d796bab84c9f9c2026a5324d6a34", + Name: "John Doe", + Email: email, + Status: "active", + } + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + require.NoError(t, eventEncryptor.Encrypt(encryptedEvent)) + encryptedRecord := &rangedb.Record{ + AggregateType: encryptedEvent.AggregateType(), + AggregateID: encryptedEvent.AggregateID(), + GlobalSequenceNumber: 0, + StreamSequenceNumber: 0, + EventType: encryptedEvent.EventType(), + InsertTimestamp: 0, + Data: encryptedEvent, + Metadata: nil, + } + records := make(chan *rangedb.Record, 1) + defer close(records) + parent := rangedb.RecordSubscriberFunc(func(record *rangedb.Record) { + records <- record + }) + subscriber := encryptedstore.NewDecryptingRecordSubscriber(parent, eventEncryptor) + + // When + subscriber.Accept(encryptedRecord) + + // Then + actualRecord := <-records + assert.Equal(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email) + }) + + t.Run("accept ignores decryption errors", func(t *testing.T) { + // Given + const email = "john@example.com" + encryptedEvent := &cryptotest.CustomerSignedUp{ + ID: "fa14d796bab84c9f9c2026a5324d6a34", + Name: "John Doe", + Email: email, + Status: "active", + } + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + require.NoError(t, eventEncryptor.Encrypt(encryptedEvent)) + + failingEventEncryptor := cryptotest.NewFailingEventEncryptor() + encryptedRecord := &rangedb.Record{ + AggregateType: encryptedEvent.AggregateType(), + AggregateID: encryptedEvent.AggregateID(), + GlobalSequenceNumber: 0, + StreamSequenceNumber: 0, + EventType: encryptedEvent.EventType(), + InsertTimestamp: 0, + Data: encryptedEvent, + Metadata: nil, + } + records := make(chan *rangedb.Record, 1) + defer close(records) + parent := rangedb.RecordSubscriberFunc(func(record *rangedb.Record) { + records <- record + }) + subscriber := encryptedstore.NewDecryptingRecordSubscriber(parent, failingEventEncryptor) + + // When + subscriber.Accept(encryptedRecord) + + // Then + actualRecord := <-records + assert.NotEqual(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email) + }) +} diff --git a/provider/encryptedstore/encrypted_store.go b/provider/encryptedstore/encrypted_store.go new file mode 100644 index 0000000..3528787 --- /dev/null +++ b/provider/encryptedstore/encrypted_store.go @@ -0,0 +1,70 @@ +package encryptedstore + +import ( + "context" + + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto" +) + +type encryptedStore struct { + parent rangedb.Store + eventEncryptor crypto.EventEncryptor +} + +func New(parentStore rangedb.Store, eventEncryptor crypto.EventEncryptor) *encryptedStore { + return &encryptedStore{ + parent: parentStore, + eventEncryptor: eventEncryptor, + } +} + +func (e *encryptedStore) Bind(events ...rangedb.Event) { + e.parent.Bind(events...) +} + +func (e *encryptedStore) Events(ctx context.Context, globalSequenceNumber uint64) rangedb.RecordIterator { + return NewDecryptingRecordIterator(e.parent.Events(ctx, globalSequenceNumber), e.eventEncryptor) +} + +func (e *encryptedStore) EventsByAggregateTypes(ctx context.Context, globalSequenceNumber uint64, aggregateTypes ...string) rangedb.RecordIterator { + return NewDecryptingRecordIterator(e.parent.EventsByAggregateTypes(ctx, globalSequenceNumber, aggregateTypes...), e.eventEncryptor) +} + +func (e *encryptedStore) EventsByStream(ctx context.Context, streamSequenceNumber uint64, streamName string) rangedb.RecordIterator { + return NewDecryptingRecordIterator(e.parent.EventsByStream(ctx, streamSequenceNumber, streamName), e.eventEncryptor) +} + +func (e *encryptedStore) OptimisticSave(ctx context.Context, expectedStreamSequenceNumber uint64, eventRecords ...*rangedb.EventRecord) (uint64, error) { + for _, record := range eventRecords { + err := e.eventEncryptor.Encrypt(record.Event) + if err != nil { + return 0, err + } + } + + return e.parent.OptimisticSave(ctx, expectedStreamSequenceNumber, eventRecords...) +} + +func (e *encryptedStore) Save(ctx context.Context, eventRecords ...*rangedb.EventRecord) (uint64, error) { + for _, record := range eventRecords { + err := e.eventEncryptor.Encrypt(record.Event) + if err != nil { + return 0, err + } + } + + return e.parent.Save(ctx, eventRecords...) +} + +func (e *encryptedStore) AllEventsSubscription(ctx context.Context, bufferSize int, subscriber rangedb.RecordSubscriber) rangedb.RecordSubscription { + return e.parent.AllEventsSubscription(ctx, bufferSize, NewDecryptingRecordSubscriber(subscriber, e.eventEncryptor)) +} + +func (e *encryptedStore) AggregateTypesSubscription(ctx context.Context, bufferSize int, subscriber rangedb.RecordSubscriber, aggregateTypes ...string) rangedb.RecordSubscription { + return e.parent.AggregateTypesSubscription(ctx, bufferSize, NewDecryptingRecordSubscriber(subscriber, e.eventEncryptor), aggregateTypes...) +} + +func (e *encryptedStore) TotalEventsInStream(ctx context.Context, streamName string) (uint64, error) { + return e.parent.TotalEventsInStream(ctx, streamName) +} diff --git a/provider/encryptedstore/encrypted_store_test.go b/provider/encryptedstore/encrypted_store_test.go new file mode 100644 index 0000000..8ceac99 --- /dev/null +++ b/provider/encryptedstore/encrypted_store_test.go @@ -0,0 +1,229 @@ +package encryptedstore_test + +import ( + "testing" + + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto/aes" + "github.com/inklabs/rangedb/pkg/crypto/cryptotest" + "github.com/inklabs/rangedb/pkg/crypto/eventencryptor" + "github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore" + "github.com/inklabs/rangedb/provider/encryptedstore" + "github.com/inklabs/rangedb/provider/inmemorystore" + "github.com/inklabs/rangedb/rangedbtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncryptedStore(t *testing.T) { + t.Run("OptimisticSave method encrypts, and Events method decrypts", func(t *testing.T) { + // Given + inMemoryStore := inmemorystore.New() + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + const email = "john@example.com" + event := &cryptotest.CustomerSignedUp{ + ID: "753b3e92f49b48979962fc185e644f89", + Name: "John Doe", + Email: email, + Status: "active", + } + ctx := rangedbtest.TimeoutContext(t) + + // When + _, err := encryptedStore.OptimisticSave(ctx, 0, &rangedb.EventRecord{Event: event}) + + // Then + require.NoError(t, err) + ctx = rangedbtest.TimeoutContext(t) + rawRecords := recordIteratorToSlice(t, inMemoryStore.Events(ctx, 0)) + records := recordIteratorToSlice(t, encryptedStore.Events(ctx, 0)) + require.Len(t, rawRecords, 1) + require.Len(t, records, 1) + assert.NotEqual(t, email, rawRecords[0].(*cryptotest.CustomerSignedUp).Email) + assert.Equal(t, email, records[0].(*cryptotest.CustomerSignedUp).Email) + }) + + t.Run("save method encrypts, and Events method decrypts", func(t *testing.T) { + // Given + inMemoryStore := inmemorystore.New() + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + const email = "john@example.com" + event := &cryptotest.CustomerSignedUp{ + ID: "753b3e92f49b48979962fc185e644f89", + Name: "John Doe", + Email: email, + Status: "active", + } + ctx := rangedbtest.TimeoutContext(t) + + // When + _, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event}) + + // Then + require.NoError(t, err) + ctx = rangedbtest.TimeoutContext(t) + rawRecords := recordIteratorToSlice(t, inMemoryStore.Events(ctx, 0)) + records := recordIteratorToSlice(t, encryptedStore.Events(ctx, 0)) + require.Len(t, rawRecords, 1) + require.Len(t, records, 1) + assert.NotEqual(t, email, rawRecords[0].(*cryptotest.CustomerSignedUp).Email) + assert.Equal(t, email, records[0].(*cryptotest.CustomerSignedUp).Email) + }) + + t.Run("save method encrypts, and EventsByAggregateTypes method decrypts", func(t *testing.T) { + // Given + inMemoryStore := inmemorystore.New() + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + const email = "john@example.com" + event := &cryptotest.CustomerSignedUp{ + ID: "753b3e92f49b48979962fc185e644f89", + Name: "John Doe", + Email: email, + Status: "active", + } + ctx := rangedbtest.TimeoutContext(t) + + // When + _, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event}) + + // Then + require.NoError(t, err) + ctx = rangedbtest.TimeoutContext(t) + rawRecords := recordIteratorToSlice(t, inMemoryStore.EventsByAggregateTypes(ctx, 0, event.AggregateType())) + records := recordIteratorToSlice(t, encryptedStore.EventsByAggregateTypes(ctx, 0, event.AggregateType())) + require.Len(t, rawRecords, 1) + require.Len(t, records, 1) + assert.NotEqual(t, email, rawRecords[0].(*cryptotest.CustomerSignedUp).Email) + assert.Equal(t, email, records[0].(*cryptotest.CustomerSignedUp).Email) + }) + + t.Run("save method encrypts, and EventsByStream method decrypts", func(t *testing.T) { + // Given + inMemoryStore := inmemorystore.New() + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + const email = "john@example.com" + event := &cryptotest.CustomerSignedUp{ + ID: "753b3e92f49b48979962fc185e644f89", + Name: "John Doe", + Email: email, + Status: "active", + } + ctx := rangedbtest.TimeoutContext(t) + + // When + _, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event}) + + // Then + require.NoError(t, err) + ctx = rangedbtest.TimeoutContext(t) + rawRecords := recordIteratorToSlice(t, inMemoryStore.EventsByStream(ctx, 0, rangedb.GetEventStream(event))) + records := recordIteratorToSlice(t, encryptedStore.EventsByStream(ctx, 0, rangedb.GetEventStream(event))) + require.Len(t, rawRecords, 1) + require.Len(t, records, 1) + assert.NotEqual(t, email, rawRecords[0].(*cryptotest.CustomerSignedUp).Email) + assert.Equal(t, email, records[0].(*cryptotest.CustomerSignedUp).Email) + }) + + t.Run("save method encrypts, and AllEventsSubscription method decrypts", func(t *testing.T) { + // Given + inMemoryStore := inmemorystore.New() + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + const email = "john@example.com" + event := &cryptotest.CustomerSignedUp{ + ID: "753b3e92f49b48979962fc185e644f89", + Name: "John Doe", + Email: email, + Status: "active", + } + records := make(chan *rangedb.Record) + defer close(records) + subscriber := rangedb.RecordSubscriberFunc(func(record *rangedb.Record) { + records <- record + }) + ctx := rangedbtest.TimeoutContext(t) + subscription := encryptedStore.AllEventsSubscription(ctx, 0, subscriber) + require.NoError(t, subscription.Start()) + ctx = rangedbtest.TimeoutContext(t) + + // When + _, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event}) + + // Then + require.NoError(t, err) + actualRecord := <-records + subscription.Stop() + assert.Equal(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email) + }) + + t.Run("save method encrypts, and AggregateTypesSubscription method decrypts", func(t *testing.T) { + // Given + inMemoryStore := inmemorystore.New() + aesEncryptor := aes.NewGCM() + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + const email = "john@example.com" + event := &cryptotest.CustomerSignedUp{ + ID: "753b3e92f49b48979962fc185e644f89", + Name: "John Doe", + Email: email, + Status: "active", + } + records := make(chan *rangedb.Record) + defer close(records) + subscriber := rangedb.RecordSubscriberFunc(func(record *rangedb.Record) { + records <- record + }) + ctx := rangedbtest.TimeoutContext(t) + subscription := encryptedStore.AggregateTypesSubscription(ctx, 0, subscriber, event.AggregateType()) + require.NoError(t, subscription.Start()) + ctx = rangedbtest.TimeoutContext(t) + + // When + _, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event}) + + // Then + require.NoError(t, err) + actualRecord := <-records + subscription.Stop() + assert.Equal(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email) + }) +} + +func recordIteratorToSlice(t *testing.T, recordIterator rangedb.RecordIterator) []rangedb.Event { + t.Helper() + var events []rangedb.Event + + for recordIterator.Next() { + if recordIterator.Err() != nil { + require.NoError(t, recordIterator.Err()) + return nil + } + + value := recordIterator.Record().Data.(rangedb.Event) + events = append(events, value) + } + + return events +} From 3d38a942c03295292bc382261cc29cefb83afd39 Mon Sep 17 00:00:00 2001 From: Jamie Isaacs Date: Mon, 10 May 2021 16:18:04 -0700 Subject: [PATCH 2/4] #20: Added Encrypted Store example --- examples/README.md | 1 + provider/encryptedstore/encrypt_event_test.go | 70 +++++++++++++++++++ provider/encryptedstore/helper_test.go | 24 +++++++ 3 files changed, 95 insertions(+) create mode 100644 provider/encryptedstore/encrypt_event_test.go create mode 100644 provider/encryptedstore/helper_test.go diff --git a/examples/README.md b/examples/README.md index 20649a7..284cee7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -53,3 +53,4 @@ https://verraes.net/2019/05/eventsourcing-patterns-throw-away-the-key/ * [Encrypt/Decrypt Event](../pkg/crypto/eventencryptor/encrypt_event_test.go) * [Delete Encryption Key](../pkg/crypto/eventencryptor/delete_encryption_key_test.go) +* [Auto Encrypt/Decrypt Event with Decryption Store](../provider/encryptedstore/encrypt_event_test.go) diff --git a/provider/encryptedstore/encrypt_event_test.go b/provider/encryptedstore/encrypt_event_test.go new file mode 100644 index 0000000..00c9673 --- /dev/null +++ b/provider/encryptedstore/encrypt_event_test.go @@ -0,0 +1,70 @@ +package encryptedstore_test + +import ( + "context" + "fmt" + "math/rand" + + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/crypto/aes" + "github.com/inklabs/rangedb/pkg/crypto/cryptotest" + "github.com/inklabs/rangedb/pkg/crypto/eventencryptor" + "github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore" + "github.com/inklabs/rangedb/pkg/shortuuid" + "github.com/inklabs/rangedb/provider/encryptedstore" + "github.com/inklabs/rangedb/provider/inmemorystore" +) + +func ExampleNew_automatically_encrypt_decrypt() { + // Given + shortuuid.SetRand(100) + seededRandReader := rand.New(rand.NewSource(100)) + aesEncryptor := aes.NewGCM() + aesEncryptor.SetRandReader(seededRandReader) + keyStore := inmemorykeystore.New() + eventEncryptor := eventencryptor.New(keyStore, aesEncryptor) + eventEncryptor.SetRandReader(seededRandReader) + event := &cryptotest.CustomerSignedUp{ + ID: "fe65ac8d8c3a45e9b3cee407f10ee518", + Name: "John Doe", + Email: "john@example.com", + Status: "active", + } + inMemoryStore := inmemorystore.New() + encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor) + encryptedStore.Bind(&cryptotest.CustomerSignedUp{}) + + ctx := context.Background() + + // When + _, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event}) + PrintError(err) + + fmt.Println("Auto Decrypted Event:") + recordIterator := encryptedStore.Events(ctx, 0) + for recordIterator.Next() { + PrintEvent(recordIterator.Record().Data.(rangedb.Event)) + } + + fmt.Println("Raw Encrypted Event:") + rawRecordIterator := inMemoryStore.Events(ctx, 0) + for rawRecordIterator.Next() { + PrintEvent(rawRecordIterator.Record().Data.(rangedb.Event)) + } + + // Output: + // Auto Decrypted Event: + // { + // "ID": "fe65ac8d8c3a45e9b3cee407f10ee518", + // "Name": "John Doe", + // "Email": "john@example.com", + // "Status": "active" + // } + // Raw Encrypted Event: + // { + // "ID": "fe65ac8d8c3a45e9b3cee407f10ee518", + // "Name": "Lp5pGK8QGYw3NJyJVBsW49HESSf+NEraAQoBmpLXboZvsN/L", + // "Email": "o1H9t1BClYc5UcyUV+Roe3wz5gwRZRjgBI/xzwZs8ueQGQ5L8uGnbrTGrh8=", + // "Status": "active" + // } +} diff --git a/provider/encryptedstore/helper_test.go b/provider/encryptedstore/helper_test.go new file mode 100644 index 0000000..e049f37 --- /dev/null +++ b/provider/encryptedstore/helper_test.go @@ -0,0 +1,24 @@ +package encryptedstore_test + +import ( + "encoding/json" + "fmt" + + "github.com/inklabs/rangedb" + "github.com/inklabs/rangedb/pkg/jsontools" +) + +func PrintError(errors ...error) { + for _, err := range errors { + if err != nil { + fmt.Println(err) + } + } +} + +func PrintEvent(event rangedb.Event) { + body, err := json.Marshal(event) + PrintError(err) + + fmt.Println(jsontools.PrettyJSON(body)) +} From e80cfc28f15da0707a81d154b65cf87b7c78a361 Mon Sep 17 00:00:00 2001 From: Jamie Isaacs Date: Mon, 10 May 2021 16:22:46 -0700 Subject: [PATCH 3/4] #20: Added documentation --- pkg/crypto/cryptotest/failing_event_encryptor.go | 1 + provider/encryptedstore/decrypting_record_subscriber.go | 1 + provider/encryptedstore/encrypted_store.go | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/crypto/cryptotest/failing_event_encryptor.go b/pkg/crypto/cryptotest/failing_event_encryptor.go index 1eb51c7..55eb6d4 100644 --- a/pkg/crypto/cryptotest/failing_event_encryptor.go +++ b/pkg/crypto/cryptotest/failing_event_encryptor.go @@ -8,6 +8,7 @@ import ( type failingEventEncryptor struct{} +// NewFailingEventEncryptor always errors on Encrypt/Decrypt func NewFailingEventEncryptor() *failingEventEncryptor { return &failingEventEncryptor{} } diff --git a/provider/encryptedstore/decrypting_record_subscriber.go b/provider/encryptedstore/decrypting_record_subscriber.go index 1ca9f70..a59d414 100644 --- a/provider/encryptedstore/decrypting_record_subscriber.go +++ b/provider/encryptedstore/decrypting_record_subscriber.go @@ -10,6 +10,7 @@ type decryptingRecordSubscriber struct { eventEncryptor crypto.EventEncryptor } +// NewDecryptingRecordSubscriber decrypts records on Accept func NewDecryptingRecordSubscriber(parent rangedb.RecordSubscriber, eventEncryptor crypto.EventEncryptor) *decryptingRecordSubscriber { return &decryptingRecordSubscriber{ parent: parent, diff --git a/provider/encryptedstore/encrypted_store.go b/provider/encryptedstore/encrypted_store.go index 3528787..78f6a21 100644 --- a/provider/encryptedstore/encrypted_store.go +++ b/provider/encryptedstore/encrypted_store.go @@ -12,9 +12,10 @@ type encryptedStore struct { eventEncryptor crypto.EventEncryptor } -func New(parentStore rangedb.Store, eventEncryptor crypto.EventEncryptor) *encryptedStore { +// New constructs an Encrypted Store that automatically encrypts/decrypts events for a decorated parent rangedb.Store. +func New(parent rangedb.Store, eventEncryptor crypto.EventEncryptor) *encryptedStore { return &encryptedStore{ - parent: parentStore, + parent: parent, eventEncryptor: eventEncryptor, } } From a5400d9bd4749b7b937e6a17d1f44739778eee24 Mon Sep 17 00:00:00 2001 From: Jamie Isaacs Date: Mon, 10 May 2021 16:27:56 -0700 Subject: [PATCH 4/4] Releasing v0.10.0 --- store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store.go b/store.go index 0a3e8b0..8d07191 100644 --- a/store.go +++ b/store.go @@ -8,7 +8,7 @@ import ( ) // Version for RangeDB. -const Version = "0.9.0" +const Version = "0.10.0" // Record contains event data and metadata. type Record struct {