main/basic_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 put_item_get_item() -> 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 item = HashMap::from([
127 (
128 "partition_key".to_string(),
129 AttributeValue::S("BasicPutGetExample".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
146 ddb.put_item()
147 .table_name(ddb_table_name)
148 .set_item(Some(item.clone()))
149 .send()
150 .await?;
151
152 // 7. Get the item back from our table using the same client.
153 // The client will decrypt the item client-side, and return
154 // back the original item.
155 let key_to_get = HashMap::from([
156 (
157 "partition_key".to_string(),
158 AttributeValue::S("BasicPutGetExample".to_string()),
159 ),
160 ("sort_key".to_string(), AttributeValue::N("0".to_string())),
161 ]);
162
163 let resp = ddb
164 .get_item()
165 .table_name(ddb_table_name)
166 .set_key(Some(key_to_get))
167 // In this example we configure a strongly consistent read
168 // because we perform a read immediately after a write (for demonstrative purposes).
169 // By default, reads are only eventually consistent.
170 // Read our docs to determine which read consistency to use for your application:
171 // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html
172 .consistent_read(true)
173 .send()
174 .await?;
175
176 assert_eq!(resp.item, Some(item));
177 println!("put_item_get_item successful.");
178 Ok(())
179}