Skip to content

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

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1049f69
auto commit
rishav-karanjit Jul 31, 2025
1c3a811
auto commit
rishav-karanjit Jul 31, 2025
546e4df
auto commit
rishav-karanjit Jul 31, 2025
fc93915
auto commit
rishav-karanjit Jul 31, 2025
db00601
auto commit
rishav-karanjit Jul 31, 2025
5760865
auto commit
rishav-karanjit Aug 1, 2025
a369dcb
auto commit
rishav-karanjit Aug 1, 2025
38befdd
auto commit
rishav-karanjit Aug 1, 2025
681dcb0
auto commit
rishav-karanjit Aug 1, 2025
11cc980
auto commit
rishav-karanjit Aug 1, 2025
28394e8
auto commit
rishav-karanjit Aug 1, 2025
7d507eb
auto commit
rishav-karanjit Aug 1, 2025
2670157
auto commit
rishav-karanjit Aug 1, 2025
654dceb
auto commit
rishav-karanjit Aug 1, 2025
d5e36b8
auto commit
rishav-karanjit Aug 1, 2025
46c116b
auto commit
rishav-karanjit Aug 1, 2025
e1e4664
auto commit
rishav-karanjit Aug 1, 2025
14fdbc4
auto commit
rishav-karanjit Aug 1, 2025
02a895f
auto commit
rishav-karanjit Aug 1, 2025
6320c71
auto commit
rishav-karanjit Aug 1, 2025
158077a
auto commit
rishav-karanjit Aug 1, 2025
910e2c2
Merge branch 'main' into plaintextmigration
rishav-karanjit Aug 1, 2025
2f82fff
auto commit
rishav-karanjit Aug 4, 2025
21a1432
Merge branch 'plaintextmigration' of https://github.com/aws/aws-datab…
rishav-karanjit Aug 4, 2025
6f5bc22
auto commit
rishav-karanjit Aug 4, 2025
8939baa
auto commit
rishav-karanjit Aug 4, 2025
1cac0e9
auto commit
rishav-karanjit Aug 4, 2025
21573ff
auto commit
rishav-karanjit Aug 4, 2025
845d51f
auto commit
rishav-karanjit Aug 4, 2025
be195f2
auto commit
rishav-karanjit Aug 4, 2025
ced6cbf
auto commit
rishav-karanjit Aug 4, 2025
6c3971b
auto commit
rishav-karanjit Aug 4, 2025
83f4bb3
auto commit
rishav-karanjit Aug 4, 2025
1d943e1
auto commit
rishav-karanjit Aug 4, 2025
041e19f
auto commit
rishav-karanjit Aug 4, 2025
6098aa9
auto commit
rishav-karanjit Aug 4, 2025
475d77e
auto commit
rishav-karanjit Aug 4, 2025
08fc555
auto commit
rishav-karanjit Aug 4, 2025
6f5f836
auto commit
rishav-karanjit Aug 4, 2025
e0a2c7e
auto commit
rishav-karanjit Aug 4, 2025
13cf62c
auto commit
rishav-karanjit Aug 4, 2025
cde3989
auto commit
rishav-karanjit Aug 4, 2025
e952d00
auto commit
rishav-karanjit Aug 4, 2025
debaf5a
auto commit
rishav-karanjit Aug 4, 2025
b478e98
auto commit
rishav-karanjit Aug 4, 2025
9b51cb2
auto commit
rishav-karanjit Aug 4, 2025
8412c88
auto commit
rishav-karanjit Aug 4, 2025
14a3eba
auto commit
rishav-karanjit Aug 4, 2025
4eb1d8a
add step 3 -> step 1/2
rishav-karanjit Aug 4, 2025
295a4bd
Merge branch 'main' into plaintextmigration
rishav-karanjit Aug 5, 2025
ea9b480
sortkeywritevalue
rishav-karanjit Aug 6, 2025
c50b7d1
Merge branch 'plaintextmigration' of https://github.com/aws/aws-datab…
rishav-karanjit Aug 6, 2025
e2e45eb
sortkeywritevalue
rishav-karanjit Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci_test_go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ jobs:
run: |
make test_go

- name: Test Examples
- name: Run and Test Examples
Copy link
Contributor

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.

Copy link
Member Author

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

if: matrix.library == 'DynamoDbEncryption'
working-directory: ./Examples/runtimes/go
run: |
go run main.go
go test ./...
51 changes: 51 additions & 0 deletions Examples/runtimes/go/migration/PlaintextToAWSDBE/README.md
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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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
}
123 changes: 123 additions & 0 deletions Examples/runtimes/go/migration/PlaintextToAWSDBE/awsdbe/step1.go
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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

@rishav-karanjit rishav-karanjit Aug 5, 2025

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use utils here as well?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Verify we got the expected item back
// Verify we got the expected item back.
// Since we are mostly concerned with Plaintext Override,
// our focus is to assert the encrypted and signed value.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
}

}
Loading
Loading