Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5286619

Browse files
chore(rust): add plaintext to encrypted table migration examples (#1977)
1 parent c71fc82 commit 5286619

File tree

14 files changed

+996
-0
lines changed

14 files changed

+996
-0
lines changed

‎.github/workflows/library_rust_tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,4 @@ jobs:
146146
shell: bash
147147
run: |
148148
cargo run --release --example main
149+
cargo test --release --example main

‎DynamoDbEncryption/runtimes/rust/examples/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod keyring;
1414
pub mod multi_get_put_example;
1515
pub mod searchableencryption;
1616
pub mod test_utils;
17+
pub mod migration;
1718

1819
use std::convert::From;
1920

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
pub mod plaintext_to_awsdbe;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Plaintext DynamoDB Table to AWS Database Encryption SDK Encrypted Table Migration
2+
3+
This projects demonstrates the steps necessary
4+
to migrate to the AWS Database Encryption SDK for DynamoDb
5+
from a plaintext database.
6+
7+
[Step 0](plaintext/step0.go) demonstrates the starting state for your system.
8+
9+
## Step 1
10+
11+
In Step 1, you update your system to do the following:
12+
13+
- continue to read plaintext items
14+
- continue to write plaintext items
15+
- prepare to read encrypted items
16+
17+
When you deploy changes in Step 1,
18+
you should not expect any behavior change in your system,
19+
and your dataset still consists of plaintext data.
20+
21+
You must ensure that the changes in Step 1 make it to all your readers before you proceed to Step 2.
22+
23+
## Step 2
24+
25+
In Step 2, you update your system to do the following:
26+
27+
- continue to read plaintext items
28+
- start writing encrypted items
29+
- continue to read encrypted items
30+
31+
When you deploy changes in Step 2,
32+
you are introducing encrypted items to your system,
33+
and must make sure that all your readers are updated with the changes from Step 1.
34+
35+
Before you move onto the next step, you will need to encrypt all plaintext items in your dataset.
36+
Once you have completed this step,
37+
while new items are being encrypted using the new format and will be authenticated on read,
38+
your system will still accept reading plaintext, unauthenticated items.
39+
In order to complete migration to a system where you always authenticate your items,
40+
you should prioritize moving on to Step 3.
41+
42+
## Step 3
43+
44+
Once all old items are encrypted,
45+
update your system to do the following:
46+
47+
- continue to write encrypted items
48+
- continue to read encrypted items
49+
- do not accept reading plaintext items
50+
51+
Once you have deployed these changes to your system, you have completed migration.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use aws_db_esdk::material_providers::client;
5+
use aws_db_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
6+
use aws_db_esdk::CryptoAction;
7+
use aws_db_esdk::dynamodb::types::DynamoDbTableEncryptionConfig;
8+
use aws_db_esdk::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig;
9+
use aws_db_esdk::dynamodb::types::PlaintextOverride;
10+
use std::collections::HashMap;
11+
12+
pub async fn create_table_configs(
13+
kms_key_id: &str,
14+
ddb_table_name: &str,
15+
plaintext_override: PlaintextOverride,
16+
) -> Result<DynamoDbTablesEncryptionConfig, Box<dyn std::error::Error>> {
17+
// Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
18+
// For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
19+
// We will use the `CreateMrkMultiKeyring` method to create this keyring,
20+
// as it will correctly handle both single region and Multi-Region KMS Keys.
21+
let provider_config = MaterialProvidersConfig::builder().build()?;
22+
let mat_prov = client::Client::from_conf(provider_config)?;
23+
let kms_keyring = mat_prov
24+
.create_aws_kms_mrk_multi_keyring()
25+
.generator(kms_key_id)
26+
.send()
27+
.await?;
28+
29+
// Configure which attributes are encrypted and/or signed when writing new items.
30+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
31+
// we must explicitly configure how they should be treated during item encryption:
32+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
33+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
34+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
35+
let partition_key_name = "partition_key";
36+
let sort_key_name = "sort_key";
37+
let attribute_actions_on_encrypt = HashMap::from([
38+
(partition_key_name.to_string(), CryptoAction::SignOnly),
39+
(sort_key_name.to_string(), CryptoAction::SignOnly),
40+
("attribute1".to_string(), CryptoAction::EncryptAndSign),
41+
("attribute2".to_string(), CryptoAction::SignOnly),
42+
("attribute3".to_string(), CryptoAction::DoNothing),
43+
]);
44+
45+
// Configure which attributes we expect to be excluded in the signature
46+
// when reading items. There are two options for configuring this:
47+
//
48+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
49+
// When defining your DynamoDb schema and deciding on attribute names,
50+
// choose a distinguishing prefix (such as ":") for all attributes that
51+
// you do not want to include in the signature.
52+
// This has two main benefits:
53+
// - It is easier to reason about the security and authenticity of data within your item
54+
// when all unauthenticated data is easily distinguishable by their attribute name.
55+
// - If you need to add new unauthenticated attributes in the future,
56+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
57+
// and immediately start writing to that new attribute, without
58+
// any other configuration update needed.
59+
// Once you configure this field, it is not safe to update it.
60+
//
61+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
62+
// a set of attributes that should be considered unauthenticated when encountered
63+
// on read. Be careful if you use this configuration. Do not remove an attribute
64+
// name from this configuration, even if you are no longer writing with that attribute,
65+
// as old items may still include this attribute, and our configuration needs to know
66+
// to continue to exclude this attribute from the signature scope.
67+
// If you add new attribute names to this field, you must first deploy the update to this
68+
// field to all readers in your host fleet before deploying the update to start writing
69+
// with that new attribute.
70+
//
71+
// For this example, we will explicitly list the attributes that are not signed.
72+
let unsigned_attributes = vec!["attribute3".to_string()];
73+
74+
// Create the DynamoDb Encryption configuration for the table we will be writing to.
75+
let table_config = DynamoDbTableEncryptionConfig::builder()
76+
.logical_table_name(ddb_table_name)
77+
.partition_key_name(partition_key_name)
78+
.sort_key_name(sort_key_name)
79+
.attribute_actions_on_encrypt(attribute_actions_on_encrypt)
80+
.keyring(kms_keyring)
81+
.allowed_unsigned_attributes(unsigned_attributes)
82+
.plaintext_override(plaintext_override)
83+
.build()?;
84+
85+
let table_configs = DynamoDbTablesEncryptionConfig::builder()
86+
.table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
87+
.build()?;
88+
89+
Ok(table_configs)
90+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /