diff --git a/go/appencryption/.versionfile b/go/appencryption/.versionfile index 17e51c385..d917d3e26 100644 --- a/go/appencryption/.versionfile +++ b/go/appencryption/.versionfile @@ -1 +1 @@ -0.1.1 +0.1.2 diff --git a/go/appencryption/pkg/persistence/dynamodb.go b/go/appencryption/pkg/persistence/dynamodb.go index 6e3081f1b..80b6bb75b 100644 --- a/go/appencryption/pkg/persistence/dynamodb.go +++ b/go/appencryption/pkg/persistence/dynamodb.go @@ -21,10 +21,10 @@ import ( ) const ( - tableName = "EncryptionKey" - partitionKey = "Id" - sortKey = "Created" - keyRecord = "KeyRecord" + defaultTableName = "EncryptionKey" + partitionKey = "Id" + sortKey = "Created" + keyRecord = "KeyRecord" ) var ( @@ -39,13 +39,14 @@ var ( // DynamoDBMetastore implements the Metastore interface. type DynamoDBMetastore struct { - svc dynamodbiface.DynamoDBAPI - keySuffix string + svc dynamodbiface.DynamoDBAPI + regionSuffix string + tableName string } -// GetSuffix returns the DynamoDB region suffix or blank if not configured. -func (d *DynamoDBMetastore) GetSuffix() string { - return d.keySuffix +// GetRegionSuffix returns the DynamoDB region suffix or blank if not configured. +func (d *DynamoDBMetastore) GetRegionSuffix() string { + return d.regionSuffix } // DynamoDBMetastoreOption is used to configure additional options in a DynamoDBMetastore. @@ -58,14 +59,24 @@ func WithDynamoDBRegionSuffix(enabled bool) DynamoDBMetastoreOption { return func(d *DynamoDBMetastore, p client.ConfigProvider) { if enabled { config := p.ClientConfig(dynamodb.EndpointsID) - d.keySuffix = *config.Config.Region + d.regionSuffix = *config.Config.Region + } + } +} + +// WithTableName configures the DynamoDBMetastore to use the specified table name. +func WithTableName(table string) DynamoDBMetastoreOption { + return func(d *DynamoDBMetastore, p client.ConfigProvider) { + if len(table) > 0 { + d.tableName = table } } } func NewDynamoDBMetastore(sess client.ConfigProvider, opts ...DynamoDBMetastoreOption) *DynamoDBMetastore { d := &DynamoDBMetastore{ - svc: dynamodb.New(sess), + svc: dynamodb.New(sess), + tableName: defaultTableName, } for _, opt := range opts { @@ -103,7 +114,7 @@ func (d *DynamoDBMetastore) Load(ctx context.Context, keyID string, created int6 sortKey: {N: aws.String(strconv.FormatInt(created, 10))}, }, ProjectionExpression: expr.Projection(), - TableName: aws.String(tableName), + TableName: aws.String(d.tableName), ConsistentRead: aws.Bool(true), // always use strong consistency }) @@ -140,7 +151,7 @@ func (d *DynamoDBMetastore) LoadLatest(ctx context.Context, keyID string) (*appe Limit: aws.Int64(1), // limit 1 ProjectionExpression: expr.Projection(), ScanIndexForward: aws.Bool(false), // sorts descending - TableName: aws.String(tableName), + TableName: aws.String(d.tableName), }) if err != nil { return nil, err @@ -190,7 +201,7 @@ func (d *DynamoDBMetastore) Store(ctx context.Context, keyID string, created int sortKey: {N: aws.String(strconv.FormatInt(created, 10))}, keyRecord: {M: av}, }, - TableName: aws.String(tableName), + TableName: aws.String(d.tableName), ConditionExpression: aws.String("attribute_not_exists(" + partitionKey + ")"), }) if err != nil { diff --git a/go/appencryption/pkg/persistence/dynamodb_test.go b/go/appencryption/pkg/persistence/dynamodb_test.go index a772bd18b..302f5dc61 100644 --- a/go/appencryption/pkg/persistence/dynamodb_test.go +++ b/go/appencryption/pkg/persistence/dynamodb_test.go @@ -34,6 +34,7 @@ type DynamoDBSuite struct { } const ( + tableName = "CustomTableName" portProtocolDynamoDB = "8000/tcp" maxTriesDynamoDB = 5 waitTimeDynamoDB = 10 @@ -172,7 +173,7 @@ func (suite *DynamoDBSuite) SetupTest() { } suite.putItemInDynamoDB(getDynamoDBItem(en, suite.instant)) - suite.dynamodbMetastore = NewDynamoDBMetastore(suite.sess) + suite.dynamodbMetastore = NewDynamoDBMetastore(suite.sess, WithTableName(tableName)) suite.prefixedDynamodbMetastore = NewDynamoDBMetastore(suite.sess, WithDynamoDBRegionSuffix(true)) } @@ -339,10 +340,23 @@ func (suite *DynamoDBSuite) TestDynamoDBMetastore_Store_WithFailureShouldReturnE func (suite *DynamoDBSuite) TestDynamoDBMetastore_WithDynamoDBRegionSuffix() { // keyPrefix should be empty unless WithDynamoDBRegionSuffix is used - assert.Empty(suite.T(), suite.dynamodbMetastore.keySuffix) + assert.Empty(suite.T(), suite.dynamodbMetastore.GetRegionSuffix()) // WithDynamoDBRegionSuffix should set the keyPrefix equal to the client's region - assert.Equal(suite.T(), *suite.sess.Config.Region, suite.prefixedDynamodbMetastore.keySuffix) + assert.Equal(suite.T(), *suite.sess.Config.Region, suite.prefixedDynamodbMetastore.GetRegionSuffix()) +} + +func (suite *DynamoDBSuite) TestDynamoDBMetastore_WithTableName() { + table := "DummyTable" + db := NewDynamoDBMetastore(suite.sess, WithTableName(table)) + + assert.Equal(suite.T(), table, db.tableName) +} + +func (suite *DynamoDBSuite) TestDynamoDBMetastore_DefaultTableName() { + db := NewDynamoDBMetastore(suite.sess) + + assert.Equal(suite.T(), defaultTableName, db.tableName) } func TestDynamoSuite(t *testing.T) { diff --git a/go/appencryption/session.go b/go/appencryption/session.go index e018388aa..f1613e94c 100644 --- a/go/appencryption/session.go +++ b/go/appencryption/session.go @@ -89,8 +89,8 @@ func (f *SessionFactory) GetSession(id string) (*Session, error) { } var p partition - if v, ok := f.Metastore.(interface{ GetSuffix() string }); ok && len(v.GetSuffix()) > 0 { - p = newSuffixedPartition(id, f.Config.Service, f.Config.Product, v.GetSuffix()) + if v, ok := f.Metastore.(interface{ GetRegionSuffix() string }); ok && len(v.GetRegionSuffix()) > 0 { + p = newSuffixedPartition(id, f.Config.Service, f.Config.Product, v.GetRegionSuffix()) } else { p = newPartition(id, f.Config.Service, f.Config.Product) } diff --git a/go/appencryption/session_test.go b/go/appencryption/session_test.go index 1ed9d030c..bd533ed9b 100644 --- a/go/appencryption/session_test.go +++ b/go/appencryption/session_test.go @@ -281,7 +281,7 @@ type MockDynamoDBMetastore struct { *MockMetastore } -func (m *MockDynamoDBMetastore) GetSuffix() string { +func (m *MockDynamoDBMetastore) GetRegionSuffix() string { args := m.Called() return args.String(0) } @@ -299,7 +299,7 @@ func TestSessionFactory_GetSession_DefaultPartition(t *testing.T) { func TestSessionFactory_GetSession_SuffixedPartition(t *testing.T) { store := &MockDynamoDBMetastore{MockMetastore: new(MockMetastore)} - store.On("GetSuffix").Return("suffix") + store.On("GetRegionSuffix").Return("suffix") factory := NewSessionFactory(new(Config), store, nil, nil) @@ -313,7 +313,7 @@ func TestSessionFactory_GetSession_SuffixedPartition(t *testing.T) { func TestSessionFactory_GetSession_Blank_GetSuffix_DefaultPartition(t *testing.T) { store := &MockDynamoDBMetastore{MockMetastore: new(MockMetastore)} - store.On("GetSuffix").Return("") + store.On("GetRegionSuffix").Return("") factory := NewSessionFactory(new(Config), store, nil, nil) diff --git a/server/go/README.md b/server/go/README.md index c6f0b9988..0adf61acb 100644 --- a/server/go/README.md +++ b/server/go/README.md @@ -54,6 +54,10 @@ Asherah Options: [$ASHERAH_CONNECTION_STRING] --enable-region-suffix Configure the metastore to use regional suffixes (only supported by --metastore=dynamodb) [$ASHERAH_ENABLE_REGION_SUFFIX] + --dynamodb-endpoint= An optional endpoint URL (hostname only or fully qualified URI) (only + supported by --metastore=dynamodb) [$ASHERAH_DYNAMODB_ENDPOINT] + --dynamodb-region= The AWS region for DynamoDB requests (defaults to globally configured region) + (only supported by --metastore=dynamodb) [$ASHERAH_DYNAMODB_REGION] --kms=[aws|static] Configures the master key management service (default: aws) [$ASHERAH_KMS_MODE] --region-map= A comma separated list of key-value pairs in the form of diff --git a/server/go/pkg/server/options.go b/server/go/pkg/server/options.go index a994efa68..e13f51190 100644 --- a/server/go/pkg/server/options.go +++ b/server/go/pkg/server/options.go @@ -15,6 +15,8 @@ type Options struct { Metastore string `long:"metastore" choice:"rdbms" choice:"dynamodb" choice:"memory" required:"yes" description:"Determines the type of metastore to use for persisting keys" env:"ASHERAH_METASTORE_MODE"` ConnectionString string `long:"conn" description:"The database connection string (required if --metastore=rdbms)" env:"ASHERAH_CONNECTION_STRING"` EnableRegionSuffix bool `long:"enable-region-suffix" description:"Configure the metastore to use regional suffixes (only supported by --metastore=dynamodb)" env:"ASHERAH_ENABLE_REGION_SUFFIX"` + DynamoDBEndpoint string `long:"dynamodb-endpoint" description:"An optional endpoint URL (hostname only or fully qualified URI) (only supported by --metastore=dynamodb)" env:"ASHERAH_DYNAMODB_ENDPOINT"` + DynamoDBRegion string `long:"dynamodb-region" description:"The AWS region for DynamoDB requests (defaults to globally configured region) (only supported by --metastore=dynamodb)" env:"ASHERAH_DYNAMODB_REGION"` KMS string `long:"kms" choice:"aws" choice:"static" default:"aws" description:"Configures the master key management service" env:"ASHERAH_KMS_MODE"` RegionMap RegionMap `long:"region-map" description:"A comma separated list of key-value pairs in the form of REGION1=ARN1[,REGION2=ARN2] (required if --kms=aws)" env:"ASHERAH_REGION_MAP"` PreferredRegion string `long:"preferred-region" description:"The preferred AWS region (required if --kms=aws)" env:"ASHERAH_PREFERRED_REGION"` diff --git a/server/go/pkg/server/server.go b/server/go/pkg/server/server.go index 14a8d01ff..2b2eb3b4a 100644 --- a/server/go/pkg/server/server.go +++ b/server/go/pkg/server/server.go @@ -5,6 +5,7 @@ import ( "io" "log" + "github.com/aws/aws-sdk-go/aws" awssession "github.com/aws/aws-sdk-go/aws/session" "github.com/godaddy/asherah/go/appencryption" "github.com/godaddy/asherah/go/appencryption/pkg/crypto/aead" @@ -105,9 +106,19 @@ func NewMetastore(opts *Options) appencryption.Metastore { return persistence.NewSQLMetastore(db) case "dynamodb": - sess := awssession.Must(awssession.NewSessionWithOptions(awssession.Options{ + awsOpts := awssession.Options{ SharedConfigState: awssession.SharedConfigEnable, - })) + } + + if len(opts.DynamoDBEndpoint) > 0 { + awsOpts.Config.Endpoint = aws.String(opts.DynamoDBEndpoint) + } + + if len(opts.DynamoDBRegion) > 0 { + awsOpts.Config.Region = aws.String(opts.DynamoDBRegion) + } + + sess := awssession.Must(awssession.NewSessionWithOptions(awsOpts)) return persistence.NewDynamoDBMetastore(sess, persistence.WithDynamoDBRegionSuffix(opts.EnableRegionSuffix)) default: diff --git a/server/go/pkg/server/server_test.go b/server/go/pkg/server/server_test.go index ed0da210b..3554c391c 100644 --- a/server/go/pkg/server/server_test.go +++ b/server/go/pkg/server/server_test.go @@ -122,7 +122,10 @@ func Test_Streamer_NewHandler(t *testing.T) { optCombos := []*Options{ {KMS: "aws", Metastore: "rdbms"}, {KMS: "aws", Metastore: "dynamodb"}, + {KMS: "aws", Metastore: "dynamodb", DynamoDBRegion: "us-east-1"}, + {KMS: "aws", Metastore: "dynamodb", DynamoDBEndpoint: "http://localhost:8000"}, {KMS: "static", Metastore: "rdbms"}, + {KMS: "static", Metastore: "memory"}, } for _, opts := range optCombos {