main/migration/plaintext_to_awsdbe/awsdbe/
migration_step_1.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 1: This is the first 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 only in plaintext
192. Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration
20
21While this step configures your client to be ready to start reading encrypted items,
22we do not yet expect to be reading any encrypted items,
23as our client still writes plaintext items.
24Before you move on to step 2, ensure that these changes have successfully been deployed
25to all of your readers.
26
27Running this example requires access to the DDB Table whose name
28is provided in the function parameter.
29This table must be configured with the following
30primary key configuration:
31  - Partition key is named "partition_key" with type (S)
32  - Sort key is named "sort_key" with type (N)
33*/
34pub 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")]
139async fn test_migration_step_1() -> Result<(), Box<dyn std::error::Error>> {
140    use crate::migration::plaintext_to_awsdbe::awsdbe::migration_step_2::migration_step_2_example;
141    use crate::migration::plaintext_to_awsdbe::awsdbe::migration_step_3::migration_step_3_example;
142    use crate::migration::plaintext_to_awsdbe::plaintext::migration_step_0::migration_step_0_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(
153        kms_key_id,
154        table_name,
155        &partition_key,
156        sort_keys[1],
157        sort_keys[1],
158    )
159    .await?;
160    assert!(success, "MigrationStep1 should complete successfully");
161
162    // Given: Step 0 has succeeded
163    let success =
164        migration_step_0_example(table_name, &partition_key, sort_keys[0], sort_keys[0]).await?;
165    assert!(success, "MigrationStep0 should complete successfully");
166
167    // When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read plaintext values from Step 0)
168    let success = migration_step_1_example(
169        kms_key_id,
170        table_name,
171        &partition_key,
172        sort_keys[1],
173        sort_keys[0],
174    )
175    .await?;
176    assert!(
177        success,
178        "MigrationStep1 should be able to read items written by Step 0"
179    );
180
181    // Given: Step 2 has succeeded
182    let success = migration_step_2_example(
183        kms_key_id,
184        table_name,
185        &partition_key,
186        sort_keys[2],
187        sort_keys[2],
188    )
189    .await?;
190    assert!(success, "MigrationStep2 should complete successfully");
191
192    // When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read encrypted values from Step 2)
193    let success = migration_step_1_example(
194        kms_key_id,
195        table_name,
196        &partition_key,
197        sort_keys[1],
198        sort_keys[2],
199    )
200    .await?;
201    assert!(
202        success,
203        "MigrationStep1 should be able to read items written by Step 2"
204    );
205
206    // Given: Step 3 has succeeded
207    let success = migration_step_3_example(
208        kms_key_id,
209        table_name,
210        &partition_key,
211        sort_keys[3],
212        sort_keys[3],
213    )
214    .await?;
215    assert!(success, "MigrationStep3 should complete successfully");
216
217    // When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read encrypted values from Step 3)
218    let success = migration_step_1_example(
219        kms_key_id,
220        table_name,
221        &partition_key,
222        sort_keys[1],
223        sort_keys[3],
224    )
225    .await?;
226    assert!(
227        success,
228        "MigrationStep1 should be able to read items written by Step 3"
229    );
230
231    // Cleanup
232    for sort_key in &sort_keys {
233        test_utils::cleanup_items(table_name, &partition_key, sort_key).await?;
234    }
235
236    Ok(())
237}