1
+ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ use aws_sdk_dynamodb:: types:: AttributeValue ;
5
+ use std:: collections:: HashMap ;
6
+ use aws_db_esdk:: intercept:: DbEsdkInterceptor ;
7
+ use aws_db_esdk:: dynamodb:: types:: PlaintextOverride ;
8
+ use crate :: migration:: plaintext_to_awsdbe:: migration_utils:: {
9
+ verify_returned_item, ENCRYPTED_AND_SIGNED_VALUE , SIGN_ONLY_VALUE , DO_NOTHING_VALUE ,
10
+ } ;
11
+ use crate :: migration:: plaintext_to_awsdbe:: awsdbe:: common:: create_table_configs;
12
+
13
+ /*
14
+ Migration Step 1: This is the first step in the migration process from
15
+ plaintext to encrypted DynamoDB using the AWS Database Encryption SDK.
16
+
17
+ In this example, we configure a DynamoDB Encryption client to do the following:
18
+ 1. Write items only in plaintext
19
+ 2. Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration
20
+
21
+ While this step configures your client to be ready to start reading encrypted items,
22
+ we do not yet expect to be reading any encrypted items,
23
+ as our client still writes plaintext items.
24
+ Before you move on to step 2, ensure that these changes have successfully been deployed
25
+ to all of your readers.
26
+
27
+ Running this example requires access to the DDB Table whose name
28
+ is provided in the function parameter.
29
+ This table must be configured with the following
30
+ primary key configuration:
31
+ - Partition key is named "partition_key" with type (S)
32
+ - Sort key is named "sort_key" with type (N)
33
+ */
34
+ pub async fn migration_step_1_example (
35
+ kms_key_id : & str ,
36
+ ddb_table_name : & str ,
37
+ partition_key_value : & str ,
38
+ sort_key_write_value : & str ,
39
+ sort_key_read_value : & str ,
40
+ ) -> Result < bool , Box < dyn std:: error:: Error > > {
41
+ // 1. Create table configurations
42
+ // In this step of migration we will use PlaintextOverride::ForcePlaintextWriteAllowPlaintextRead
43
+ // which means:
44
+ // - Write: Items are forced to be written as plaintext.
45
+ // Items may not be written as encrypted items.
46
+ // - Read: Items are allowed to be read as plaintext.
47
+ // Items are allowed to be read as encrypted items.
48
+ let table_configs = create_table_configs (
49
+ kms_key_id,
50
+ ddb_table_name,
51
+ PlaintextOverride :: ForcePlaintextWriteAllowPlaintextRead ,
52
+ )
53
+ . await ?;
54
+
55
+ // 2. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs
56
+ let sdk_config = aws_config:: load_defaults ( aws_config:: BehaviorVersion :: latest ( ) ) . await ;
57
+ let dynamo_config = aws_sdk_dynamodb:: config:: Builder :: from ( & sdk_config)
58
+ . interceptor ( DbEsdkInterceptor :: new ( table_configs) ?)
59
+ . build ( ) ;
60
+ let ddb = aws_sdk_dynamodb:: Client :: from_conf ( dynamo_config) ;
61
+
62
+ // 3. Put an item into our table using the above client.
63
+ // This item will be stored in plaintext due to our PlaintextOverride configuration.
64
+ let partition_key_name = "partition_key" ;
65
+ let sort_key_name = "sort_key" ;
66
+ let encrypted_and_signed_value = ENCRYPTED_AND_SIGNED_VALUE ;
67
+ let sign_only_value = SIGN_ONLY_VALUE ;
68
+ let do_nothing_value = DO_NOTHING_VALUE ;
69
+ let item = HashMap :: from ( [
70
+ (
71
+ partition_key_name. to_string ( ) ,
72
+ AttributeValue :: S ( partition_key_value. to_string ( ) ) ,
73
+ ) ,
74
+ (
75
+ sort_key_name. to_string ( ) ,
76
+ AttributeValue :: N ( sort_key_write_value. to_string ( ) ) ,
77
+ ) ,
78
+ (
79
+ "attribute1" . to_string ( ) ,
80
+ AttributeValue :: S ( encrypted_and_signed_value. to_string ( ) ) ,
81
+ ) ,
82
+ (
83
+ "attribute2" . to_string ( ) ,
84
+ AttributeValue :: S ( sign_only_value. to_string ( ) ) ,
85
+ ) ,
86
+ (
87
+ "attribute3" . to_string ( ) ,
88
+ AttributeValue :: S ( do_nothing_value. to_string ( ) ) ,
89
+ ) ,
90
+ ] ) ;
91
+
92
+ ddb. put_item ( )
93
+ . table_name ( ddb_table_name)
94
+ . set_item ( Some ( item) )
95
+ . send ( )
96
+ . await ?;
97
+
98
+ // 4. Get an item back from the table using the same client.
99
+ // If this is an item written in plaintext (i.e. any item written
100
+ // during Step 0 or 1), then the item will still be in plaintext.
101
+ // If this is an item that was encrypted client-side (i.e. any item written
102
+ // during Step 2 or after), then the item will be decrypted client-side
103
+ // and surfaced as a plaintext item.
104
+ let key = HashMap :: from ( [
105
+ (
106
+ partition_key_name. to_string ( ) ,
107
+ AttributeValue :: S ( partition_key_value. to_string ( ) ) ,
108
+ ) ,
109
+ (
110
+ sort_key_name. to_string ( ) ,
111
+ AttributeValue :: N ( sort_key_read_value. to_string ( ) ) ,
112
+ ) ,
113
+ ] ) ;
114
+
115
+ let response = ddb
116
+ . get_item ( )
117
+ . table_name ( ddb_table_name)
118
+ . set_key ( Some ( key) )
119
+ // In this example we configure a strongly consistent read
120
+ // because we perform a read immediately after a write (for demonstrative purposes).
121
+ // By default, reads are only eventually consistent.
122
+ . consistent_read ( true )
123
+ . send ( )
124
+ . await ?;
125
+
126
+ // 5. Verify we get the expected item back
127
+ if let Some ( item) = response. item {
128
+ let success = verify_returned_item ( & item, partition_key_value, sort_key_read_value) ?;
129
+ if success {
130
+ println ! ( "MigrationStep1 completed successfully" ) ;
131
+ }
132
+ Ok ( success)
133
+ } else {
134
+ Err ( "No item found" . into ( ) )
135
+ }
136
+ }
137
+
138
+ #[ tokio:: test( flavor = "multi_thread" ) ]
139
+ async fn test_migration_step_1 ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
140
+ use crate :: migration:: plaintext_to_awsdbe:: plaintext:: migration_step_0:: migration_step_0_example;
141
+ use crate :: migration:: plaintext_to_awsdbe:: awsdbe:: migration_step_2:: migration_step_2_example;
142
+ use crate :: migration:: plaintext_to_awsdbe:: awsdbe:: migration_step_3:: migration_step_3_example;
143
+ use crate :: test_utils;
144
+ use uuid:: Uuid ;
145
+
146
+ let kms_key_id = test_utils:: TEST_KMS_KEY_ID ;
147
+ let table_name = test_utils:: TEST_DDB_TABLE_NAME ;
148
+ let partition_key = Uuid :: new_v4 ( ) . to_string ( ) ;
149
+ let sort_keys = [ "0" , "1" , "2" , "3" ] ;
150
+
151
+ // Successfully executes step 1
152
+ let success = migration_step_1_example ( kms_key_id, table_name, & partition_key, sort_keys[ 1 ] , sort_keys[ 1 ] ) . await ?;
153
+ assert ! ( success, "MigrationStep1 should complete successfully" ) ;
154
+
155
+ // Given: Step 0 has succeeded
156
+ let success = migration_step_0_example ( table_name, & partition_key, sort_keys[ 0 ] , sort_keys[ 0 ] ) . await ?;
157
+ assert ! ( success, "MigrationStep0 should complete successfully" ) ;
158
+
159
+ // When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read plaintext values from Step 0)
160
+ let success = migration_step_1_example ( kms_key_id, table_name, & partition_key, sort_keys[ 1 ] , sort_keys[ 0 ] ) . await ?;
161
+ assert ! ( success, "MigrationStep1 should be able to read items written by Step 0" ) ;
162
+
163
+ // Given: Step 2 has succeeded
164
+ let success = migration_step_2_example ( kms_key_id, table_name, & partition_key, sort_keys[ 2 ] , sort_keys[ 2 ] ) . await ?;
165
+ assert ! ( success, "MigrationStep2 should complete successfully" ) ;
166
+
167
+ // When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read encrypted values from Step 2)
168
+ let success = migration_step_1_example ( kms_key_id, table_name, & partition_key, sort_keys[ 1 ] , sort_keys[ 2 ] ) . await ?;
169
+ assert ! ( success, "MigrationStep1 should be able to read items written by Step 2" ) ;
170
+
171
+ // Given: Step 3 has succeeded
172
+ let success = migration_step_3_example ( kms_key_id, table_name, & partition_key, sort_keys[ 3 ] , sort_keys[ 3 ] ) . await ?;
173
+ assert ! ( success, "MigrationStep3 should complete successfully" ) ;
174
+
175
+ // When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read encrypted values from Step 3)
176
+ let success = migration_step_1_example ( kms_key_id, table_name, & partition_key, sort_keys[ 1 ] , sort_keys[ 3 ] ) . await ?;
177
+ assert ! ( success, "MigrationStep1 should be able to read items written by Step 3" ) ;
178
+
179
+ // Cleanup
180
+ for sort_key in & sort_keys {
181
+ test_utils:: cleanup_items ( table_name, & partition_key, sort_key) . await ?;
182
+ }
183
+
184
+ Ok ( ( ) )
185
+ }
0 commit comments