main/migration/plaintext_to_awsdbe/awsdbe/
migration_step_2.rs

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