-
Notifications
You must be signed in to change notification settings - Fork 16
chore(go): add plain text migration examples #1966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1049f69
1c3a811
546e4df
fc93915
db00601
5760865
a369dcb
38befdd
681dcb0
11cc980
28394e8
7d507eb
2670157
654dceb
d5e36b8
46c116b
e1e4664
14fdbc4
02a895f
6320c71
158077a
910e2c2
2f82fff
21a1432
6f5bc22
8939baa
1cac0e9
21573ff
845d51f
be195f2
ced6cbf
6c3971b
83f4bb3
1d943e1
041e19f
6098aa9
475d77e
08fc555
6f5f836
e0a2c7e
13cf62c
cde3989
e952d00
debaf5a
b478e98
9b51cb2
8412c88
14a3eba
4eb1d8a
295a4bd
ea9b480
c50b7d1
e2e45eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Plaintext DynamoDB Table to AWS Database Encryption SDK Encrypted Table Migration | ||
|
||
This projects demonstrates the steps necessary | ||
to migrate to the AWS Database Encryption SDK for DynamoDb | ||
from a plaintext database. | ||
|
||
[Step 0](plaintext/step0.go) demonstrates the starting state for your system. | ||
|
||
## Step 1 | ||
|
||
In Step 1, you update your system to do the following: | ||
|
||
- continue to read plaintext items | ||
- continue to write plaintext items | ||
- prepare to read encrypted items | ||
|
||
When you deploy changes in Step 1, | ||
you should not expect any behavior change in your system, | ||
and your dataset still consists of plaintext data. | ||
|
||
You must ensure that the changes in Step 1 make it to all your readers before you proceed to Step 2. | ||
|
||
## Step 2 | ||
|
||
In Step 2, you update your system to do the following: | ||
|
||
- continue to read plaintext items | ||
- start writing encrypted items | ||
- continue to read encrypted items | ||
|
||
When you deploy changes in Step 2, | ||
you are introducing encrypted items to your system, | ||
and must make sure that all your readers are updated with the changes from Step 1. | ||
|
||
Before you move onto the next step, you will need to encrypt all plaintext items in your dataset. | ||
Once you have completed this step, | ||
while new items are being encrypted using the new format and will be authenticated on read, | ||
your system will still accept reading plaintext, unauthenticated items. | ||
In order to complete migration to a system where you always authenticate your items, | ||
you should prioritize moving on to Step 3. | ||
|
||
## Step 3 | ||
|
||
Once all old items are encrypted, | ||
update your system to do the following: | ||
|
||
- continue to write encrypted items | ||
- continue to read encrypted items | ||
- do not accept reading plaintext items | ||
|
||
Once you have deployed these changes to your system, you have completed migration. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package awsdbe | ||
|
||
import ( | ||
"context" | ||
|
||
mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated" | ||
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes" | ||
dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes" | ||
dbesdkstructuredencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/awscryptographydbencryptionsdkstructuredencryptionsmithygeneratedtypes" | ||
"github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/examples/utils" | ||
) | ||
|
||
func configureTable(kmsKeyID, ddbTableName string, plaintextOverride dbesdkdynamodbencryptiontypes.PlaintextOverride) dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig { | ||
|
||
// Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. | ||
// We will use the `CreateMrkMultiKeyring` method to create this keyring, | ||
// as it will correctly handle both single region and Multi-Region KMS Keys. | ||
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) | ||
utils.HandleError(err) | ||
|
||
keyringInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{ | ||
Generator: &kmsKeyID, | ||
} | ||
kmsKeyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), keyringInput) | ||
utils.HandleError(err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice touch. |
||
|
||
// Configure which attributes are encrypted and/or signed when writing new items. | ||
// For each attribute that may exist on the items we plan to write to our DynamoDbTable, | ||
// we must explicitly configure how they should be treated during item encryption: | ||
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature | ||
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature | ||
// - DO_NOTHING: The attribute is not encrypted and not included in the signature | ||
partitionKeyName := "partition_key" | ||
sortKeyName := "sort_key" | ||
|
||
attributeActions := map[string]dbesdkstructuredencryptiontypes.CryptoAction{ | ||
partitionKeyName: dbesdkstructuredencryptiontypes.CryptoActionSignOnly, | ||
sortKeyName: dbesdkstructuredencryptiontypes.CryptoActionSignOnly, | ||
"attribute1": dbesdkstructuredencryptiontypes.CryptoActionEncryptAndSign, | ||
"attribute2": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, | ||
"attribute3": dbesdkstructuredencryptiontypes.CryptoActionDoNothing, | ||
} | ||
|
||
// Configure which attributes we expect to be excluded in the signature | ||
// when reading items. This value represents all unsigned attributes | ||
// across our entire dataset. If you ever want to add new unsigned attributes | ||
// in the future, you must make an update to this field to all your readers | ||
// before deploying any change to start writing that new data. It is not safe | ||
// to remove attributes from this field. | ||
unsignedAttributes := []string{"attribute3"} | ||
|
||
// Create encryption configuration for table. | ||
tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{ | ||
LogicalTableName: ddbTableName, | ||
PartitionKeyName: partitionKeyName, | ||
SortKeyName: &sortKeyName, | ||
AttributeActionsOnEncrypt: attributeActions, | ||
Keyring: kmsKeyring, | ||
AllowedUnsignedAttributes: unsignedAttributes, | ||
PlaintextOverride: &plaintextOverride, | ||
} | ||
|
||
tableConfigsMap := make(map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig) | ||
tableConfigsMap[ddbTableName] = tableConfig | ||
|
||
listOfTableConfigs := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{ | ||
TableEncryptionConfigs: tableConfigsMap, | ||
} | ||
|
||
return listOfTableConfigs | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,123 @@ | ||||||||||
package awsdbe | ||||||||||
|
||||||||||
import ( | ||||||||||
"context" | ||||||||||
"fmt" | ||||||||||
|
||||||||||
"github.com/aws/aws-sdk-go-v2/aws" | ||||||||||
"github.com/aws/aws-sdk-go-v2/config" | ||||||||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb" | ||||||||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" | ||||||||||
|
||||||||||
"github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/dbesdkmiddleware" | ||||||||||
"github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/examples/utils" | ||||||||||
|
||||||||||
dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes" | ||||||||||
) | ||||||||||
|
||||||||||
/* | ||||||||||
Migration Step 1: This is an example demonstrating how to start using the | ||||||||||
AWS Database Encryption SDK with a pre-existing table with plaintext items. | ||||||||||
In this example, we configure a DynamoDb Encryption Interceptor to do the following: | ||||||||||
- Write items only in plaintext | ||||||||||
- Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration | ||||||||||
|
||||||||||
While this step configures your client to be ready to start reading encrypted items, | ||||||||||
we do not yet expect to be reading any encrypted items, | ||||||||||
as our client still writes plaintext items. | ||||||||||
Before you move on to step 2, ensure that these changes have successfully been deployed | ||||||||||
to all of your readers. | ||||||||||
|
||||||||||
Running this example requires access to the DDB Table whose name | ||||||||||
is provided in the function parameter. | ||||||||||
This table must be configured with the following | ||||||||||
primary key configuration: | ||||||||||
- Partition key is named "partition_key" with type (S) | ||||||||||
- Sort key is named "sort_key" with type (S) | ||||||||||
*/ | ||||||||||
func MigrationStep1(kmsKeyID, ddbTableName, partitionKeyValue, sortKeyWriteValue, sortKeyReadValue string) error { | ||||||||||
cfg, err := config.LoadDefaultConfig(context.TODO()) | ||||||||||
utils.HandleError(err) | ||||||||||
|
||||||||||
// 1. Configure your Keyring, attribute actions, | ||||||||||
// allowedUnsignedAttributes, and encryption configuration for table. | ||||||||||
// This is common across all the steps. | ||||||||||
|
||||||||||
// Note that while we still are not writing encrypted items, | ||||||||||
// and our key will not be used to encrypt items in this example, | ||||||||||
// our configuration specifies that we may read encrypted items, | ||||||||||
// and we should expect to be able to decrypt and process any encrypted items. | ||||||||||
// To that end, we must fully define our encryption configuration in | ||||||||||
// this step. | ||||||||||
|
||||||||||
// This `PlaintextOverrideForcePlaintextWriteAllowPlaintextRead` means: | ||||||||||
// - Write: Items are forced to be written as plaintext. | ||||||||||
// Items may not be written as encrypted items. | ||||||||||
// - Read: Items are allowed to be read as plaintext. | ||||||||||
// Items are allowed to be read as encrypted items. | ||||||||||
listOfTableConfigs := configureTable(kmsKeyID, ddbTableName, dbesdkdynamodbencryptiontypes.PlaintextOverrideForcePlaintextWriteAllowPlaintextRead) | ||||||||||
|
||||||||||
// 2. Create DynamoDB client with dbEsdkMiddleware | ||||||||||
dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(listOfTableConfigs) | ||||||||||
utils.HandleError(err) | ||||||||||
|
||||||||||
ddb := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware()) | ||||||||||
|
||||||||||
// 3. Put an item into your table. | ||||||||||
// This item will be stored in plaintext. | ||||||||||
encryptedAndSignedValue := "this will be encrypted and signed" | ||||||||||
item := map[string]types.AttributeValue{ | ||||||||||
"partition_key": &types.AttributeValueMemberS{Value: partitionKeyValue}, | ||||||||||
"sort_key": &types.AttributeValueMemberN{Value: sortKeyWriteValue}, | ||||||||||
"attribute1": &types.AttributeValueMemberS{Value: encryptedAndSignedValue}, | ||||||||||
"attribute2": &types.AttributeValueMemberS{Value: "this will never be encrypted, but it will be signed"}, | ||||||||||
"attribute3": &types.AttributeValueMemberS{Value: "this will never be encrypted nor signed"}, | ||||||||||
} | ||||||||||
|
||||||||||
putInput := dynamodb.PutItemInput{ | ||||||||||
TableName: &ddbTableName, | ||||||||||
Item: item, | ||||||||||
} | ||||||||||
|
||||||||||
_, err = ddb.PutItem(context.TODO(), &putInput) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we use a plain vanilla client to demonstrate that we actually aren't doing any encryption and can read unencrypted items using our client? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do this in Test. This is one combination of the test: In step 1 test, we write with vanilla DDB client and read with intercepted DDB client. In step 0 test, we write with intercepted DDB client and read with the vanilla one. |
||||||||||
|
||||||||||
// We return this error because we run test against the error. | ||||||||||
// When used in production code, you can decide how you want to handle errors. | ||||||||||
if err != nil { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use utils here as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before writing migration example, I thought panic on error everywhere is good enough but with all the test combinations in migration test, it was not good enough. I think many in the team will still have a thought that we panic on every example. So, to make it visually distinct immediately visible in the code, I did not use utils. |
||||||||||
return err | ||||||||||
} | ||||||||||
|
||||||||||
// 4. Get an item back from the table using the DynamoDb Client. | ||||||||||
// If this is an item written in plaintext (i.e. any item written | ||||||||||
// during Step 0 or 1), then the item will still be in plaintext. | ||||||||||
// If this is an item that was encrypted client-side (i.e. any item written | ||||||||||
// during Step 2 or after), then the item will be decrypted client-side | ||||||||||
// and surfaced as a plaintext item. | ||||||||||
key := map[string]types.AttributeValue{ | ||||||||||
"partition_key": &types.AttributeValueMemberS{Value: partitionKeyValue}, | ||||||||||
"sort_key": &types.AttributeValueMemberN{Value: sortKeyReadValue}, | ||||||||||
} | ||||||||||
|
||||||||||
getInput := &dynamodb.GetItemInput{ | ||||||||||
TableName: &ddbTableName, | ||||||||||
Key: key, | ||||||||||
ConsistentRead: aws.Bool(true), | ||||||||||
} | ||||||||||
|
||||||||||
result, err := ddb.GetItem(context.TODO(), getInput) | ||||||||||
// We return this error because we run test against the error. | ||||||||||
// When used in production code, you can decide how you want to handle errors. | ||||||||||
if err != nil { | ||||||||||
return err | ||||||||||
} | ||||||||||
|
||||||||||
// Verify we got the expected item back | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We had decided yesterday to test every variable. |
||||||||||
if partitionKeyValue != result.Item["partition_key"].(*types.AttributeValueMemberS).Value { | ||||||||||
panic("Decrypted item does not match original item") | ||||||||||
} | ||||||||||
if encryptedAndSignedValue != result.Item["attribute1"].(*types.AttributeValueMemberS).Value { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about other attributes? Maybe add comment if we don't want full assertion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this change look good to you? https://github.com/aws/aws-database-encryption-sdk-dynamodb/pull/1966/files#r2255583436 I will apply this change to other places as well. |
||||||||||
panic("Decrypted item does not match original item") | ||||||||||
} | ||||||||||
fmt.Println("MigrationStep1 completed successfully") | ||||||||||
return nil | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package awsdbe | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/examples/migration/PlaintextToAWSDBE/plaintext" | ||
"github.com/aws/aws-database-encryption-sdk-dynamodb/releases/go/dynamodb-esdk/examples/utils" | ||
"github.com/google/uuid" | ||
) | ||
|
||
func TestMigrationStep1(t *testing.T) { | ||
kmsKeyID := utils.KmsKeyID() | ||
tableName := utils.DdbTableName() | ||
partitionKey := uuid.New().String() | ||
sortKeys := []string{"0", "1", "2", "3"} | ||
|
||
// Successfully executes Step 1 | ||
err := MigrationStep1(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[1]) | ||
utils.HandleError(err) | ||
|
||
// Given: Step 0 has succeeded | ||
err = plaintext.MigrationStep0(tableName, partitionKey, sortKeys[0], sortKeys[0]) | ||
utils.HandleError(err) | ||
|
||
// When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read plaintext values) | ||
err = MigrationStep1(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[0]) | ||
utils.HandleError(err) | ||
|
||
// Given: Step 2 has succeeded | ||
err = MigrationStep2(kmsKeyID, tableName, partitionKey, sortKeys[2], sortKeys[2]) | ||
utils.HandleError(err) | ||
|
||
// When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read encrypted values) | ||
err = MigrationStep1(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[2]) | ||
utils.HandleError(err) | ||
|
||
// Given: Step 3 has succeeded | ||
err = MigrationStep3(kmsKeyID, tableName, partitionKey, sortKeys[3], sortKeys[3]) | ||
utils.HandleError(err) | ||
|
||
// When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read encrypted values) | ||
err = MigrationStep1(kmsKeyID, tableName, partitionKey, sortKeys[1], sortKeys[3]) | ||
utils.HandleError(err) | ||
|
||
// Cleanup | ||
for _, sortKey := range sortKeys { | ||
utils.DeleteItem(tableName, "partition_key", partitionKey, "sort_key", sortKey) | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why run and test both? Since it's an example, I don't think we need to assert all cases via unit tests, but if it's already in then no worries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For migrations, I needed to run a lot of combination of steps that made sense of run as a unit test. For all other test, just running a round trip was enough but migration is different so it was not enough