Skip to content

Commit

Permalink
Merge pull request #21 from inklabs/encrypted-store
Browse files Browse the repository at this point in the history
Encrypted Store Decorator
  • Loading branch information
pdt256 authored May 10, 2021
2 parents d19653d + a5400d9 commit 0d01874
Show file tree
Hide file tree
Showing 11 changed files with 695 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
22 changes: 22 additions & 0 deletions pkg/crypto/cryptotest/failing_event_encryptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cryptotest

import (
"fmt"

"github.com/inklabs/rangedb"
)

type failingEventEncryptor struct{}

// NewFailingEventEncryptor always errors on Encrypt/Decrypt
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")
}
50 changes: 50 additions & 0 deletions provider/encryptedstore/decrypting_record_iterator.go
Original file line number Diff line number Diff line change
@@ -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()
}
106 changes: 106 additions & 0 deletions provider/encryptedstore/decrypting_record_iterator_test.go
Original file line number Diff line number Diff line change
@@ -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())
})
}
27 changes: 27 additions & 0 deletions provider/encryptedstore/decrypting_record_subscriber.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package encryptedstore

import (
"github.com/inklabs/rangedb"
"github.com/inklabs/rangedb/pkg/crypto"
)

type decryptingRecordSubscriber struct {
parent rangedb.RecordSubscriber
eventEncryptor crypto.EventEncryptor
}

// NewDecryptingRecordSubscriber decrypts records on Accept
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)
}
94 changes: 94 additions & 0 deletions provider/encryptedstore/decrypting_record_subscriber_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
70 changes: 70 additions & 0 deletions provider/encryptedstore/encrypt_event_test.go
Original file line number Diff line number Diff line change
@@ -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"
// }
}
Loading

0 comments on commit 0d01874

Please sign in to comment.