main/
multi_get_put_example.rs

1// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::test_utils;
5use aws_sdk_dynamodb::types::AttributeValue;
6use std::collections::HashMap;
7
8use aws_db_esdk::material_providers::client;
9use aws_db_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
10use aws_db_esdk::CryptoAction;
11
12use aws_db_esdk::dynamodb::types::DynamoDbTableEncryptionConfig;
13use aws_db_esdk::intercept::DbEsdkInterceptor;
14use aws_db_esdk::material_providers::types::DbeAlgorithmSuiteId;
15use aws_db_esdk::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig;
16
17/*
18 This example sets up DynamoDb Encryption for the AWS SDK client
19 and uses the low level PutItem and GetItem DDB APIs to demonstrate
20 putting a client-side encrypted item into DynamoDb
21 and then retrieving and decrypting that item from DynamoDb.
22
23 Running this example requires access to the DDB Table whose name
24 is provided in CLI arguments.
25 This table must be configured with the following
26 primary key configuration:
27   - Partition key is named "partition_key" with type (S)
28   - Sort key is named "sort_key" with type (N)
29*/
30
31pub async fn multi_put_get() -> Result<(), crate::BoxError> {
32    let kms_key_id = test_utils::TEST_KMS_KEY_ID;
33    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
34
35    // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
36    //    For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
37    //    We will use the `CreateMrkMultiKeyring` method to create this keyring,
38    //    as it will correctly handle both single region and Multi-Region KMS Keys.
39    let provider_config = MaterialProvidersConfig::builder().build()?;
40    let mat_prov = client::Client::from_conf(provider_config)?;
41    let kms_keyring = mat_prov
42        .create_aws_kms_mrk_multi_keyring()
43        .generator(kms_key_id)
44        .send()
45        .await?;
46
47    // 2. Configure which attributes are encrypted and/or signed when writing new items.
48    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
49    //    we must explicitly configure how they should be treated during item encryption:
50    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
51    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
52    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
53    let attribute_actions_on_encrypt = HashMap::from([
54        ("partition_key".to_string(), CryptoAction::SignOnly),
55        ("sort_key".to_string(), CryptoAction::SignOnly),
56        ("attribute1".to_string(), CryptoAction::EncryptAndSign),
57        ("attribute2".to_string(), CryptoAction::SignOnly),
58        (":attribute3".to_string(), CryptoAction::DoNothing),
59    ]);
60
61    // 3. Configure which attributes we expect to be included in the signature
62    //    when reading items. There are two options for configuring this:
63    //
64    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
65    //      When defining your DynamoDb schema and deciding on attribute names,
66    //      choose a distinguishing prefix (such as ":") for all attributes that
67    //      you do not want to include in the signature.
68    //      This has two main benefits:
69    //      - It is easier to reason about the security and authenticity of data within your item
70    //        when all unauthenticated data is easily distinguishable by their attribute name.
71    //      - If you need to add new unauthenticated attributes in the future,
72    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
73    //        and immediately start writing to that new attribute, without
74    //        any other configuration update needed.
75    //      Once you configure this field, it is not safe to update it.
76    //
77    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
78    //      a set of attributes that should be considered unauthenticated when encountered
79    //      on read. Be careful if you use this configuration. Do not remove an attribute
80    //      name from this configuration, even if you are no longer writing with that attribute,
81    //      as old items may still include this attribute, and our configuration needs to know
82    //      to continue to exclude this attribute from the signature scope.
83    //      If you add new attribute names to this field, you must first deploy the update to this
84    //      field to all readers in your host fleet before deploying the update to start writing
85    //      with that new attribute.
86    //
87    //   For this example, we have designed our DynamoDb table such that any attribute name with
88    //   the ":" prefix should be considered unauthenticated.
89    const UNSIGNED_ATTR_PREFIX: &str = ":";
90
91    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
92    let table_config = DynamoDbTableEncryptionConfig::builder()
93        .logical_table_name(ddb_table_name)
94        .partition_key_name("partition_key")
95        .sort_key_name("sort_key")
96        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
97        .keyring(kms_keyring)
98        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
99        // Specifying an algorithm suite is not required,
100        // but is done here to demonstrate how to do so.
101        // We suggest using the
102        // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
103        // which includes AES-GCM with key derivation, signing, and key commitment.
104        // This is also the default algorithm suite if one is not specified in this config.
105        // For more information on supported algorithm suites, see:
106        //   https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
107        .algorithm_suite_id(
108            DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384,
109        )
110        .build()?;
111
112    let table_configs = DynamoDbTablesEncryptionConfig::builder()
113        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
114        .build()?;
115
116    // 5. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs
117    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
118    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
119        .interceptor(DbEsdkInterceptor::new(table_configs)?)
120        .build();
121    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
122
123    // 6. Put an item into our table using the above client.
124    //    Before the item gets sent to DynamoDb, it will be encrypted
125    //    client-side, according to our configuration.
126    let batch_write_item = HashMap::from([
127        (
128            "partition_key".to_string(),
129            AttributeValue::S("BatchWriteItemExample".to_string()),
130        ),
131        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
132        (
133            "attribute1".to_string(),
134            AttributeValue::S("encrypt and sign me!".to_string()),
135        ),
136        (
137            "attribute2".to_string(),
138            AttributeValue::S("sign me!".to_string()),
139        ),
140        (
141            ":attribute3".to_string(),
142            AttributeValue::S("ignore me!".to_string()),
143        ),
144    ]);
145    let put_request = aws_sdk_dynamodb::types::PutRequest::builder()
146        .set_item(Some(batch_write_item))
147        .build()?;
148
149    let batch_write_request = aws_sdk_dynamodb::types::WriteRequest::builder()
150        .put_request(put_request)
151        .build();
152
153    let transact_write_item = HashMap::from([
154        (
155            "partition_key".to_string(),
156            AttributeValue::S("TransactWriteItemExample".to_string()),
157        ),
158        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
159        (
160            "attribute1".to_string(),
161            AttributeValue::S("encrypt and sign me!".to_string()),
162        ),
163        (
164            "attribute2".to_string(),
165            AttributeValue::S("sign me!".to_string()),
166        ),
167        (
168            ":attribute3".to_string(),
169            AttributeValue::S("ignore me!".to_string()),
170        ),
171    ]);
172    let transact_put = aws_sdk_dynamodb::types::Put::builder()
173        .table_name(ddb_table_name)
174        .set_item(Some(transact_write_item))
175        .build()?;
176
177    let transact_item = aws_sdk_dynamodb::types::TransactWriteItem::builder()
178        .put(transact_put)
179        .build();
180
181    ddb.batch_write_item()
182        .request_items(ddb_table_name, vec![batch_write_request])
183        .send()
184        .await?;
185
186    ddb.transact_write_items()
187        .transact_items(transact_item)
188        .send()
189        .await?;
190
191    // 7. Get the item back from our table using the same client.
192    //    The client will decrypt the item client-side, and return
193    //    back the original item.
194    let batch_get_keys = HashMap::from([
195        (
196            "partition_key".to_string(),
197            AttributeValue::S("BatchWriteItemExample".to_string()),
198        ),
199        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
200    ]);
201    let keys_and_attr = aws_sdk_dynamodb::types::KeysAndAttributes::builder()
202        .keys(batch_get_keys)
203        .consistent_read(true)
204        .build()?;
205
206    let batch_get_response = ddb
207        .batch_get_item()
208        .request_items(ddb_table_name, keys_and_attr)
209        .send()
210        .await?;
211
212    let returned_item = &batch_get_response.responses.unwrap()[ddb_table_name][0];
213    assert_eq!(
214        returned_item["attribute1"],
215        AttributeValue::S("encrypt and sign me!".to_string())
216    );
217
218    let transact_get_keys = HashMap::from([
219        (
220            "partition_key".to_string(),
221            AttributeValue::S("TransactWriteItemExample".to_string()),
222        ),
223        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
224    ]);
225    let transact_get = aws_sdk_dynamodb::types::Get::builder()
226        .table_name(ddb_table_name)
227        .set_key(Some(transact_get_keys))
228        .build()?;
229
230    let transact_get_item = aws_sdk_dynamodb::types::TransactGetItem::builder()
231        .get(transact_get)
232        .build();
233
234    let transact_get_response = ddb
235        .transact_get_items()
236        .transact_items(transact_get_item)
237        .send()
238        .await?;
239
240    let the_item = transact_get_response.responses.as_ref().unwrap()[0]
241        .item
242        .as_ref()
243        .unwrap();
244    assert_eq!(
245        the_item["attribute1"],
246        AttributeValue::S("encrypt and sign me!".to_string())
247    );
248
249    println!("multi_put_get successful.");
250    Ok(())
251}