@@ -16,25 +16,174 @@ namespace Examples.migration.PlaintextToAWSDBE.awsdbe
1616 Migration Step 1: This is the first step in the migration process from
1717 plaintext to encrypted DynamoDB using the AWS Database Encryption SDK.
1818
19- This step will be implemented in the future. It will demonstrate how to:
20- 1. Configure a DynamoDB client with encryption capabilities
21- 2. Write items that can still be read by plaintext clients (Step 0)
22- 3. Read both plaintext items and items written by this step
19+ In this example, we configure a DynamoDB Encryption client to do the following:
20+ 1. Write items only in plaintext
21+ 2. Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration
22+
23+ While this step configures your client to be ready to start reading encrypted items,
24+ we do not yet expect to be reading any encrypted items,
25+ as our client still writes plaintext items.
26+ Before you move on to step 2, ensure that these changes have successfully been deployed
27+ to all of your readers.
2328
2429 Running this example requires access to the DDB Table whose name
2530 is provided in the function parameter.
2631 This table must be configured with the following
2732 primary key configuration:
2833 - Partition key is named "partition_key" with type (S)
29- - Sort key is named "sort_key" with type (S )
34+ - Sort key is named "sort_key" with type (N )
3035 */
3136 public class MigrationStep1
3237 {
33- // This method will be implemented in the future
3438 public static async Task < bool > MigrationStep1Example ( string kmsKeyId , string ddbTableName , string partitionKeyValue , string sortKeyWriteValue , string sortKeyReadValue )
3539 {
36- // TODO: Implement MigrationStep1
37- throw new NotImplementedException ( "MigrationStep1 is not yet implemented" ) ;
40+ try
41+ {
42+ // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
43+ // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
44+ // We will use the `CreateMrkMultiKeyring` method to create this keyring,
45+ // as it will correctly handle both single region and Multi-Region KMS Keys.
46+ var matProv = new MaterialProviders ( new MaterialProvidersConfig ( ) ) ;
47+ var keyringInput = new CreateAwsKmsMrkMultiKeyringInput { Generator = kmsKeyId } ;
48+ var kmsKeyring = matProv . CreateAwsKmsMrkMultiKeyring ( keyringInput ) ;
49+
50+ // 2. Configure which attributes are encrypted and/or signed when writing new items.
51+ // For each attribute that may exist on the items we plan to write to our DynamoDbTable,
52+ // we must explicitly configure how they should be treated during item encryption:
53+ // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
54+ // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
55+ // - DO_NOTHING: The attribute is not encrypted and not included in the signature
56+ var attributeActionsOnEncrypt = new Dictionary < string , CryptoAction >
57+ {
58+ [ "partition_key" ] = CryptoAction . SIGN_ONLY , // Our partition attribute must be SIGN_ONLY
59+ [ "sort_key" ] = CryptoAction . SIGN_ONLY , // Our sort attribute must be SIGN_ONLY
60+ [ "attribute1" ] = CryptoAction . ENCRYPT_AND_SIGN ,
61+ [ "attribute2" ] = CryptoAction . SIGN_ONLY ,
62+ [ "attribute3" ] = CryptoAction . DO_NOTHING
63+ } ;
64+
65+ // 3. Configure which attributes we expect to be excluded in the signature
66+ // when reading items. There are two options for configuring this:
67+ //
68+ // - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
69+ // When defining your DynamoDb schema and deciding on attribute names,
70+ // choose a distinguishing prefix (such as ":") for all attributes that
71+ // you do not want to include in the signature.
72+ // This has two main benefits:
73+ // - It is easier to reason about the security and authenticity of data within your item
74+ // when all unauthenticated data is easily distinguishable by their attribute name.
75+ // - If you need to add new unauthenticated attributes in the future,
76+ // you can easily make the corresponding update to your `attributeActionsOnEncrypt`
77+ // and immediately start writing to that new attribute, without
78+ // any other configuration update needed.
79+ // Once you configure this field, it is not safe to update it.
80+ //
81+ // - Configure `allowedUnsignedAttributes`: You may also explicitly list
82+ // a set of attributes that should be considered unauthenticated when encountered
83+ // on read. Be careful if you use this configuration. Do not remove an attribute
84+ // name from this configuration, even if you are no longer writing with that attribute,
85+ // as old items may still include this attribute, and our configuration needs to know
86+ // to continue to exclude this attribute from the signature scope.
87+ // If you add new attribute names to this field, you must first deploy the update to this
88+ // field to all readers in your host fleet before deploying the update to start writing
89+ // with that new attribute.
90+ //
91+ // For this example, we will explicitly list the attributes that are not signed.
92+ var unsignedAttributes = new List < string > { "attribute3" } ;
93+
94+ // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
95+ // This configuration uses PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ
96+ // which means:
97+ // - Write: Items are forced to be written as plaintext.
98+ // Items may not be written as encrypted items.
99+ // - Read: Items are allowed to be read as plaintext.
100+ // Items are allowed to be read as encrypted items.
101+ var tableConfig = new DynamoDbTableEncryptionConfig
102+ {
103+ LogicalTableName = ddbTableName ,
104+ PartitionKeyName = "partition_key" ,
105+ SortKeyName = "sort_key" ,
106+ AttributeActionsOnEncrypt = attributeActionsOnEncrypt ,
107+ Keyring = kmsKeyring ,
108+ AllowedUnsignedAttributes = unsignedAttributes ,
109+ PlaintextOverride = PlaintextOverride . FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ
110+ } ;
111+
112+ var tableConfigs = new Dictionary < string , DynamoDbTableEncryptionConfig >
113+ {
114+ [ ddbTableName ] = tableConfig
115+ } ;
116+
117+ // 5. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs
118+ var ddb = new Client . DynamoDbClient (
119+ new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs } ) ;
120+
121+ // 6. Put an item into our table using the above client.
122+ // This item will be stored in plaintext due to our PlaintextOverride configuration.
123+ string encryptedAndSignedValue = MigrationUtils . ENCRYPTED_AND_SIGNED_VALUE ;
124+ string signOnlyValue = MigrationUtils . SIGN_ONLY_VALUE ;
125+ string doNothingValue = MigrationUtils . DO_NOTHING_VALUE ;
126+ var item = new Dictionary < string , AttributeValue >
127+ {
128+ [ "partition_key" ] = new AttributeValue { S = partitionKeyValue } ,
129+ [ "sort_key" ] = new AttributeValue { N = sortKeyWriteValue } ,
130+ [ "attribute1" ] = new AttributeValue { S = encryptedAndSignedValue } ,
131+ [ "attribute2" ] = new AttributeValue { S = signOnlyValue } ,
132+ [ "attribute3" ] = new AttributeValue { S = doNothingValue }
133+ } ;
134+
135+ var putRequest = new PutItemRequest
136+ {
137+ TableName = ddbTableName ,
138+ Item = item
139+ } ;
140+
141+ var putResponse = await ddb . PutItemAsync ( putRequest ) ;
142+ Debug . Assert ( putResponse . HttpStatusCode == HttpStatusCode . OK ) ;
143+
144+ // 7. Get an item back from the table using the same client.
145+ // If this is an item written in plaintext (i.e. any item written
146+ // during Step 0 or 1), then the item will still be in plaintext.
147+ // If this is an item that was encrypted client-side (i.e. any item written
148+ // during Step 2 or after), then the item will be decrypted client-side
149+ // and surfaced as a plaintext item.
150+ var key = new Dictionary < string , AttributeValue >
151+ {
152+ [ "partition_key" ] = new AttributeValue { S = partitionKeyValue } ,
153+ [ "sort_key" ] = new AttributeValue { N = sortKeyReadValue }
154+ } ;
155+
156+ var getRequest = new GetItemRequest
157+ {
158+ TableName = ddbTableName ,
159+ Key = key ,
160+ // In this example we configure a strongly consistent read
161+ // because we perform a read immediately after a write (for demonstrative purposes).
162+ // By default, reads are only eventually consistent.
163+ ConsistentRead = true
164+ } ;
165+
166+ var getResponse = await ddb . GetItemAsync ( getRequest ) ;
167+ Debug . Assert ( getResponse . HttpStatusCode == HttpStatusCode . OK ) ;
168+
169+ // 8. Verify we get the expected item back
170+ if ( getResponse . Item == null )
171+ {
172+ throw new Exception ( "No item found" ) ;
173+ }
174+
175+ bool success = MigrationUtils . VerifyReturnedItem ( getResponse , partitionKeyValue , sortKeyReadValue ) ;
176+ if ( success )
177+ {
178+ Console . WriteLine ( "MigrationStep1 completed successfully" ) ;
179+ }
180+ return success ;
181+ }
182+ catch ( Exception e )
183+ {
184+ Console . WriteLine ( $ "Error in MigrationStep1: { e . Message } ") ;
185+ throw ;
186+ }
38187 }
39188 }
40189}
0 commit comments