Skip to content

Commit 04f569b

Browse files
auto commit
1 parent db45578 commit 04f569b

File tree

11 files changed

+938
-0
lines changed

11 files changed

+938
-0
lines changed
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: 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::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: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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::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+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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::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 2: This is the second 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 with encryption (no longer writing plaintext)
19+
2. Read both plaintext items and encrypted items
20+
21+
Once you deploy this change to your system, you will have a dataset
22+
containing both encrypted and plaintext items.
23+
Because the changes in Step 1 have been deployed to all readers,
24+
we can be sure that our entire system is ready to read this new data.
25+
26+
Before you move onto the next step, you will need to encrypt all plaintext items in your dataset.
27+
How you will want to do this depends on your system.
28+
29+
Running this example requires access to the DDB Table whose name
30+
is provided in the function parameter.
31+
This table must be configured with the following
32+
primary key configuration:
33+
- Partition key is named "partition_key" with type (S)
34+
- Sort key is named "sort_key" with type (N)
35+
*/
36+
pub 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+
}

0 commit comments

Comments
 (0)