-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from inklabs/encrypted-store
Encrypted Store Decorator
- Loading branch information
Showing
11 changed files
with
695 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
106
provider/encryptedstore/decrypting_record_iterator_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
94
provider/encryptedstore/decrypting_record_subscriber_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
// } | ||
} |
Oops, something went wrong.