Struct Client

Source
pub struct Client { /* private fields */ }

Implementations§

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source

pub fn create_aws_kms_mrk_multi_keyring( &self, ) -> CreateAwsKmsMrkMultiKeyringFluentBuilder

Examples found in repository?
examples/itemencryptor/item_encrypt_decrypt.rs (line 45)
34pub async fn encrypt_decrypt() -> Result<(), crate::BoxError> {
35    let kms_key_id = test_utils::TEST_KMS_KEY_ID;
36    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
37
38    // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
39    //    For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
40    //    We will use the `CreateMrkMultiKeyring` method to create this keyring,
41    //    as it will correctly handle both single region and Multi-Region KMS Keys.
42    let provider_config = MaterialProvidersConfig::builder().build()?;
43    let mat_prov = mpl_client::Client::from_conf(provider_config)?;
44    let kms_keyring = mat_prov
45        .create_aws_kms_mrk_multi_keyring()
46        .generator(kms_key_id)
47        .send()
48        .await?;
49
50    // 2. Configure which attributes are encrypted and/or signed when writing new items.
51    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
52    //    we must explicitly configure how they should be treated during item encryption:
53    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
54    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
55    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
56    let attribute_actions_on_encrypt = HashMap::from([
57        ("partition_key".to_string(), CryptoAction::SignOnly),
58        ("sort_key".to_string(), CryptoAction::SignOnly),
59        ("attribute1".to_string(), CryptoAction::EncryptAndSign),
60        ("attribute2".to_string(), CryptoAction::SignOnly),
61        (":attribute3".to_string(), CryptoAction::DoNothing),
62    ]);
63
64    // 3. Configure which attributes we expect to be included in the signature
65    //    when reading items. There are two options for configuring this:
66    //
67    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
68    //      When defining your DynamoDb schema and deciding on attribute names,
69    //      choose a distinguishing prefix (such as ":") for all attributes that
70    //      you do not want to include in the signature.
71    //      This has two main benefits:
72    //      - It is easier to reason about the security and authenticity of data within your item
73    //        when all unauthenticated data is easily distinguishable by their attribute name.
74    //      - If you need to add new unauthenticated attributes in the future,
75    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
76    //        and immediately start writing to that new attribute, without
77    //        any other configuration update needed.
78    //      Once you configure this field, it is not safe to update it.
79    //
80    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
81    //      a set of attributes that should be considered unauthenticated when encountered
82    //      on read. Be careful if you use this configuration. Do not remove an attribute
83    //      name from this configuration, even if you are no longer writing with that attribute,
84    //      as old items may still include this attribute, and our configuration needs to know
85    //      to continue to exclude this attribute from the signature scope.
86    //      If you add new attribute names to this field, you must first deploy the update to this
87    //      field to all readers in your host fleet before deploying the update to start writing
88    //      with that new attribute.
89    //
90    //   For this example, we have designed our DynamoDb table such that any attribute name with
91    //   the ":" prefix should be considered unauthenticated.
92    const UNSIGNED_ATTR_PREFIX: &str = ":";
93
94    // 4. Create the configuration for the DynamoDb Item Encryptor
95    let config = DynamoDbItemEncryptorConfig::builder()
96        .logical_table_name(ddb_table_name)
97        .partition_key_name("partition_key")
98        .sort_key_name("sort_key")
99        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
100        .keyring(kms_keyring)
101        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
102        // Specifying an algorithm suite is not required,
103        // but is done here to demonstrate how to do so.
104        // We suggest using the
105        // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
106        // which includes AES-GCM with key derivation, signing, and key commitment.
107        // This is also the default algorithm suite if one is not specified in this config.
108        // For more information on supported algorithm suites, see:
109        //   https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
110        .algorithm_suite_id(
111            DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384,
112        )
113        .build()?;
114
115    // 5. Create the DynamoDb Item Encryptor
116    let item_encryptor = enc_client::Client::from_conf(config)?;
117
118    // 6. Directly encrypt a DynamoDb item using the DynamoDb Item Encryptor
119    let original_item = HashMap::from([
120        (
121            "partition_key".to_string(),
122            AttributeValue::S("ItemEncryptDecryptExample".to_string()),
123        ),
124        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
125        (
126            "attribute1".to_string(),
127            AttributeValue::S("encrypt and sign me!".to_string()),
128        ),
129        (
130            "attribute2".to_string(),
131            AttributeValue::S("sign me!".to_string()),
132        ),
133        (
134            ":attribute3".to_string(),
135            AttributeValue::S("ignore me!".to_string()),
136        ),
137    ]);
138
139    let encrypted_item = item_encryptor
140        .encrypt_item()
141        .plaintext_item(original_item.clone())
142        .send()
143        .await?
144        .encrypted_item
145        .unwrap();
146
147    // Demonstrate that the item has been encrypted
148    assert_eq!(
149        encrypted_item["partition_key"],
150        AttributeValue::S("ItemEncryptDecryptExample".to_string())
151    );
152    assert_eq!(
153        encrypted_item["sort_key"],
154        AttributeValue::N("0".to_string())
155    );
156    assert!(encrypted_item["attribute1"].is_b());
157    assert!(!encrypted_item["attribute1"].is_s());
158
159    // 7. Directly decrypt the encrypted item using the DynamoDb Item Encryptor
160    let decrypted_item = item_encryptor
161        .decrypt_item()
162        .encrypted_item(encrypted_item)
163        .send()
164        .await?
165        .plaintext_item
166        .unwrap();
167
168    // Demonstrate that GetItem succeeded and returned the decrypted item
169    assert_eq!(decrypted_item, original_item);
170    println!("encrypt_decrypt successful.");
171    Ok(())
172}
More examples
Hide additional examples
examples/basic_get_put_example.rs (line 42)
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}
examples/keyring/mrk_discovery_multi_keyring.rs (line 54)
37pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
38    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
39    let key_arn = test_utils::TEST_MRK_KEY_ID;
40    let account_ids = vec![test_utils::TEST_AWS_ACCOUNT_ID.to_string()];
41    let regions = vec![test_utils::TEST_AWS_REGION.to_string()];
42
43    // 1. Create a single MRK multi-keyring using the key arn.
44    //    Although this example demonstrates use of the MRK discovery multi-keyring,
45    //    a discovery keyring cannot be used to encrypt. So we will need to construct
46    //    a non-discovery keyring for this example to encrypt. For more information on MRK
47    //    multi-keyrings, see the MultiMrkKeyringExample in this directory.
48    //    Though this is an "MRK multi-keyring", we do not need to provide multiple keys,
49    //    and can use single-region KMS keys. We will provide a single key here; this
50    //    can be either an MRK or a single-region key.
51    let mpl_config = MaterialProvidersConfig::builder().build()?;
52    let mpl = mpl_client::Client::from_conf(mpl_config)?;
53    let encrypt_keyring = mpl
54        .create_aws_kms_mrk_multi_keyring()
55        .generator(key_arn)
56        .send()
57        .await?;
58
59    // 2. Configure which attributes are encrypted and/or signed when writing new items.
60    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
61    //    we must explicitly configure how they should be treated during item encryption:
62    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and icncluded in the signature
63    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
64    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
65    let attribute_actions_on_encrypt = HashMap::from([
66        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
67        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
68        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
69    ]);
70
71    // 3. Configure which attributes we expect to be included in the signature
72    //    when reading items. There are two options for configuring this:
73    //
74    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
75    //      When defining your DynamoDb schema and deciding on attribute names,
76    //      choose a distinguishing prefix (such as ":") for all attributes that
77    //      you do not want to include in the signature.
78    //      This has two main benefits:
79    //      - It is easier to reason about the security and authenticity of data within your item
80    //        when all unauthenticated data is easily distinguishable by their attribute name.
81    //      - If you need to add new unauthenticated attributes in the future,
82    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
83    //        and immediately start writing to that new attribute, without
84    //        any other configuration update needed.
85    //      Once you configure this field, it is not safe to update it.
86    //
87    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
88    //      a set of attributes that should be considered unauthenticated when encountered
89    //      on read. Be careful if you use this configuration. Do not remove an attribute
90    //      name from this configuration, even if you are no longer writing with that attribute,
91    //      as old items may still include this attribute, and our configuration needs to know
92    //      to continue to exclude this attribute from the signature scope.
93    //      If you add new attribute names to this field, you must first deploy the update to this
94    //      field to all readers in your host fleet before deploying the update to start writing
95    //      with that new attribute.
96    //
97    //   For this example, we currently authenticate all attributes. To make it easier to
98    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
99    const UNSIGNED_ATTR_PREFIX: &str = ":";
100
101    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
102    let table_config = DynamoDbTableEncryptionConfig::builder()
103        .logical_table_name(ddb_table_name)
104        .partition_key_name("partition_key")
105        .sort_key_name("sort_key")
106        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
107        .keyring(encrypt_keyring)
108        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
109        .build()?;
110
111    let table_configs = DynamoDbTablesEncryptionConfig::builder()
112        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
113        .build()?;
114
115    // 5. Create a new AWS SDK DynamoDb client using the config above
116    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
117    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
118        .interceptor(DbEsdkInterceptor::new(table_configs)?)
119        .build();
120    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
121
122    // 6. Put an item into our table using the above client.
123    //    Before the item gets sent to DynamoDb, it will be encrypted
124    //    client-side using the MRK multi-keyring.
125    let item = HashMap::from([
126        (
127            "partition_key".to_string(),
128            AttributeValue::S("awsKmsMrkDiscoveryMultiKeyringItem".to_string()),
129        ),
130        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
131        (
132            "sensitive_data".to_string(),
133            AttributeValue::S("encrypt and sign me!".to_string()),
134        ),
135    ]);
136
137    ddb.put_item()
138        .table_name(ddb_table_name)
139        .set_item(Some(item.clone()))
140        .send()
141        .await?;
142
143    // 7. Construct a discovery filter.
144    //    A discovery filter limits the set of encrypted data keys
145    //    the keyring can use to decrypt data.
146    //    We will only let the keyring use keys in the selected AWS accounts
147    //    and in the `aws` partition.
148    //    This is the suggested config for most users; for more detailed config, see
149    //      https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
150    let discovery_filter = DiscoveryFilter::builder()
151        .partition("aws")
152        .account_ids(account_ids)
153        .build()?;
154
155    // 8. Construct a discovery keyring.
156    //    Note that we choose to use the MRK discovery multi-keyring, even though
157    //    our original keyring used a single KMS key.
158
159    let decrypt_keyring = mpl
160        .create_aws_kms_mrk_discovery_multi_keyring()
161        .discovery_filter(discovery_filter)
162        .regions(regions)
163        .send()
164        .await?;
165
166    // 9. Create new DDB config and client using the decrypt discovery keyring.
167    //     This is the same as the above config, except we pass in the decrypt keyring.
168    let table_config_for_decrypt = DynamoDbTableEncryptionConfig::builder()
169        .logical_table_name(ddb_table_name)
170        .partition_key_name("partition_key")
171        .sort_key_name("sort_key")
172        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
173        .keyring(decrypt_keyring)
174        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
175        .build()?;
176
177    let table_configs_for_decrypt = DynamoDbTablesEncryptionConfig::builder()
178        .table_encryption_configs(HashMap::from([(
179            ddb_table_name.to_string(),
180            table_config_for_decrypt,
181        )]))
182        .build()?;
183
184    let dynamo_config_for_decrypt = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
185        .interceptor(DbEsdkInterceptor::new(table_configs_for_decrypt)?)
186        .build();
187    let ddb_for_decrypt = aws_sdk_dynamodb::Client::from_conf(dynamo_config_for_decrypt);
188
189    // 10. Get the item back from our table using the client.
190    //     The client will retrieve encrypted items from the DDB table, then
191    //     detect the KMS key that was used to encrypt their data keys.
192    //     The client will make a request to KMS to decrypt with the encrypting KMS key.
193    //     If the client has permission to decrypt with the KMS key,
194    //     the client will decrypt the item client-side using the keyring
195    //     and return the original item.
196    let key_to_get = HashMap::from([
197        (
198            "partition_key".to_string(),
199            AttributeValue::S("awsKmsMrkDiscoveryMultiKeyringItem".to_string()),
200        ),
201        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
202    ]);
203
204    let resp = ddb_for_decrypt
205        .get_item()
206        .table_name(ddb_table_name)
207        .set_key(Some(key_to_get))
208        .consistent_read(true)
209        .send()
210        .await?;
211
212    assert_eq!(resp.item, Some(item));
213
214    println!("mrk_discovery_multi_keyring successful.");
215    Ok(())
216}
examples/keyring/multi_keyring.rs (line 68)
46pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
47    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
48    let key_arn = test_utils::TEST_KMS_KEY_ID;
49    let aes_key_bytes = generate_aes_key_bytes();
50
51    // 1. Create the raw AES keyring.
52    let mpl_config = MaterialProvidersConfig::builder().build()?;
53    let mpl = mpl_client::Client::from_conf(mpl_config)?;
54    let raw_aes_keyring = mpl
55        .create_raw_aes_keyring()
56        .key_name("my-aes-key-name")
57        .key_namespace("my-key-namespace")
58        .wrapping_key(aes_key_bytes)
59        .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16)
60        .send()
61        .await?;
62
63    // 2. Create the AWS KMS keyring.
64    //    We create a MRK multi keyring, as this interface also supports
65    //    single-region KMS keys (standard KMS keys),
66    //    and creates the KMS client for us automatically.
67    let aws_kms_mrk_multi_keyring = mpl
68        .create_aws_kms_mrk_multi_keyring()
69        .generator(key_arn)
70        .send()
71        .await?;
72
73    // 3. Create the multi-keyring.
74    //    We will label the AWS KMS keyring as the generator and the raw AES keyring as the
75    //        only child keyring.
76    //    You must provide a generator keyring to encrypt data.
77    //    You may provide additional child keyrings. Each child keyring will be able to
78    //        decrypt data encrypted with the multi-keyring on its own. It does not need
79    //        knowledge of any other child keyrings or the generator keyring to decrypt.
80
81    let multi_keyring = mpl
82        .create_multi_keyring()
83        .generator(aws_kms_mrk_multi_keyring)
84        .child_keyrings(vec![raw_aes_keyring.clone()])
85        .send()
86        .await?;
87
88    // 4. Configure which attributes are encrypted and/or signed when writing new items.
89    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
90    //    we must explicitly configure how they should be treated during item encryption:
91    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
92    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
93    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
94    let attribute_actions_on_encrypt = HashMap::from([
95        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
96        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
97        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
98    ]);
99
100    // 5. Configure which attributes we expect to be included in the signature
101    //    when reading items. There are two options for configuring this:
102    //
103    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
104    //      When defining your DynamoDb schema and deciding on attribute names,
105    //      choose a distinguishing prefix (such as ":") for all attributes that
106    //      you do not want to include in the signature.
107    //      This has two main benefits:
108    //      - It is easier to reason about the security and authenticity of data within your item
109    //        when all unauthenticated data is easily distinguishable by their attribute name.
110    //      - If you need to add new unauthenticated attributes in the future,
111    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
112    //        and immediately start writing to that new attribute, without
113    //        any other configuration update needed.
114    //      Once you configure this field, it is not safe to update it.
115    //
116    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
117    //      a set of attributes that should be considered unauthenticated when encountered
118    //      on read. Be careful if you use this configuration. Do not remove an attribute
119    //      name from this configuration, even if you are no longer writing with that attribute,
120    //      as old items may still include this attribute, and our configuration needs to know
121    //      to continue to exclude this attribute from the signature scope.
122    //      If you add new attribute names to this field, you must first deploy the update to this
123    //      field to all readers in your host fleet before deploying the update to start writing
124    //      with that new attribute.
125    //
126    //   For this example, we currently authenticate all attributes. To make it easier to
127    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
128    const UNSIGNED_ATTR_PREFIX: &str = ":";
129
130    // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
131    //    Note that this example creates one config/client combination for PUT, and another
132    //        for GET. The PUT config uses the multi-keyring, while the GET config uses the
133    //        raw AES keyring. This is solely done to demonstrate that a keyring included as
134    //        a child of a multi-keyring can be used to decrypt data on its own.
135    let table_config = DynamoDbTableEncryptionConfig::builder()
136        .logical_table_name(ddb_table_name)
137        .partition_key_name("partition_key")
138        .sort_key_name("sort_key")
139        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
140        .keyring(multi_keyring)
141        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
142        .build()?;
143
144    let table_configs = DynamoDbTablesEncryptionConfig::builder()
145        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
146        .build()?;
147
148    // 7. Create a new AWS SDK DynamoDb client using the config above
149    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
150    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
151        .interceptor(DbEsdkInterceptor::new(table_configs)?)
152        .build();
153    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
154
155    // 8. Put an item into our table using the above client.
156    //    Before the item gets sent to DynamoDb, it will be encrypted
157    //    client-side using the multi-keyring.
158    //    The item will be encrypted with all wrapping keys in the keyring,
159    //    so that it can be decrypted with any one of the keys.
160    let item = HashMap::from([
161        (
162            "partition_key".to_string(),
163            AttributeValue::S("multiKeyringItem".to_string()),
164        ),
165        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
166        (
167            "sensitive_data".to_string(),
168            AttributeValue::S("encrypt and sign me!".to_string()),
169        ),
170    ]);
171
172    ddb.put_item()
173        .table_name(ddb_table_name)
174        .set_item(Some(item.clone()))
175        .send()
176        .await?;
177
178    // 9. Get the item back from our table using the above client.
179    //     The client will decrypt the item client-side using the AWS KMS
180    //     keyring, and return back the original item.
181    //     Since the generator key is the first available key in the keyring,
182    //     that is the key that will be used to decrypt this item.
183    let key_to_get = HashMap::from([
184        (
185            "partition_key".to_string(),
186            AttributeValue::S("multiKeyringItem".to_string()),
187        ),
188        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
189    ]);
190
191    let resp = ddb
192        .get_item()
193        .table_name(ddb_table_name)
194        .set_key(Some(key_to_get.clone()))
195        .consistent_read(true)
196        .send()
197        .await?;
198
199    assert_eq!(resp.item, Some(item.clone()));
200
201    // 10. Create a new config and client with only the raw AES keyring to GET the item
202    //     This is the same setup as above, except the config uses the `rawAesKeyring`.
203    let only_aes_table_config = DynamoDbTableEncryptionConfig::builder()
204        .logical_table_name(ddb_table_name)
205        .partition_key_name("partition_key")
206        .sort_key_name("sort_key")
207        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
208        .keyring(raw_aes_keyring)
209        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
210        .build()?;
211
212    let only_aes_table_configs = DynamoDbTablesEncryptionConfig::builder()
213        .table_encryption_configs(HashMap::from([(
214            ddb_table_name.to_string(),
215            only_aes_table_config,
216        )]))
217        .build()?;
218
219    let only_aes_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
220        .interceptor(DbEsdkInterceptor::new(only_aes_table_configs)?)
221        .build();
222    let only_aes_ddb = aws_sdk_dynamodb::Client::from_conf(only_aes_dynamo_config);
223
224    // 11. Get the item back from our table using the client
225    //     configured with only the raw AES keyring.
226    //     The client will decrypt the item client-side using the raw
227    //     AES keyring, and return back the original item.
228    let resp = only_aes_ddb
229        .get_item()
230        .table_name(ddb_table_name)
231        .set_key(Some(key_to_get.clone()))
232        .consistent_read(true)
233        .send()
234        .await?;
235
236    assert_eq!(resp.item, Some(item.clone()));
237
238    println!("multi_keyring successful.");
239    Ok(())
240}
examples/multi_get_put_example.rs (line 42)
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}
examples/clientsupplier/client_supplier_example.rs (line 61)
37pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
38    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
39    // Note that we pass in an MRK in us-east-1...
40    let key_arn = test_utils::TEST_MRK_REPLICA_KEY_ID_US_EAST_1.to_string();
41    let account_ids = vec![test_utils::TEST_AWS_ACCOUNT_ID.to_string()];
42    // ...and access its replica in eu-west-1
43    let regions = vec!["eu-west-1".to_string()];
44
45    // 1. Create a single MRK multi-keyring.
46    //    This can be either a single-region KMS key or an MRK.
47    //    For this example to succeed, the key's region must either
48    //    1) be in the regions list, or
49    //    2) the key must be an MRK with a replica defined
50    //    in a region in the regions list, and the client
51    //    must have the correct permissions to access the replica.
52    let mpl_config = MaterialProvidersConfig::builder().build()?;
53    let mpl = mpl_client::Client::from_conf(mpl_config)?;
54
55    // Create the multi-keyring using our custom client supplier
56    // defined in the RegionalRoleClientSupplier class in this directory.
57    // Note: RegionalRoleClientSupplier will internally use the key_arn's region
58    // to retrieve the correct IAM role.
59
60    let mrk_keyring_with_client_supplier = mpl
61        .create_aws_kms_mrk_multi_keyring()
62        .client_supplier(RegionalRoleClientSupplier {})
63        .generator(key_arn)
64        .send()
65        .await?;
66
67    // 2. Configure which attributes are encrypted and/or signed when writing new items.
68    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
69    //    we must explicitly configure how they should be treated during item encryption:
70    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
71    //      - SIGN_ONLY: The attribute is not encrypted, but is still included in the signature
72    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
73    let attribute_actions_on_encrypt = HashMap::from([
74        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
75        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
76        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
77    ]);
78
79    // 3. Configure which attributes we expect to be included in the signature
80    //    when reading items. There are two options for configuring this:
81    //
82    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
83    //      When defining your DynamoDb schema and deciding on attribute names,
84    //      choose a distinguishing prefix (such as ":") for all attributes that
85    //      you do not want to include in the signature.
86    //      This has two main benefits:
87    //      - It is easier to reason about the security and authenticity of data within your item
88    //        when all unauthenticated data is easily distinguishable by their attribute name.
89    //      - If you need to add new unauthenticated attributes in the future,
90    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
91    //        and immediately start writing to that new attribute, without
92    //        any other configuration update needed.
93    //      Once you configure this field, it is not safe to update it.
94    //
95    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
96    //      a set of attributes that should be considered unauthenticated when encountered
97    //      on read. Be careful if you use this configuration. Do not remove an attribute
98    //      name from this configuration, even if you are no longer writing with that attribute,
99    //      as old items may still include this attribute, and our configuration needs to know
100    //      to continue to exclude this attribute from the signature scope.
101    //      If you add new attribute names to this field, you must first deploy the update to this
102    //      field to all readers in your host fleet before deploying the update to start writing
103    //      with that new attribute.
104    //
105    //   For this example, we currently authenticate all attributes. To make it easier to
106    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
107    const UNSIGNED_ATTR_PREFIX: &str = ":";
108
109    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
110    let table_config = DynamoDbTableEncryptionConfig::builder()
111        .logical_table_name(ddb_table_name)
112        .partition_key_name("partition_key")
113        .sort_key_name("sort_key")
114        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
115        .keyring(mrk_keyring_with_client_supplier)
116        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
117        .build()?;
118
119    let table_configs = DynamoDbTablesEncryptionConfig::builder()
120        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
121        .build()?;
122
123    // 5. Create a new AWS SDK DynamoDb client using the DynamoDb Config above
124    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
125    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
126        .interceptor(DbEsdkInterceptor::new(table_configs)?)
127        .build();
128    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
129
130    // 6. Put an item into our table using the above client.
131    //    Before the item gets sent to DynamoDb, it will be encrypted
132    //    client-side using the MRK multi-keyring.
133    //    The data key protecting this item will be encrypted
134    //    with all the KMS Keys in this keyring, so that it can be
135    //    decrypted with any one of those KMS Keys.
136    let item = HashMap::from([
137        (
138            "partition_key".to_string(),
139            AttributeValue::S("clientSupplierItem".to_string()),
140        ),
141        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
142        (
143            "sensitive_data".to_string(),
144            AttributeValue::S("encrypt and sign me!".to_string()),
145        ),
146    ]);
147
148    ddb.put_item()
149        .table_name(ddb_table_name)
150        .set_item(Some(item.clone()))
151        .send()
152        .await?;
153
154    // 7. Get the item back from our table using the same keyring.
155    //    The client will decrypt the item client-side using the MRK
156    //    and return the original item.
157    let key_to_get = HashMap::from([
158        (
159            "partition_key".to_string(),
160            AttributeValue::S("clientSupplierItem".to_string()),
161        ),
162        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
163    ]);
164
165    let resp = ddb
166        .get_item()
167        .table_name(ddb_table_name)
168        .set_key(Some(key_to_get.clone()))
169        .consistent_read(true)
170        .send()
171        .await?;
172
173    assert_eq!(
174        resp.item.unwrap()["sensitive_data"],
175        AttributeValue::S("encrypt and sign me!".to_string())
176    );
177
178    // 8. Create a MRK discovery multi-keyring with a custom client supplier.
179    //    A discovery MRK multi-keyring will be composed of
180    //    multiple discovery MRK keyrings, one for each region.
181    //    Each component keyring has its own KMS client in a particular region.
182    //    When we provide a client supplier to the multi-keyring, all component
183    //    keyrings will use that client supplier configuration.
184    //    In our tests, we make `key_arn` an MRK with a replica, and
185    //    provide only the replica region in our discovery filter.
186    let discovery_filter = DiscoveryFilter::builder()
187        .partition("aws")
188        .account_ids(account_ids)
189        .build()?;
190
191    let mrk_discovery_client_supplier_keyring = mpl
192        .create_aws_kms_mrk_discovery_multi_keyring()
193        .client_supplier(RegionalRoleClientSupplier {})
194        .discovery_filter(discovery_filter)
195        .regions(regions)
196        .send()
197        .await?;
198
199    // 9. Create a new config and client using the discovery keyring.
200    //     This is the same setup as above, except we provide the discovery keyring to the config.
201    let only_replica_table_config = DynamoDbTableEncryptionConfig::builder()
202        .logical_table_name(ddb_table_name)
203        .partition_key_name("partition_key")
204        .sort_key_name("sort_key")
205        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
206        .keyring(mrk_discovery_client_supplier_keyring)
207        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
208        .build()?;
209
210    let only_replica_table_configs = DynamoDbTablesEncryptionConfig::builder()
211        .table_encryption_configs(HashMap::from([(
212            ddb_table_name.to_string(),
213            only_replica_table_config,
214        )]))
215        .build()?;
216
217    let only_replica_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
218        .interceptor(DbEsdkInterceptor::new(only_replica_table_configs)?)
219        .build();
220    let only_replica_ddb = aws_sdk_dynamodb::Client::from_conf(only_replica_dynamo_config);
221
222    // 10. Get the item back from our table using the discovery keyring client.
223    //     The client will decrypt the item client-side using the keyring,
224    //     and return the original item.
225    //     The discovery keyring will only use KMS keys in the provided regions and
226    //     AWS accounts. Since we have provided it with a custom client supplier
227    //     which uses different IAM roles based on the key region,
228    //     the discovery keyring will use a particular IAM role to decrypt
229    //     based on the region of the KMS key it uses to decrypt.
230
231    let resp = only_replica_ddb
232        .get_item()
233        .table_name(ddb_table_name)
234        .set_key(Some(key_to_get))
235        .consistent_read(true)
236        .send()
237        .await?;
238
239    assert_eq!(
240        resp.item.unwrap()["sensitive_data"],
241        AttributeValue::S("encrypt and sign me!".to_string())
242    );
243
244    println!("client_supplier_example successful.");
245    Ok(())
246}
Source§

impl Client

Source§

impl Client

Source

pub fn create_aws_kms_mrk_discovery_multi_keyring( &self, ) -> CreateAwsKmsMrkDiscoveryMultiKeyringFluentBuilder

Examples found in repository?
examples/keyring/mrk_discovery_multi_keyring.rs (line 160)
37pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
38    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
39    let key_arn = test_utils::TEST_MRK_KEY_ID;
40    let account_ids = vec![test_utils::TEST_AWS_ACCOUNT_ID.to_string()];
41    let regions = vec![test_utils::TEST_AWS_REGION.to_string()];
42
43    // 1. Create a single MRK multi-keyring using the key arn.
44    //    Although this example demonstrates use of the MRK discovery multi-keyring,
45    //    a discovery keyring cannot be used to encrypt. So we will need to construct
46    //    a non-discovery keyring for this example to encrypt. For more information on MRK
47    //    multi-keyrings, see the MultiMrkKeyringExample in this directory.
48    //    Though this is an "MRK multi-keyring", we do not need to provide multiple keys,
49    //    and can use single-region KMS keys. We will provide a single key here; this
50    //    can be either an MRK or a single-region key.
51    let mpl_config = MaterialProvidersConfig::builder().build()?;
52    let mpl = mpl_client::Client::from_conf(mpl_config)?;
53    let encrypt_keyring = mpl
54        .create_aws_kms_mrk_multi_keyring()
55        .generator(key_arn)
56        .send()
57        .await?;
58
59    // 2. Configure which attributes are encrypted and/or signed when writing new items.
60    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
61    //    we must explicitly configure how they should be treated during item encryption:
62    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and icncluded in the signature
63    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
64    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
65    let attribute_actions_on_encrypt = HashMap::from([
66        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
67        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
68        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
69    ]);
70
71    // 3. Configure which attributes we expect to be included in the signature
72    //    when reading items. There are two options for configuring this:
73    //
74    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
75    //      When defining your DynamoDb schema and deciding on attribute names,
76    //      choose a distinguishing prefix (such as ":") for all attributes that
77    //      you do not want to include in the signature.
78    //      This has two main benefits:
79    //      - It is easier to reason about the security and authenticity of data within your item
80    //        when all unauthenticated data is easily distinguishable by their attribute name.
81    //      - If you need to add new unauthenticated attributes in the future,
82    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
83    //        and immediately start writing to that new attribute, without
84    //        any other configuration update needed.
85    //      Once you configure this field, it is not safe to update it.
86    //
87    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
88    //      a set of attributes that should be considered unauthenticated when encountered
89    //      on read. Be careful if you use this configuration. Do not remove an attribute
90    //      name from this configuration, even if you are no longer writing with that attribute,
91    //      as old items may still include this attribute, and our configuration needs to know
92    //      to continue to exclude this attribute from the signature scope.
93    //      If you add new attribute names to this field, you must first deploy the update to this
94    //      field to all readers in your host fleet before deploying the update to start writing
95    //      with that new attribute.
96    //
97    //   For this example, we currently authenticate all attributes. To make it easier to
98    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
99    const UNSIGNED_ATTR_PREFIX: &str = ":";
100
101    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
102    let table_config = DynamoDbTableEncryptionConfig::builder()
103        .logical_table_name(ddb_table_name)
104        .partition_key_name("partition_key")
105        .sort_key_name("sort_key")
106        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
107        .keyring(encrypt_keyring)
108        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
109        .build()?;
110
111    let table_configs = DynamoDbTablesEncryptionConfig::builder()
112        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
113        .build()?;
114
115    // 5. Create a new AWS SDK DynamoDb client using the config above
116    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
117    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
118        .interceptor(DbEsdkInterceptor::new(table_configs)?)
119        .build();
120    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
121
122    // 6. Put an item into our table using the above client.
123    //    Before the item gets sent to DynamoDb, it will be encrypted
124    //    client-side using the MRK multi-keyring.
125    let item = HashMap::from([
126        (
127            "partition_key".to_string(),
128            AttributeValue::S("awsKmsMrkDiscoveryMultiKeyringItem".to_string()),
129        ),
130        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
131        (
132            "sensitive_data".to_string(),
133            AttributeValue::S("encrypt and sign me!".to_string()),
134        ),
135    ]);
136
137    ddb.put_item()
138        .table_name(ddb_table_name)
139        .set_item(Some(item.clone()))
140        .send()
141        .await?;
142
143    // 7. Construct a discovery filter.
144    //    A discovery filter limits the set of encrypted data keys
145    //    the keyring can use to decrypt data.
146    //    We will only let the keyring use keys in the selected AWS accounts
147    //    and in the `aws` partition.
148    //    This is the suggested config for most users; for more detailed config, see
149    //      https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
150    let discovery_filter = DiscoveryFilter::builder()
151        .partition("aws")
152        .account_ids(account_ids)
153        .build()?;
154
155    // 8. Construct a discovery keyring.
156    //    Note that we choose to use the MRK discovery multi-keyring, even though
157    //    our original keyring used a single KMS key.
158
159    let decrypt_keyring = mpl
160        .create_aws_kms_mrk_discovery_multi_keyring()
161        .discovery_filter(discovery_filter)
162        .regions(regions)
163        .send()
164        .await?;
165
166    // 9. Create new DDB config and client using the decrypt discovery keyring.
167    //     This is the same as the above config, except we pass in the decrypt keyring.
168    let table_config_for_decrypt = DynamoDbTableEncryptionConfig::builder()
169        .logical_table_name(ddb_table_name)
170        .partition_key_name("partition_key")
171        .sort_key_name("sort_key")
172        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
173        .keyring(decrypt_keyring)
174        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
175        .build()?;
176
177    let table_configs_for_decrypt = DynamoDbTablesEncryptionConfig::builder()
178        .table_encryption_configs(HashMap::from([(
179            ddb_table_name.to_string(),
180            table_config_for_decrypt,
181        )]))
182        .build()?;
183
184    let dynamo_config_for_decrypt = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
185        .interceptor(DbEsdkInterceptor::new(table_configs_for_decrypt)?)
186        .build();
187    let ddb_for_decrypt = aws_sdk_dynamodb::Client::from_conf(dynamo_config_for_decrypt);
188
189    // 10. Get the item back from our table using the client.
190    //     The client will retrieve encrypted items from the DDB table, then
191    //     detect the KMS key that was used to encrypt their data keys.
192    //     The client will make a request to KMS to decrypt with the encrypting KMS key.
193    //     If the client has permission to decrypt with the KMS key,
194    //     the client will decrypt the item client-side using the keyring
195    //     and return the original item.
196    let key_to_get = HashMap::from([
197        (
198            "partition_key".to_string(),
199            AttributeValue::S("awsKmsMrkDiscoveryMultiKeyringItem".to_string()),
200        ),
201        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
202    ]);
203
204    let resp = ddb_for_decrypt
205        .get_item()
206        .table_name(ddb_table_name)
207        .set_key(Some(key_to_get))
208        .consistent_read(true)
209        .send()
210        .await?;
211
212    assert_eq!(resp.item, Some(item));
213
214    println!("mrk_discovery_multi_keyring successful.");
215    Ok(())
216}
More examples
Hide additional examples
examples/clientsupplier/client_supplier_example.rs (line 192)
37pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
38    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
39    // Note that we pass in an MRK in us-east-1...
40    let key_arn = test_utils::TEST_MRK_REPLICA_KEY_ID_US_EAST_1.to_string();
41    let account_ids = vec![test_utils::TEST_AWS_ACCOUNT_ID.to_string()];
42    // ...and access its replica in eu-west-1
43    let regions = vec!["eu-west-1".to_string()];
44
45    // 1. Create a single MRK multi-keyring.
46    //    This can be either a single-region KMS key or an MRK.
47    //    For this example to succeed, the key's region must either
48    //    1) be in the regions list, or
49    //    2) the key must be an MRK with a replica defined
50    //    in a region in the regions list, and the client
51    //    must have the correct permissions to access the replica.
52    let mpl_config = MaterialProvidersConfig::builder().build()?;
53    let mpl = mpl_client::Client::from_conf(mpl_config)?;
54
55    // Create the multi-keyring using our custom client supplier
56    // defined in the RegionalRoleClientSupplier class in this directory.
57    // Note: RegionalRoleClientSupplier will internally use the key_arn's region
58    // to retrieve the correct IAM role.
59
60    let mrk_keyring_with_client_supplier = mpl
61        .create_aws_kms_mrk_multi_keyring()
62        .client_supplier(RegionalRoleClientSupplier {})
63        .generator(key_arn)
64        .send()
65        .await?;
66
67    // 2. Configure which attributes are encrypted and/or signed when writing new items.
68    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
69    //    we must explicitly configure how they should be treated during item encryption:
70    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
71    //      - SIGN_ONLY: The attribute is not encrypted, but is still included in the signature
72    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
73    let attribute_actions_on_encrypt = HashMap::from([
74        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
75        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
76        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
77    ]);
78
79    // 3. Configure which attributes we expect to be included in the signature
80    //    when reading items. There are two options for configuring this:
81    //
82    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
83    //      When defining your DynamoDb schema and deciding on attribute names,
84    //      choose a distinguishing prefix (such as ":") for all attributes that
85    //      you do not want to include in the signature.
86    //      This has two main benefits:
87    //      - It is easier to reason about the security and authenticity of data within your item
88    //        when all unauthenticated data is easily distinguishable by their attribute name.
89    //      - If you need to add new unauthenticated attributes in the future,
90    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
91    //        and immediately start writing to that new attribute, without
92    //        any other configuration update needed.
93    //      Once you configure this field, it is not safe to update it.
94    //
95    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
96    //      a set of attributes that should be considered unauthenticated when encountered
97    //      on read. Be careful if you use this configuration. Do not remove an attribute
98    //      name from this configuration, even if you are no longer writing with that attribute,
99    //      as old items may still include this attribute, and our configuration needs to know
100    //      to continue to exclude this attribute from the signature scope.
101    //      If you add new attribute names to this field, you must first deploy the update to this
102    //      field to all readers in your host fleet before deploying the update to start writing
103    //      with that new attribute.
104    //
105    //   For this example, we currently authenticate all attributes. To make it easier to
106    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
107    const UNSIGNED_ATTR_PREFIX: &str = ":";
108
109    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
110    let table_config = DynamoDbTableEncryptionConfig::builder()
111        .logical_table_name(ddb_table_name)
112        .partition_key_name("partition_key")
113        .sort_key_name("sort_key")
114        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
115        .keyring(mrk_keyring_with_client_supplier)
116        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
117        .build()?;
118
119    let table_configs = DynamoDbTablesEncryptionConfig::builder()
120        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
121        .build()?;
122
123    // 5. Create a new AWS SDK DynamoDb client using the DynamoDb Config above
124    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
125    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
126        .interceptor(DbEsdkInterceptor::new(table_configs)?)
127        .build();
128    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
129
130    // 6. Put an item into our table using the above client.
131    //    Before the item gets sent to DynamoDb, it will be encrypted
132    //    client-side using the MRK multi-keyring.
133    //    The data key protecting this item will be encrypted
134    //    with all the KMS Keys in this keyring, so that it can be
135    //    decrypted with any one of those KMS Keys.
136    let item = HashMap::from([
137        (
138            "partition_key".to_string(),
139            AttributeValue::S("clientSupplierItem".to_string()),
140        ),
141        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
142        (
143            "sensitive_data".to_string(),
144            AttributeValue::S("encrypt and sign me!".to_string()),
145        ),
146    ]);
147
148    ddb.put_item()
149        .table_name(ddb_table_name)
150        .set_item(Some(item.clone()))
151        .send()
152        .await?;
153
154    // 7. Get the item back from our table using the same keyring.
155    //    The client will decrypt the item client-side using the MRK
156    //    and return the original item.
157    let key_to_get = HashMap::from([
158        (
159            "partition_key".to_string(),
160            AttributeValue::S("clientSupplierItem".to_string()),
161        ),
162        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
163    ]);
164
165    let resp = ddb
166        .get_item()
167        .table_name(ddb_table_name)
168        .set_key(Some(key_to_get.clone()))
169        .consistent_read(true)
170        .send()
171        .await?;
172
173    assert_eq!(
174        resp.item.unwrap()["sensitive_data"],
175        AttributeValue::S("encrypt and sign me!".to_string())
176    );
177
178    // 8. Create a MRK discovery multi-keyring with a custom client supplier.
179    //    A discovery MRK multi-keyring will be composed of
180    //    multiple discovery MRK keyrings, one for each region.
181    //    Each component keyring has its own KMS client in a particular region.
182    //    When we provide a client supplier to the multi-keyring, all component
183    //    keyrings will use that client supplier configuration.
184    //    In our tests, we make `key_arn` an MRK with a replica, and
185    //    provide only the replica region in our discovery filter.
186    let discovery_filter = DiscoveryFilter::builder()
187        .partition("aws")
188        .account_ids(account_ids)
189        .build()?;
190
191    let mrk_discovery_client_supplier_keyring = mpl
192        .create_aws_kms_mrk_discovery_multi_keyring()
193        .client_supplier(RegionalRoleClientSupplier {})
194        .discovery_filter(discovery_filter)
195        .regions(regions)
196        .send()
197        .await?;
198
199    // 9. Create a new config and client using the discovery keyring.
200    //     This is the same setup as above, except we provide the discovery keyring to the config.
201    let only_replica_table_config = DynamoDbTableEncryptionConfig::builder()
202        .logical_table_name(ddb_table_name)
203        .partition_key_name("partition_key")
204        .sort_key_name("sort_key")
205        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
206        .keyring(mrk_discovery_client_supplier_keyring)
207        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
208        .build()?;
209
210    let only_replica_table_configs = DynamoDbTablesEncryptionConfig::builder()
211        .table_encryption_configs(HashMap::from([(
212            ddb_table_name.to_string(),
213            only_replica_table_config,
214        )]))
215        .build()?;
216
217    let only_replica_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
218        .interceptor(DbEsdkInterceptor::new(only_replica_table_configs)?)
219        .build();
220    let only_replica_ddb = aws_sdk_dynamodb::Client::from_conf(only_replica_dynamo_config);
221
222    // 10. Get the item back from our table using the discovery keyring client.
223    //     The client will decrypt the item client-side using the keyring,
224    //     and return the original item.
225    //     The discovery keyring will only use KMS keys in the provided regions and
226    //     AWS accounts. Since we have provided it with a custom client supplier
227    //     which uses different IAM roles based on the key region,
228    //     the discovery keyring will use a particular IAM role to decrypt
229    //     based on the region of the KMS key it uses to decrypt.
230
231    let resp = only_replica_ddb
232        .get_item()
233        .table_name(ddb_table_name)
234        .set_key(Some(key_to_get))
235        .consistent_read(true)
236        .send()
237        .await?;
238
239    assert_eq!(
240        resp.item.unwrap()["sensitive_data"],
241        AttributeValue::S("encrypt and sign me!".to_string())
242    );
243
244    println!("client_supplier_example successful.");
245    Ok(())
246}
Source§

impl Client

Source

pub fn create_aws_kms_hierarchical_keyring( &self, ) -> CreateAwsKmsHierarchicalKeyringFluentBuilder

Examples found in repository?
examples/keyring/hierarchical_keyring.rs (line 115)
63pub async fn put_item_get_item(
64    tenant1_branch_key_id: &str,
65    tenant2_branch_key_id: &str,
66) -> Result<(), crate::BoxError> {
67    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
68
69    let keystore_table_name = test_utils::TEST_KEYSTORE_NAME;
70    let logical_keystore_name = test_utils::TEST_LOGICAL_KEYSTORE_NAME;
71    let kms_key_id = test_utils::TEST_KEYSTORE_KMS_KEY_ID;
72
73    // Initial KeyStore Setup: This example requires that you have already
74    // created your KeyStore, and have populated it with two new branch keys.
75    // See the "Create KeyStore Table Example" and "Create KeyStore Key Example"
76    // for an example of how to do this.
77
78    // 1. Configure your KeyStore resource.
79    //    This SHOULD be the same configuration that you used
80    //    to initially create and populate your KeyStore.
81    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
82    let key_store_config = KeyStoreConfig::builder()
83        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
84        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
85        .ddb_table_name(keystore_table_name)
86        .logical_key_store_name(logical_keystore_name)
87        .kms_configuration(KmsConfiguration::KmsKeyArn(kms_key_id.to_string()))
88        .build()?;
89
90    let key_store = keystore_client::Client::from_conf(key_store_config)?;
91
92    // 2. Create a Branch Key ID Supplier. See ExampleBranchKeyIdSupplier in this directory.
93    let dbesdk_config = DynamoDbEncryptionConfig::builder().build()?;
94    let dbesdk = dbesdk_client::Client::from_conf(dbesdk_config)?;
95    let supplier = ExampleBranchKeyIdSupplier::new(tenant1_branch_key_id, tenant2_branch_key_id);
96
97    let branch_key_id_supplier = dbesdk
98        .create_dynamo_db_encryption_branch_key_id_supplier()
99        .ddb_key_branch_key_id_supplier(supplier)
100        .send()
101        .await?
102        .branch_key_id_supplier
103        .unwrap();
104
105    // 3. Create the Hierarchical Keyring, using the Branch Key ID Supplier above.
106    //    With this configuration, the AWS SDK Client ultimately configured will be capable
107    //    of encrypting or decrypting items for either tenant (assuming correct KMS access).
108    //    If you want to restrict the client to only encrypt or decrypt for a single tenant,
109    //    configure this Hierarchical Keyring using `.branchKeyId(tenant1BranchKeyId)` instead
110    //    of `.branchKeyIdSupplier(branchKeyIdSupplier)`.
111    let mpl_config = MaterialProvidersConfig::builder().build()?;
112    let mpl = mpl_client::Client::from_conf(mpl_config)?;
113
114    let hierarchical_keyring = mpl
115        .create_aws_kms_hierarchical_keyring()
116        .branch_key_id_supplier(branch_key_id_supplier)
117        .key_store(key_store)
118        .ttl_seconds(600)
119        .send()
120        .await?;
121
122    // 4. Configure which attributes are encrypted and/or signed when writing new items.
123    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
124    //    we must explicitly configure how they should be treated during item encryption:
125    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
126    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
127    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
128    let attribute_actions_on_encrypt = HashMap::from([
129        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
130        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
131        (
132            "tenant_sensitive_data".to_string(),
133            CryptoAction::EncryptAndSign,
134        ),
135    ]);
136
137    // 5. Configure which attributes we expect to be included in the signature
138    //    when reading items. There are two options for configuring this:
139    //
140    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
141    //      When defining your DynamoDb schema and deciding on attribute names,
142    //      choose a distinguishing prefix (such as ":") for all attributes that
143    //      you do not want to include in the signature.
144    //      This has two main benefits:
145    //      - It is easier to reason about the security and authenticity of data within your item
146    //        when all unauthenticated data is easily distinguishable by their attribute name.
147    //      - If you need to add new unauthenticated attributes in the future,
148    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
149    //        and immediately start writing to that new attribute, without
150    //        any other configuration update needed.
151    //      Once you configure this field, it is not safe to update it.
152    //
153    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
154    //      a set of attributes that should be considered unauthenticated when encountered
155    //      on read. Be careful if you use this configuration. Do not remove an attribute
156    //      name from this configuration, even if you are no longer writing with that attribute,
157    //      as old items may still include this attribute, and our configuration needs to know
158    //      to continue to exclude this attribute from the signature scope.
159    //      If you add new attribute names to this field, you must first deploy the update to this
160    //      field to all readers in your host fleet before deploying the update to start writing
161    //      with that new attribute.
162    //
163    //   For this example, we currently authenticate all attributes. To make it easier to
164    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
165    const UNSIGNED_ATTR_PREFIX: &str = ":";
166
167    // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
168    let table_config = DynamoDbTableEncryptionConfig::builder()
169        .logical_table_name(ddb_table_name)
170        .partition_key_name("partition_key")
171        .sort_key_name("sort_key")
172        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
173        .keyring(hierarchical_keyring)
174        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
175        .build()?;
176
177    let table_configs = DynamoDbTablesEncryptionConfig::builder()
178        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
179        .build()?;
180
181    // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
182    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
183    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
184        .interceptor(DbEsdkInterceptor::new(table_configs)?)
185        .build();
186    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
187
188    // 8. Put an item into our table using the above client.
189    //    Before the item gets sent to DynamoDb, it will be encrypted
190    //    client-side, according to our configuration.
191    //    Because the item we are writing uses "tenantId1" as our partition value,
192    //    based on the code we wrote in the ExampleBranchKeySupplier,
193    //    `tenant1BranchKeyId` will be used to encrypt this item.
194    let item = HashMap::from([
195        (
196            "partition_key".to_string(),
197            AttributeValue::S("tenant1Id".to_string()),
198        ),
199        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
200        (
201            "tenant_sensitive_data".to_string(),
202            AttributeValue::S("encrypt and sign me!".to_string()),
203        ),
204    ]);
205
206    ddb.put_item()
207        .table_name(ddb_table_name)
208        .set_item(Some(item.clone()))
209        .send()
210        .await?;
211
212    // 9. Get the item back from our table using the same client.
213    //     The client will decrypt the item client-side, and return
214    //     back the original item.
215    //     Because the returned item's partition value is "tenantId1",
216    //     based on the code we wrote in the ExampleBranchKeySupplier,
217    //     `tenant1BranchKeyId` will be used to decrypt this item.
218    let key_to_get = HashMap::from([
219        (
220            "partition_key".to_string(),
221            AttributeValue::S("tenant1Id".to_string()),
222        ),
223        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
224    ]);
225
226    let resp = ddb
227        .get_item()
228        .table_name(ddb_table_name)
229        .set_key(Some(key_to_get))
230        .consistent_read(true)
231        .send()
232        .await?;
233
234    assert_eq!(resp.item, Some(item));
235    println!("hierarchical_keyring successful.");
236    Ok(())
237}
More examples
Hide additional examples
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 175)
60pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> {
61    let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME;
62    let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN;
63    let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME;
64
65    // 1. Create Beacons.
66    //    These are the same beacons as in the "BasicSearchableEncryptionExample" in this directory.
67    //    See that file to see details on beacon construction and parameters.
68    //    While we will not directly query against these beacons,
69    //      you must create standard beacons on encrypted fields
70    //      that we wish to use in compound beacons.
71    let last4_beacon = StandardBeacon::builder()
72        .name("inspector_id_last4")
73        .length(10)
74        .build()?;
75
76    let unit_beacon = StandardBeacon::builder().name("unit").length(30).build()?;
77
78    let standard_beacon_list = vec![last4_beacon, unit_beacon];
79
80    // 2. Define encrypted parts.
81    //    Encrypted parts define the beacons that can be used to construct a compound beacon,
82    //        and how the compound beacon prefixes those beacon values.
83
84    // A encrypted part must receive:
85    //  - name: Name of a standard beacon
86    //  - prefix: Any string. This is plaintext that prefixes the beaconized value in the compound beacon.
87    //            Prefixes must be unique across the configuration, and must not be a prefix of another prefix;
88    //            i.e. for all configured prefixes, the first N characters of a prefix must not equal another prefix.
89    // In practice, it is suggested to have a short value distinguishable from other parts served on the prefix.
90    // For this example, we will choose "L-" as the prefix for "Last 4 digits of inspector ID".
91    // With this prefix and the standard beacon's bit length definition (10), the beaconized
92    //     version of the inspector ID's last 4 digits will appear as
93    //     `L-000` to `L-3ff` inside a compound beacon.
94
95    // For this example, we will choose "U-" as the prefix for "unit".
96    // With this prefix and the standard beacon's bit length definition (30), a unit beacon will appear
97    //     as `U-00000000` to `U-3fffffff` inside a compound beacon.
98    let encrypted_parts_list = vec![
99        EncryptedPart::builder()
100            .name("inspector_id_last4")
101            .prefix("L-")
102            .build()?,
103        EncryptedPart::builder().name("unit").prefix("U-").build()?,
104    ];
105
106    // 3. Define compound beacon.
107    //    A compound beacon allows one to serve multiple beacons or attributes from a single index.
108    //    A compound beacon must receive:
109    //     - name: The name of the beacon. Compound beacon values will be written to `aws_ddb_e_[name]`.
110    //     - split: A character separating parts in a compound beacon
111    //    A compound beacon may also receive:
112    //     - encrypted: A list of encrypted parts. This is effectively a list of beacons. We provide the list
113    //                  that we created above.
114    //     - constructors: A list of constructors. This is an ordered list of possible ways to create a beacon.
115    //                     We have not defined any constructors here; see the complex example for how to do this.
116    //                     The client will provide a default constructor, which will write a compound beacon as:
117    //                     all signed parts in the order they are added to the signed list;
118    //                     all encrypted parts in order they are added to the encrypted list; all parts required.
119    //                     In this example, we expect compound beacons to be written as
120    //                     `L-XXX.U-YYYYYYYY`, since our encrypted list looks like
121    //                     [last4EncryptedPart, unitEncryptedPart].
122    //     - signed: A list of signed parts, i.e. plaintext attributes. This would be provided if we
123    //                     wanted to use plaintext values as part of constructing our compound beacon. We do not
124    //                     provide this here; see the Complex example for an example.
125    let compound_beacon_list = vec![CompoundBeacon::builder()
126        .name("last4UnitCompound")
127        .split(".")
128        .encrypted(encrypted_parts_list)
129        .build()?];
130
131    // 4. Configure the Keystore
132    //    These are the same constructions as in the Basic example, which describes these in more detail.
133
134    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
135    let key_store_config = KeyStoreConfig::builder()
136        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
137        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
138        .ddb_table_name(branch_key_ddb_table_name)
139        .logical_key_store_name(branch_key_ddb_table_name)
140        .kms_configuration(KmsConfiguration::KmsKeyArn(
141            branch_key_wrapping_kms_key_arn.to_string(),
142        ))
143        .build()?;
144
145    let key_store = keystore_client::Client::from_conf(key_store_config)?;
146
147    // 5. Create BeaconVersion.
148    //    This is similar to the Basic example, except we have also provided a compoundBeaconList.
149    //    We must also continue to provide all of the standard beacons that compose a compound beacon list.
150    let beacon_version = BeaconVersion::builder()
151        .standard_beacons(standard_beacon_list)
152        .compound_beacons(compound_beacon_list)
153        .version(1) // MUST be 1
154        .key_store(key_store.clone())
155        .key_source(BeaconKeySource::Single(
156            SingleKeyStore::builder()
157                // `keyId` references a beacon key.
158                // For every branch key we create in the keystore,
159                // we also create a beacon key.
160                // This beacon key is not the same as the branch key,
161                // but is created with the same ID as the branch key.
162                .key_id(branch_key_id)
163                .cache_ttl(6000)
164                .build()?,
165        ))
166        .build()?;
167    let beacon_versions = vec![beacon_version];
168
169    // 6. Create a Hierarchical Keyring
170    //    This is the same configuration as in the Basic example.
171
172    let mpl_config = MaterialProvidersConfig::builder().build()?;
173    let mpl = mpl_client::Client::from_conf(mpl_config)?;
174    let kms_keyring = mpl
175        .create_aws_kms_hierarchical_keyring()
176        .branch_key_id(branch_key_id)
177        .key_store(key_store)
178        .ttl_seconds(6000)
179        .send()
180        .await?;
181
182    // 7. Configure which attributes are encrypted and/or signed when writing new items.
183    let attribute_actions_on_encrypt = HashMap::from([
184        ("work_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
185        ("inspection_date".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
186        (
187            "inspector_id_last4".to_string(),
188            CryptoAction::EncryptAndSign,
189        ), // Beaconized attributes must be encrypted
190        ("unit".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
191    ]);
192
193    // We do not need to define a crypto action on last4UnitCompound.
194    // We only need to define crypto actions on attributes that we pass to PutItem.
195
196    // 8. Create the DynamoDb Encryption configuration for the table we will be writing to.
197    //    The beaconVersions are added to the search configuration.
198    let table_config = DynamoDbTableEncryptionConfig::builder()
199        .logical_table_name(ddb_table_name)
200        .partition_key_name("work_id")
201        .sort_key_name("inspection_date")
202        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
203        .keyring(kms_keyring)
204        .search(
205            SearchConfig::builder()
206                .write_version(1) // MUST be 1
207                .versions(beacon_versions)
208                .build()?,
209        )
210        .build()?;
211
212    // 9. Create config
213    let encryption_config = DynamoDbTablesEncryptionConfig::builder()
214        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
215        .build()?;
216
217    // 10. Create an item with both attributes used in the compound beacon.
218    let item = HashMap::from([
219        (
220            "work_id".to_string(),
221            AttributeValue::S("9ce39272-8068-4efd-a211-cd162ad65d4c".to_string()),
222        ),
223        (
224            "inspection_date".to_string(),
225            AttributeValue::S("2023-06-13".to_string()),
226        ),
227        (
228            "inspector_id_last4".to_string(),
229            AttributeValue::S("5678".to_string()),
230        ),
231        (
232            "unit".to_string(),
233            AttributeValue::S("011899988199".to_string()),
234        ),
235    ]);
236
237    // 11. If developing or debugging, verify config by checking compound beacon values directly
238    let trans = transform_client::Client::from_conf(encryption_config.clone())?;
239    let resolve_output = trans
240        .resolve_attributes()
241        .table_name(ddb_table_name)
242        .item(item.clone())
243        .version(1)
244        .send()
245        .await?;
246
247    // Verify that there are no virtual fields
248    assert_eq!(resolve_output.virtual_fields.unwrap().len(), 0);
249
250    // Verify that CompoundBeacons has the expected value
251    let compound_beacons = resolve_output.compound_beacons.unwrap();
252    assert_eq!(compound_beacons.len(), 1);
253    assert_eq!(
254        compound_beacons["last4UnitCompound"],
255        "L-5678.U-011899988199"
256    );
257    // Note : the compound beacon actually stored in the table is not "L-5678.U-011899988199"
258    // but rather something like "L-abc.U-123", as both parts are EncryptedParts
259    // and therefore the text is replaced by the associated beacon
260
261    // 12. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
262    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
263        .interceptor(DbEsdkInterceptor::new(encryption_config)?)
264        .build();
265    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
266
267    // 13. Write the item to the table
268    ddb.put_item()
269        .table_name(ddb_table_name)
270        .set_item(Some(item.clone()))
271        .send()
272        .await?;
273
274    // 14. Query for the item we just put.
275    let expression_attribute_values = HashMap::from([
276        // This query expression takes a few factors into consideration:
277        //  - The configured prefix for the last 4 digits of an inspector ID is "L-";
278        //    the prefix for the unit is "U-"
279        //  - The configured split character, separating component parts, is "."
280        //  - The default constructor adds encrypted parts in the order they are in the encrypted list, which
281        //    configures `last4` to come before `unit``
282        // NOTE: We did not need to create a compound beacon for this query. This query could have also been
283        //       done by querying on the partition and sort key, as was done in the Basic example.
284        //       This is intended to be a simple example to demonstrate how one might set up a compound beacon.
285        //       For examples where compound beacons are required, see the Complex example.
286        //       The most basic extension to this example that would require a compound beacon would add a third
287        //       part to the compound beacon, then query against three parts.
288        (
289            ":value".to_string(),
290            AttributeValue::S("L-5678.U-011899988199".to_string()),
291        ),
292    ]);
293
294    // GSIs are sometimes a little bit delayed, so we retry if the query comes up empty.
295    for _i in 0..10 {
296        let query_response = ddb
297            .query()
298            .table_name(ddb_table_name)
299            .index_name(GSI_NAME)
300            .key_condition_expression("last4UnitCompound = :value")
301            .set_expression_attribute_values(Some(expression_attribute_values.clone()))
302            .send()
303            .await?;
304
305        // if no results, sleep and try again
306        if query_response.items.is_none() || query_response.items.as_ref().unwrap().is_empty() {
307            std::thread::sleep(std::time::Duration::from_millis(20));
308            continue;
309        }
310
311        let attribute_values = query_response.items.unwrap();
312        // Validate only 1 item was returned: the item we just put
313        assert_eq!(attribute_values.len(), 1);
314        let returned_item = &attribute_values[0];
315        // Validate the item has the expected attributes
316        assert_eq!(
317            returned_item["inspector_id_last4"],
318            AttributeValue::S("5678".to_string())
319        );
320        assert_eq!(
321            returned_item["unit"],
322            AttributeValue::S("011899988199".to_string())
323        );
324        break;
325    }
326    println!("compound_beacon_searchable_encryption successful.");
327    Ok(())
328}
examples/searchableencryption/beacon_styles_searchable_encryption.rs (line 165)
56pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> {
57    let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME;
58    let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN;
59    let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME;
60
61    // 1. Create Beacons.
62    let standard_beacon_list = vec![
63        // The fruit beacon allows searching on the encrypted fruit attribute
64        // We have selected 30 as an example beacon length, but you should go to
65        // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
66        // when creating your beacons.
67        StandardBeacon::builder().name("fruit").length(30).build()?,
68        // The basket beacon allows searching on the encrypted basket attribute
69        // Basket is used as a Set, and therefore needs a beacon style to reflect that.
70        // Further, we need to be able to compare the items in basket to the fruit attribute
71        // so we `share` this beacon with `fruit`.
72        // Since we need both of these things, we use the SharedSet style.
73        StandardBeacon::builder()
74            .name("basket")
75            .length(30)
76            .style(BeaconStyle::SharedSet(
77                SharedSet::builder().other("fruit").build()?,
78            ))
79            .build()?,
80        // The dessert beacon allows searching on the encrypted dessert attribute
81        // We need to be able to compare the dessert attribute to the fruit attribute
82        // so we `share` this beacon with `fruit`.
83        StandardBeacon::builder()
84            .name("dessert")
85            .length(30)
86            .style(BeaconStyle::Shared(
87                Shared::builder().other("fruit").build()?,
88            ))
89            .build()?,
90        // The veggieBeacon allows searching on the encrypted veggies attribute
91        // veggies is used as a Set, and therefore needs a beacon style to reflect that.
92        StandardBeacon::builder()
93            .name("veggies")
94            .length(30)
95            .style(BeaconStyle::AsSet(AsSet::builder().build()?))
96            .build()?,
97        // The work_typeBeacon allows searching on the encrypted work_type attribute
98        // We only use it as part of the compound work_unit beacon,
99        // so we disable its use as a standalone beacon
100        StandardBeacon::builder()
101            .name("work_type")
102            .length(30)
103            .style(BeaconStyle::PartOnly(PartOnly::builder().build()?))
104            .build()?,
105    ];
106
107    // Here we build a compound beacon from work_id and work_type
108    // If we had tried to make a StandardBeacon from work_type, we would have seen an error
109    // because work_type is "PartOnly"
110    let encrypted_part_list = vec![EncryptedPart::builder()
111        .name("work_type")
112        .prefix("T-")
113        .build()?];
114
115    let signed_part_list = vec![SignedPart::builder().name("work_id").prefix("I-").build()?];
116
117    let compound_beacon_list = vec![CompoundBeacon::builder()
118        .name("work_unit")
119        .split(".")
120        .encrypted(encrypted_part_list)
121        .signed(signed_part_list)
122        .build()?];
123
124    // 2. Configure the Keystore
125    //    These are the same constructions as in the Basic example, which describes these in more detail.
126    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
127    let key_store_config = KeyStoreConfig::builder()
128        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
129        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
130        .ddb_table_name(branch_key_ddb_table_name)
131        .logical_key_store_name(branch_key_ddb_table_name)
132        .kms_configuration(KmsConfiguration::KmsKeyArn(
133            branch_key_wrapping_kms_key_arn.to_string(),
134        ))
135        .build()?;
136
137    let key_store = keystore_client::Client::from_conf(key_store_config)?;
138
139    // 3. Create BeaconVersion.
140    //    This is similar to the Basic example
141    let beacon_version = BeaconVersion::builder()
142        .standard_beacons(standard_beacon_list)
143        .compound_beacons(compound_beacon_list)
144        .version(1) // MUST be 1
145        .key_store(key_store.clone())
146        .key_source(BeaconKeySource::Single(
147            SingleKeyStore::builder()
148                // `keyId` references a beacon key.
149                // For every branch key we create in the keystore,
150                // we also create a beacon key.
151                // This beacon key is not the same as the branch key,
152                // but is created with the same ID as the branch key.
153                .key_id(branch_key_id)
154                .cache_ttl(6000)
155                .build()?,
156        ))
157        .build()?;
158    let beacon_versions = vec![beacon_version];
159
160    // 4. Create a Hierarchical Keyring
161    //    This is the same configuration as in the Basic example.
162    let mpl_config = MaterialProvidersConfig::builder().build()?;
163    let mpl = mpl_client::Client::from_conf(mpl_config)?;
164    let kms_keyring = mpl
165        .create_aws_kms_hierarchical_keyring()
166        .branch_key_id(branch_key_id)
167        .key_store(key_store)
168        .ttl_seconds(6000)
169        .send()
170        .await?;
171
172    // 5. Configure which attributes are encrypted and/or signed when writing new items.
173    let attribute_actions_on_encrypt = HashMap::from([
174        ("work_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
175        ("inspection_date".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
176        ("dessert".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
177        ("fruit".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
178        ("basket".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
179        ("veggies".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
180        ("work_type".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
181    ]);
182
183    // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
184    //    The beaconVersions are added to the search configuration.
185    let table_config = DynamoDbTableEncryptionConfig::builder()
186        .logical_table_name(ddb_table_name)
187        .partition_key_name("work_id")
188        .sort_key_name("inspection_date")
189        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
190        .keyring(kms_keyring)
191        .search(
192            SearchConfig::builder()
193                .write_version(1) // MUST be 1
194                .versions(beacon_versions)
195                .build()?,
196        )
197        .build()?;
198
199    // 7. Create config
200    let encryption_config = DynamoDbTablesEncryptionConfig::builder()
201        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
202        .build()?;
203
204    // 8. Create item one, specifically with "dessert != fruit", and "fruit in basket".
205    let item1 = HashMap::from([
206        ("work_id".to_string(), AttributeValue::S("1".to_string())),
207        (
208            "inspection_date".to_string(),
209            AttributeValue::S("2023-06-13".to_string()),
210        ),
211        ("dessert".to_string(), AttributeValue::S("cake".to_string())),
212        ("fruit".to_string(), AttributeValue::S("banana".to_string())),
213        (
214            "basket".to_string(),
215            AttributeValue::Ss(vec![
216                "banana".to_string(),
217                "apple".to_string(),
218                "pear".to_string(),
219            ]),
220        ),
221        (
222            "veggies".to_string(),
223            AttributeValue::Ss(vec![
224                "beans".to_string(),
225                "carrots".to_string(),
226                "celery".to_string(),
227            ]),
228        ),
229        (
230            "work_type".to_string(),
231            AttributeValue::S("small".to_string()),
232        ),
233    ]);
234
235    // 9. Create item two, specifically with "dessert == fruit", and "fruit not in basket".
236    let item2 = HashMap::from([
237        ("work_id".to_string(), AttributeValue::S("2".to_string())),
238        (
239            "inspection_date".to_string(),
240            AttributeValue::S("2023-06-13".to_string()),
241        ),
242        (
243            "dessert".to_string(),
244            AttributeValue::S("orange".to_string()),
245        ),
246        ("fruit".to_string(), AttributeValue::S("orange".to_string())),
247        (
248            "basket".to_string(),
249            AttributeValue::Ss(vec![
250                "strawberry".to_string(),
251                "blueberry".to_string(),
252                "blackberry".to_string(),
253            ]),
254        ),
255        (
256            "veggies".to_string(),
257            AttributeValue::Ss(vec![
258                "beans".to_string(),
259                "carrots".to_string(),
260                "peas".to_string(),
261            ]),
262        ),
263        (
264            "work_type".to_string(),
265            AttributeValue::S("large".to_string()),
266        ),
267    ]);
268
269    // 10. Create a new AWS SDK DynamoDb client using the DynamoDb Config above
270    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
271        .interceptor(DbEsdkInterceptor::new(encryption_config)?)
272        .build();
273    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
274
275    // 11. Add the two items
276    ddb.put_item()
277        .table_name(ddb_table_name)
278        .set_item(Some(item1.clone()))
279        .send()
280        .await?;
281
282    ddb.put_item()
283        .table_name(ddb_table_name)
284        .set_item(Some(item2.clone()))
285        .send()
286        .await?;
287
288    // 12. Test the first type of Set operation :
289    // Select records where the basket attribute holds a particular value
290    let expression_attribute_values = HashMap::from([(
291        ":value".to_string(),
292        AttributeValue::S("banana".to_string()),
293    )]);
294
295    let scan_response = ddb
296        .scan()
297        .table_name(ddb_table_name)
298        .filter_expression("contains(basket, :value)")
299        .set_expression_attribute_values(Some(expression_attribute_values.clone()))
300        .send()
301        .await?;
302
303    let attribute_values = scan_response.items.unwrap();
304    // Validate only 1 item was returned: item1
305    assert_eq!(attribute_values.len(), 1);
306    let returned_item = &attribute_values[0];
307    // Validate the item has the expected attributes
308    assert_eq!(returned_item["work_id"], item1["work_id"]);
309
310    // 13. Test the second type of Set operation :
311    // Select records where the basket attribute holds the fruit attribute
312    let scan_response = ddb
313        .scan()
314        .table_name(ddb_table_name)
315        .filter_expression("contains(basket, fruit)")
316        .send()
317        .await?;
318
319    let attribute_values = scan_response.items.unwrap();
320    // Validate only 1 item was returned: item1
321    assert_eq!(attribute_values.len(), 1);
322    let returned_item = &attribute_values[0];
323    // Validate the item has the expected attributes
324    assert_eq!(returned_item["work_id"], item1["work_id"]);
325
326    // 14. Test the third type of Set operation :
327    // Select records where the fruit attribute exists in a particular set
328    let basket3 = vec![
329        "boysenberry".to_string(),
330        "orange".to_string(),
331        "grape".to_string(),
332    ];
333    let expression_attribute_values =
334        HashMap::from([(":value".to_string(), AttributeValue::Ss(basket3))]);
335
336    let scan_response = ddb
337        .scan()
338        .table_name(ddb_table_name)
339        .filter_expression("contains(:value, fruit)")
340        .set_expression_attribute_values(Some(expression_attribute_values.clone()))
341        .send()
342        .await?;
343
344    let attribute_values = scan_response.items.unwrap();
345    // Validate only 1 item was returned: item1
346    assert_eq!(attribute_values.len(), 1);
347    let returned_item = &attribute_values[0];
348    // Validate the item has the expected attributes
349    assert_eq!(returned_item["work_id"], item2["work_id"]);
350
351    // 15. Test a Shared search. Select records where the dessert attribute matches the fruit attribute
352    let scan_response = ddb
353        .scan()
354        .table_name(ddb_table_name)
355        .filter_expression("dessert = fruit")
356        .send()
357        .await?;
358
359    let attribute_values = scan_response.items.unwrap();
360    // Validate only 1 item was returned: item1
361    assert_eq!(attribute_values.len(), 1);
362    let returned_item = &attribute_values[0];
363    // Validate the item has the expected attributes
364    assert_eq!(returned_item["work_id"], item2["work_id"]);
365
366    // 15. Test the AsSet attribute 'veggies' :
367    // Select records where the veggies attribute holds a particular value
368    let expression_attribute_values =
369        HashMap::from([(":value".to_string(), AttributeValue::S("peas".to_string()))]);
370
371    let scan_response = ddb
372        .scan()
373        .table_name(ddb_table_name)
374        .filter_expression("contains(veggies, :value)")
375        .set_expression_attribute_values(Some(expression_attribute_values.clone()))
376        .send()
377        .await?;
378
379    let attribute_values = scan_response.items.unwrap();
380    // Validate only 1 item was returned: item1
381    assert_eq!(attribute_values.len(), 1);
382    let returned_item = &attribute_values[0];
383    // Validate the item has the expected attributes
384    assert_eq!(returned_item["work_id"], item2["work_id"]);
385
386    // 16. Test the compound beacon 'work_unit' :
387    let expression_attribute_values = HashMap::from([(
388        ":value".to_string(),
389        AttributeValue::S("I-1.T-small".to_string()),
390    )]);
391
392    let scan_response = ddb
393        .scan()
394        .table_name(ddb_table_name)
395        .filter_expression("work_unit = :value")
396        .set_expression_attribute_values(Some(expression_attribute_values.clone()))
397        .send()
398        .await?;
399
400    let attribute_values = scan_response.items.unwrap();
401    // Validate only 1 item was returned: item1
402    assert_eq!(attribute_values.len(), 1);
403    let returned_item = &attribute_values[0];
404    // Validate the item has the expected attributes
405    assert_eq!(returned_item["work_id"], item1["work_id"]);
406
407    println!("beacon_styles_searchable_encryption successful.");
408    Ok(())
409}
examples/searchableencryption/virtual_beacon_searchable_encryption.rs (line 261)
119pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> {
120    let ddb_table_name = test_utils::SIMPLE_BEACON_TEST_DDB_TABLE_NAME;
121    let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN;
122    let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME;
123
124    // 1. Construct a length-1 prefix virtual transform.
125    //    `hasTestResult` is a binary attribute, containing either `true` or `false`.
126    //    As an example to demonstrate virtual transforms, we will truncate the value
127    //    of `hasTestResult` in the virtual field to the length-1 prefix of the binary value, i.e.:
128    //     - "true" -> "t"
129    //     - "false -> "f"
130    //    This is not necessary. This is done as a demonstration of virtual transforms.
131    //    Virtual transform operations treat all attributes as strings
132    //    (i.e. the boolean value `true` is interpreted as a string "true"),
133    //    so its length-1 prefix is just "t".
134
135    let length1_prefix_virtual_transform_list = vec![VirtualTransform::Prefix(
136        GetPrefix::builder().length(1).build()?,
137    )];
138
139    // 2. Construct the VirtualParts required for the VirtualField
140    let has_test_result_part = VirtualPart::builder()
141        .loc("hasTestResult")
142        .trans(length1_prefix_virtual_transform_list)
143        .build()?;
144
145    let state_part = VirtualPart::builder().loc("state").build()?;
146    // Note that we do not apply any transform to the `state` attribute,
147    // and the virtual field will read in the attribute as-is.
148
149    // 3. Construct the VirtualField from the VirtualParts
150    //    Note that the order that virtual parts are added to the virtualPartList
151    //    dictates the order in which they are concatenated to build the virtual field.
152    //    You must add virtual parts in the same order on write as you do on read.
153    let virtual_part_list = vec![state_part, has_test_result_part];
154
155    let state_and_has_test_result_field = VirtualField::builder()
156        .name("stateAndHasTestResult")
157        .parts(virtual_part_list)
158        .build()?;
159
160    let virtual_field_list = vec![state_and_has_test_result_field];
161
162    // 4. Configure our beacon.
163    //    The virtual field is assumed to hold a US 2-letter state abbreviation
164    //    (56 possible values = 50 states + 6 territories) concatenated with a binary attribute
165    //    (2 possible values: true/false hasTestResult field), we expect a population size of
166    //    56 * 2 = 112 possible values.
167    //    We will also assume that these values are reasonably well-distributed across
168    //    customer IDs. In practice, this will not be true. We would expect
169    //    more populous states to appear more frequently in the database.
170    //    A more complex analysis would show that a stricter upper bound
171    //    is necessary to account for this by hiding information from the
172    //    underlying distribution.
173    //
174    //    This link provides guidance for choosing a beacon length:
175    //       https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
176    //    We follow the guidance in the link above to determine reasonable bounds for beacon length:
177    //     - min: log(sqrt(112))/log(2) ~= 3.4, round down to 3
178    //     - max: log((112/2))/log(2) ~= 5.8, round up to 6
179    //    You will somehow need to round results to a nearby integer.
180    //    We choose to round to the nearest integer; you might consider a different rounding approach.
181    //    Rounding up will return fewer expected "false positives" in queries,
182    //       leading to fewer decrypt calls and better performance,
183    //       but it is easier to identify which beacon values encode distinct plaintexts.
184    //    Rounding down will return more expected "false positives" in queries,
185    //       leading to more decrypt calls and worse performance,
186    //       but it is harder to identify which beacon values encode distinct plaintexts.
187    //    We can choose a beacon length between 3 and 6:
188    //     - Closer to 3, we expect more "false positives" to be returned,
189    //       making it harder to identify which beacon values encode distinct plaintexts,
190    //       but leading to more decrypt calls and worse performance
191    //     - Closer to 6, we expect fewer "false positives" returned in queries,
192    //       leading to fewer decrypt calls and better performance,
193    //       but it is easier to identify which beacon values encode distinct plaintexts.
194    //    As an example, we will choose 5.
195    //    Values stored in aws_dbe_b_stateAndHasTestResult will be 5 bits long (0x00 - 0x1f)
196    //    There will be 2^5 = 32 possible HMAC values.
197    //    With a well-distributed dataset (112 values), for a particular beacon we expect
198    //    (112/32) = 3.5 combinations of abbreviation + true/false attribute
199    //    sharing that beacon value.
200    let standard_beacon_list = vec![StandardBeacon::builder()
201        .name("stateAndHasTestResult")
202        .length(5)
203        .build()?];
204
205    // 5. Configure Keystore.
206    //    This example expects that you have already set up a KeyStore with a single branch key.
207    //    See the "CreateKeyStoreTableExample" and "CreateKeyStoreKeyExample" files for how to do this.
208    //    After you create a branch key, you should persist its ID for use in this example.
209    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
210    let key_store_config = KeyStoreConfig::builder()
211        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
212        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
213        .ddb_table_name(branch_key_ddb_table_name)
214        .logical_key_store_name(branch_key_ddb_table_name)
215        .kms_configuration(KmsConfiguration::KmsKeyArn(
216            branch_key_wrapping_kms_key_arn.to_string(),
217        ))
218        .build()?;
219
220    let key_store = keystore_client::Client::from_conf(key_store_config)?;
221
222    // 6. Create BeaconVersion.
223    //    The BeaconVersion inside the list holds the list of beacons on the table.
224    //    The BeaconVersion also stores information about the keystore.
225    //    BeaconVersion must be provided:
226    //      - keyStore: The keystore configured in the previous step.
227    //      - keySource: A configuration for the key source.
228    //        For simple use cases, we can configure a 'singleKeySource' which
229    //        statically configures a single beaconKey. That is the approach this example takes.
230    //        For use cases where you want to use different beacon keys depending on the data
231    //        (for example if your table holds data for multiple tenants, and you want to use
232    //        a different beacon key per tenant), look into configuring a MultiKeyStore:
233    //          https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption-multitenant.html
234    //    We also provide our standard beacon list and virtual fields here.
235    let beacon_version = BeaconVersion::builder()
236        .standard_beacons(standard_beacon_list)
237        .virtual_fields(virtual_field_list)
238        .version(1) // MUST be 1
239        .key_store(key_store.clone())
240        .key_source(BeaconKeySource::Single(
241            SingleKeyStore::builder()
242                // `keyId` references a beacon key.
243                // For every branch key we create in the keystore,
244                // we also create a beacon key.
245                // This beacon key is not the same as the branch key,
246                // but is created with the same ID as the branch key.
247                .key_id(branch_key_id)
248                .cache_ttl(6000)
249                .build()?,
250        ))
251        .build()?;
252    let beacon_versions = vec![beacon_version];
253
254    // 7. Create a Hierarchical Keyring
255    //    This is a KMS keyring that utilizes the keystore table.
256    //    This config defines how items are encrypted and decrypted.
257    //    NOTE: You should configure this to use the same keystore as your search config.
258    let mpl_config = MaterialProvidersConfig::builder().build()?;
259    let mpl = mpl_client::Client::from_conf(mpl_config)?;
260    let kms_keyring = mpl
261        .create_aws_kms_hierarchical_keyring()
262        .branch_key_id(branch_key_id)
263        .key_store(key_store)
264        .ttl_seconds(6000)
265        .send()
266        .await?;
267
268    // 8. Configure which attributes are encrypted and/or signed when writing new items.
269    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
270    //    we must explicitly configure how they should be treated during item encryption:
271    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
272    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
273    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
274    //    Any attributes that will be used in beacons must be configured as ENCRYPT_AND_SIGN.
275    let attribute_actions_on_encrypt = HashMap::from([
276        ("customer_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
277        ("create_time".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
278        ("state".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
279        ("hasTestResult".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
280    ]);
281
282    // 9. Create the DynamoDb Encryption configuration for the table we will be writing to.
283    //    The beaconVersions are added to the search configuration.
284    let table_config = DynamoDbTableEncryptionConfig::builder()
285        .logical_table_name(ddb_table_name)
286        .partition_key_name("customer_id")
287        .sort_key_name("create_time")
288        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
289        .keyring(kms_keyring)
290        .search(
291            SearchConfig::builder()
292                .write_version(1) // MUST be 1
293                .versions(beacon_versions)
294                .build()?,
295        )
296        .build()?;
297
298    // 10. Create config
299    let encryption_config = DynamoDbTablesEncryptionConfig::builder()
300        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
301        .build()?;
302
303    // 11. Create test items
304
305    // Create item with hasTestResult=true
306    let item_with_has_test_result = HashMap::from([
307        (
308            "customer_id".to_string(),
309            AttributeValue::S("ABC-123".to_string()),
310        ),
311        (
312            "create_time".to_string(),
313            AttributeValue::N("1681495205".to_string()),
314        ),
315        ("state".to_string(), AttributeValue::S("CA".to_string())),
316        ("hasTestResult".to_string(), AttributeValue::Bool(true)),
317    ]);
318
319    // Create item with hasTestResult=false
320    let item_with_no_has_test_result = HashMap::from([
321        (
322            "customer_id".to_string(),
323            AttributeValue::S("DEF-456".to_string()),
324        ),
325        (
326            "create_time".to_string(),
327            AttributeValue::N("1681495205".to_string()),
328        ),
329        ("state".to_string(), AttributeValue::S("CA".to_string())),
330        ("hasTestResult".to_string(), AttributeValue::Bool(false)),
331    ]);
332
333    // 12. If developing or debugging, verify config by checking virtual field values directly
334    let trans = transform_client::Client::from_conf(encryption_config.clone())?;
335    let resolve_output = trans
336        .resolve_attributes()
337        .table_name(ddb_table_name)
338        .item(item_with_has_test_result.clone())
339        .version(1)
340        .send()
341        .await?;
342
343    // CompoundBeacons is empty because we have no Compound Beacons configured
344    assert_eq!(resolve_output.compound_beacons.unwrap().len(), 0);
345
346    // Verify that VirtualFields has the expected value
347    let virtual_fields = resolve_output.virtual_fields.unwrap();
348    assert_eq!(virtual_fields.len(), 1);
349    assert_eq!(virtual_fields["stateAndHasTestResult"], "CAt");
350
351    // 13. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
352    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
353        .interceptor(DbEsdkInterceptor::new(encryption_config)?)
354        .build();
355    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
356
357    // 14. Put two items into our table using the above client.
358    //     The two items will differ only in their `customer_id` attribute (primary key)
359    //         and their `hasTestResult` attribute.
360    //     We will query against these items to demonstrate how to use our setup above
361    //         to query against our `stateAndHasTestResult` beacon.
362    //     Before the item gets sent to DynamoDb, it will be encrypted
363    //         client-side, according to our configuration.
364    //     Since our configuration includes a beacon on a virtual field named
365    //         `stateAndHasTestResult`, the client will add an attribute
366    //         to the item with name `aws_dbe_b_stateAndHasTestResult`.
367    //         Its value will be an HMAC truncated to as many bits as the
368    //         beacon's `length` parameter; i.e. 5.
369
370    ddb.put_item()
371        .table_name(ddb_table_name)
372        .set_item(Some(item_with_has_test_result.clone()))
373        .send()
374        .await?;
375
376    ddb.put_item()
377        .table_name(ddb_table_name)
378        .set_item(Some(item_with_no_has_test_result.clone()))
379        .send()
380        .await?;
381
382    // 15. Query by stateAndHasTestResult attribute.
383    //     Note that we are constructing the query as if we were querying on plaintext values.
384    //     However, the DDB encryption client will detect that this attribute name has a beacon configured.
385    //     The client will add the beaconized attribute name and attribute value to the query,
386    //         and transform the query to use the beaconized name and value.
387    //     Internally, the client will query for and receive all items with a matching HMAC value in the beacon field.
388    //     This may include a number of "false positives" with different ciphertext, but the same truncated HMAC.
389    //     e.g. if truncate(HMAC("CAt"), 5) == truncate(HMAC("DCf"), 5), the query will return both items.
390    //     The client will decrypt all returned items to determine which ones have the expected attribute values,
391    //         and only surface items with the correct plaintext to the user.
392    //     This procedure is internal to the client and is abstracted away from the user;
393    //     e.g. the user will only see "CAt" and never "DCf", though the actual query returned both.
394    let expression_attribute_values = HashMap::from([
395        // We are querying for the item with `state`="CA" and `hasTestResult`=`true`.
396        // Since we added virtual parts as `state` then `hasTestResult`,
397        //     we must write our query expression in the same order.
398        // We constructed our virtual field as `state`+`hasTestResult`,
399        //     so we add the two parts in that order.
400        // Since we also created a virtual transform that truncated `hasTestResult`
401        //     to its length-1 prefix, i.e. "true" -> "t",
402        //     we write that field as its length-1 prefix in the query.
403        (
404            ":stateAndHasTestResult".to_string(),
405            AttributeValue::S("CAt".to_string()),
406        ),
407    ]);
408
409    // GSIs are sometimes a little bit delayed, so we retry if the query comes up empty.
410    for _i in 0..10 {
411        let query_response = ddb
412            .query()
413            .table_name(ddb_table_name)
414            .index_name(GSI_NAME)
415            .key_condition_expression("stateAndHasTestResult = :stateAndHasTestResult")
416            .set_expression_attribute_values(Some(expression_attribute_values.clone()))
417            .send()
418            .await?;
419
420        // if no results, sleep and try again
421        if query_response.items.is_none() || query_response.items.as_ref().unwrap().is_empty() {
422            std::thread::sleep(std::time::Duration::from_millis(20));
423            continue;
424        }
425
426        let attribute_values = query_response.items.unwrap();
427        // Validate only 1 item was returned: the item we just put
428        assert_eq!(attribute_values.len(), 1);
429        let returned_item = &attribute_values[0];
430        // Validate the item has the expected attributes
431        assert_eq!(returned_item["state"], AttributeValue::S("CA".to_string()));
432        assert_eq!(returned_item["hasTestResult"], AttributeValue::Bool(true));
433        break;
434    }
435    println!("virtual_beacon_searchable_encryption successful.");
436    Ok(())
437}
examples/searchableencryption/basic_searchable_encryption.rs (line 207)
54pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> {
55    // The whole thing is wrapped in a future to ensure that everything is Send and Sync
56    let future = async move {
57        let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME;
58        let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN;
59        let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME;
60
61        // 1. Configure Beacons.
62        //    The beacon name must be the name of a table attribute that will be encrypted.
63        //    The `length` parameter dictates how many bits are in the beacon attribute value.
64        //    The following link provides guidance on choosing a beacon length:
65        //        https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
66
67        // The configured DDB table has a GSI on the `aws_dbe_b_inspector_id_last4` AttributeName.
68        // This field holds the last 4 digits of an inspector ID.
69        // For our example, this field may range from 0 to 9,999 (10,000 possible values).
70        // For our example, we assume a full inspector ID is an integer
71        //     ranging from 0 to 99,999,999. We do not assume that the full inspector ID's
72        //     values are uniformly distributed across its range of possible values.
73        //     In many use cases, the prefix of an identifier encodes some information
74        //     about that identifier (e.g. zipcode and SSN prefixes encode geographic
75        //     information), while the suffix does not and is more uniformly distributed.
76        //     We will assume that the inspector ID field matches a similar use case.
77        //     So for this example, we only store and use the last
78        //     4 digits of the inspector ID, which we assume is uniformly distributed.
79        // Since the full ID's range is divisible by the range of the last 4 digits,
80        //     then the last 4 digits of the inspector ID are uniformly distributed
81        //     over the range from 0 to 9,999.
82        // See our documentation for why you should avoid creating beacons over non-uniform distributions
83        //  https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me
84        // A single inspector ID suffix may be assigned to multiple `work_id`s.
85        //
86        // This link provides guidance for choosing a beacon length:
87        //    https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
88        // We follow the guidance in the link above to determine reasonable bounds
89        // for the length of a beacon on the last 4 digits of an inspector ID:
90        //  - min: log(sqrt(10,000))/log(2) ~= 6.6, round up to 7
91        //  - max: log((10,000/2))/log(2) ~= 12.3, round down to 12
92        // You will somehow need to round results to a nearby integer.
93        // We choose to round to the nearest integer; you might consider a different rounding approach.
94        // Rounding up will return fewer expected "false positives" in queries,
95        //    leading to fewer decrypt calls and better performance,
96        //    but it is easier to identify which beacon values encode distinct plaintexts.
97        // Rounding down will return more expected "false positives" in queries,
98        //    leading to more decrypt calls and worse performance,
99        //    but it is harder to identify which beacon values encode distinct plaintexts.
100        // We can choose a beacon length between 7 and 12:
101        //  - Closer to 7, we expect more "false positives" to be returned,
102        //    making it harder to identify which beacon values encode distinct plaintexts,
103        //    but leading to more decrypt calls and worse performance
104        //  - Closer to 12, we expect fewer "false positives" returned in queries,
105        //    leading to fewer decrypt calls and better performance,
106        //    but it is easier to identify which beacon values encode distinct plaintexts.
107        // As an example, we will choose 10.
108        //
109        // Values stored in aws_dbe_b_inspector_id_last4 will be 10 bits long (0x000 - 0x3ff)
110        // There will be 2^10 = 1,024 possible HMAC values.
111        // With a sufficiently large number of well-distributed inspector IDs,
112        //    for a particular beacon we expect (10,000/1,024) ~= 9.8 4-digit inspector ID suffixes
113        //    sharing that beacon value.
114        let last4_beacon = StandardBeacon::builder()
115            .name("inspector_id_last4")
116            .length(10)
117            .build()?;
118
119        // The configured DDB table has a GSI on the `aws_dbe_b_unit` AttributeName.
120        // This field holds a unit serial number.
121        // For this example, this is a 12-digit integer from 0 to 999,999,999,999 (10^12 possible values).
122        // We will assume values for this attribute are uniformly distributed across this range.
123        // A single unit serial number may be assigned to multiple `work_id`s.
124        //
125        // This link provides guidance for choosing a beacon length:
126        //    https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html
127        // We follow the guidance in the link above to determine reasonable bounds
128        // for the length of a beacon on a unit serial number:
129        //  - min: log(sqrt(999,999,999,999))/log(2) ~= 19.9, round up to 20
130        //  - max: log((999,999,999,999/2))/log(2) ~= 38.9, round up to 39
131        // We can choose a beacon length between 20 and 39:
132        //  - Closer to 20, we expect more "false positives" to be returned,
133        //    making it harder to identify which beacon values encode distinct plaintexts,
134        //    but leading to more decrypt calls and worse performance
135        //  - Closer to 39, we expect fewer "false positives" returned in queries,
136        //    leading to fewer decrypt calls and better performance,
137        //    but it is easier to identify which beacon values encode distinct plaintexts.
138        // As an example, we will choose 30.
139        //
140        // Values stored in aws_dbe_b_unit will be 30 bits long (0x00000000 - 0x3fffffff)
141        // There will be 2^30 = 1,073,741,824 ~= 1.1B possible HMAC values.
142        // With a sufficiently large number of well-distributed inspector IDs,
143        //    for a particular beacon we expect (10^12/2^30) ~= 931.3 unit serial numbers
144        //    sharing that beacon value.
145        let unit_beacon = StandardBeacon::builder().name("unit").length(30).build()?;
146
147        let standard_beacon_list = vec![last4_beacon, unit_beacon];
148
149        // 2. Configure Keystore.
150        //    The keystore is a separate DDB table where the client stores encryption and decryption materials.
151        //    In order to configure beacons on the DDB client, you must configure a keystore.
152        //
153        //    This example expects that you have already set up a KeyStore with a single branch key.
154        //    See the "Create KeyStore Table Example" and "Create KeyStore Key Example" for how to do this.
155        //    After you create a branch key, you should persist its ID for use in this example.
156        let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
157        let key_store_config = KeyStoreConfig::builder()
158            .kms_client(aws_sdk_kms::Client::new(&sdk_config))
159            .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
160            .ddb_table_name(branch_key_ddb_table_name)
161            .logical_key_store_name(branch_key_ddb_table_name)
162            .kms_configuration(KmsConfiguration::KmsKeyArn(
163                branch_key_wrapping_kms_key_arn.to_string(),
164            ))
165            .build()?;
166
167        let key_store = keystore_client::Client::from_conf(key_store_config)?;
168
169        // 3. Create BeaconVersion.
170        //    The BeaconVersion inside the list holds the list of beacons on the table.
171        //    The BeaconVersion also stores information about the keystore.
172        //    BeaconVersion must be provided:
173        //      - keyStore: The keystore configured in step 2.
174        //      - keySource: A configuration for the key source.
175        //        For simple use cases, we can configure a 'singleKeySource' which
176        //        statically configures a single beaconKey. That is the approach this example takes.
177        //        For use cases where you want to use different beacon keys depending on the data
178        //        (for example if your table holds data for multiple tenants, and you want to use
179        //        a different beacon key per tenant), look into configuring a MultiKeyStore:
180        //          https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption-multitenant.html
181
182        let beacon_version = BeaconVersion::builder()
183            .standard_beacons(standard_beacon_list)
184            .version(1) // MUST be 1
185            .key_store(key_store.clone())
186            .key_source(BeaconKeySource::Single(
187                SingleKeyStore::builder()
188                    // `keyId` references a beacon key.
189                    // For every branch key we create in the keystore,
190                    // we also create a beacon key.
191                    // This beacon key is not the same as the branch key,
192                    // but is created with the same ID as the branch key.
193                    .key_id(branch_key_id)
194                    .cache_ttl(6000)
195                    .build()?,
196            ))
197            .build()?;
198        let beacon_versions = vec![beacon_version];
199
200        // 4. Create a Hierarchical Keyring
201        //    This is a KMS keyring that utilizes the keystore table.
202        //    This config defines how items are encrypted and decrypted.
203        //    NOTE: You should configure this to use the same keystore as your search config.
204        let provider_config = MaterialProvidersConfig::builder().build()?;
205        let mat_prov = client::Client::from_conf(provider_config)?;
206        let kms_keyring = mat_prov
207            .create_aws_kms_hierarchical_keyring()
208            .branch_key_id(branch_key_id)
209            .key_store(key_store)
210            .ttl_seconds(6000)
211            .send()
212            .await?;
213
214        // 5. Configure which attributes are encrypted and/or signed when writing new items.
215        //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
216        //    we must explicitly configure how they should be treated during item encryption:
217        //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
218        //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
219        //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
220        //    Any attributes that will be used in beacons must be configured as ENCRYPT_AND_SIGN.
221        let attribute_actions_on_encrypt = HashMap::from([
222            ("work_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
223            ("inspection_date".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
224            (
225                "inspector_id_last4".to_string(),
226                CryptoAction::EncryptAndSign,
227            ), // Beaconized attributes must be encrypted
228            ("unit".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted
229        ]);
230
231        // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
232        //    The beaconVersions are added to the search configuration.
233        let table_config = DynamoDbTableEncryptionConfig::builder()
234            .logical_table_name(ddb_table_name)
235            .partition_key_name("work_id")
236            .sort_key_name("inspection_date")
237            .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
238            .keyring(kms_keyring)
239            .search(
240                SearchConfig::builder()
241                    .write_version(1) // MUST be 1
242                    .versions(beacon_versions)
243                    .build()?,
244            )
245            .build()?;
246
247        let table_configs = DynamoDbTablesEncryptionConfig::builder()
248            .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
249            .build()?;
250
251        // 7. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs
252        let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
253        let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
254            .interceptor(DbEsdkInterceptor::new(table_configs)?)
255            .build();
256        let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
257
258        // 8. Put an item into our table using the above client.
259        //    Before the item gets sent to DynamoDb, it will be encrypted
260        //        client-side, according to our configuration.
261        //    Since our configuration includes beacons for `inspector_id_last4` and `unit`,
262        //        the client will add two additional attributes to the item. These attributes will have names
263        //        `aws_dbe_b_inspector_id_last4` and `aws_dbe_b_unit`. Their values will be HMACs
264        //        truncated to as many bits as the beacon's `length` parameter; e.g.
265        //    aws_dbe_b_inspector_id_last4 = truncate(HMAC("4321"), 10)
266        //    aws_dbe_b_unit = truncate(HMAC("123456789012"), 30)
267
268        let item = HashMap::from([
269            (
270                "work_id".to_string(),
271                AttributeValue::S("1313ba89-5661-41eb-ba6c-cb1b4cb67b2d".to_string()),
272            ),
273            (
274                "inspection_date".to_string(),
275                AttributeValue::S("2023-06-13".to_string()),
276            ),
277            (
278                "inspector_id_last4".to_string(),
279                AttributeValue::S("4321".to_string()),
280            ),
281            (
282                "unit".to_string(),
283                AttributeValue::S("123456789012".to_string()),
284            ),
285        ]);
286
287        ddb.put_item()
288            .table_name(ddb_table_name)
289            .set_item(Some(item.clone()))
290            .send()
291            .await?;
292
293        // 9. Query for the item we just put.
294        //     Note that we are constructing the query as if we were querying on plaintext values.
295        //     However, the DDB encryption client will detect that this attribute name has a beacon configured.
296        //     The client will add the beaconized attribute name and attribute value to the query,
297        //         and transform the query to use the beaconized name and value.
298        //     Internally, the client will query for and receive all items with a matching HMAC value in the beacon field.
299        //     This may include a number of "false positives" with different ciphertext, but the same truncated HMAC.
300        //     e.g. if truncate(HMAC("123456789012"), 30)
301        //          == truncate(HMAC("098765432109"), 30),
302        //     the query will return both items.
303        //     The client will decrypt all returned items to determine which ones have the expected attribute values,
304        //         and only surface items with the correct plaintext to the user.
305        //     This procedure is internal to the client and is abstracted away from the user;
306        //     e.g. the user will only see "123456789012" and never
307        //        "098765432109", though the actual query returned both.
308        let expression_attributes_names = HashMap::from([
309            ("#last4".to_string(), "inspector_id_last4".to_string()),
310            ("#unit".to_string(), "unit".to_string()),
311        ]);
312
313        let expression_attribute_values = HashMap::from([
314            (":last4".to_string(), AttributeValue::S("4321".to_string())),
315            (
316                ":unit".to_string(),
317                AttributeValue::S("123456789012".to_string()),
318            ),
319        ]);
320
321        // GSIs do not update instantly
322        // so if the results come back empty
323        // we retry after a short sleep
324        for _i in 0..10 {
325            let query_response = ddb
326                .query()
327                .table_name(ddb_table_name)
328                .index_name(GSI_NAME)
329                .key_condition_expression("#last4 = :last4 and #unit = :unit")
330                .set_expression_attribute_names(Some(expression_attributes_names.clone()))
331                .set_expression_attribute_values(Some(expression_attribute_values.clone()))
332                .send()
333                .await?;
334
335            // if no results, sleep and try again
336            if query_response.items.is_none() || query_response.items.as_ref().unwrap().is_empty() {
337                std::thread::sleep(std::time::Duration::from_millis(20));
338                continue;
339            }
340
341            let attribute_values = query_response.items.unwrap();
342            // Validate only 1 item was returned: the item we just put
343            assert_eq!(attribute_values.len(), 1);
344            let returned_item = &attribute_values[0];
345            // Validate the item has the expected attributes
346            assert_eq!(
347                returned_item["inspector_id_last4"],
348                AttributeValue::S("4321".to_string())
349            );
350            assert_eq!(
351                returned_item["unit"],
352                AttributeValue::S("123456789012".to_string())
353            );
354            break;
355        }
356        println!("basic_searchable_encryption successful.");
357        Ok(())
358    };
359    future.await
360}
examples/searchableencryption/complexexample/beacon_config.rs (line 486)
39pub async fn setup_beacon_config(
40    ddb_table_name: &str,
41    branch_key_id: &str,
42    branch_key_wrapping_kms_key_arn: &str,
43    branch_key_ddb_table_name: &str,
44) -> Result<aws_sdk_dynamodb::Client, crate::BoxError> {
45    // 1. Create keystore and branch key
46    //    These are the same constructions as in the Basic examples, which describe this in more detail.
47    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
48    let key_store_config = KeyStoreConfig::builder()
49        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
50        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
51        .ddb_table_name(branch_key_ddb_table_name)
52        .logical_key_store_name(branch_key_ddb_table_name)
53        .kms_configuration(KmsConfiguration::KmsKeyArn(
54            branch_key_wrapping_kms_key_arn.to_string(),
55        ))
56        .build()?;
57
58    let key_store = keystore_client::Client::from_conf(key_store_config)?;
59
60    // 2. Create standard beacons
61    //    For this example, we use a standard beacon length of 4.
62    //    The BasicSearchableEncryptionExample gives a more thorough consideration of beacon length.
63    //    For production applications, one should always exercise rigor when deciding beacon length, including
64    //        examining population size and considering performance.
65    let standard_beacon_list = vec![
66        StandardBeacon::builder()
67            .name("EmployeeID")
68            .length(4)
69            .build()?,
70        StandardBeacon::builder()
71            .name("TicketNumber")
72            .length(4)
73            .build()?,
74        StandardBeacon::builder()
75            .name("ProjectName")
76            .length(4)
77            .build()?,
78        StandardBeacon::builder()
79            .name("EmployeeEmail")
80            .length(4)
81            .build()?,
82        StandardBeacon::builder()
83            .name("CreatorEmail")
84            .length(4)
85            .build()?,
86        StandardBeacon::builder()
87            .name("ProjectStatus")
88            .length(4)
89            .build()?,
90        StandardBeacon::builder()
91            .name("OrganizerEmail")
92            .length(4)
93            .build()?,
94        StandardBeacon::builder()
95            .name("ManagerEmail")
96            .length(4)
97            .build()?,
98        StandardBeacon::builder()
99            .name("AssigneeEmail")
100            .length(4)
101            .build()?,
102        StandardBeacon::builder()
103            .name("Severity")
104            .length(4)
105            .build()?,
106        StandardBeacon::builder()
107            .name("City")
108            .loc("Location.City")
109            .length(4)
110            .build()?,
111        StandardBeacon::builder()
112            .name("Building")
113            .loc("Location.Building")
114            .length(4)
115            .build()?,
116        StandardBeacon::builder()
117            .name("Floor")
118            .loc("Location.Floor")
119            .length(4)
120            .build()?,
121        StandardBeacon::builder()
122            .name("Room")
123            .loc("Location.Room")
124            .length(4)
125            .build()?,
126        StandardBeacon::builder()
127            .name("Desk")
128            .loc("Location.Desk")
129            .length(4)
130            .build()?,
131    ];
132
133    // 3. Define encrypted parts
134    //    Note that some of the prefixes are modified from the suggested prefixes in Demo.md.
135    //    This is because all prefixes must be unique in a configuration.
136    //    Encrypted parts are described in more detail in the CompoundBeaconSearchableEncryptionExample.
137    let encrypted_parts_list = vec![
138        EncryptedPart::builder()
139            .name("EmployeeID")
140            .prefix("E-")
141            .build()?,
142        EncryptedPart::builder()
143            .name("TicketNumber")
144            .prefix("T-")
145            .build()?,
146        EncryptedPart::builder()
147            .name("ProjectName")
148            .prefix("P-")
149            .build()?,
150        EncryptedPart::builder()
151            .name("EmployeeEmail")
152            .prefix("EE-")
153            .build()?,
154        EncryptedPart::builder()
155            .name("CreatorEmail")
156            .prefix("CE-")
157            .build()?,
158        EncryptedPart::builder()
159            .name("OrganizerEmail")
160            .prefix("OE-")
161            .build()?,
162        EncryptedPart::builder()
163            .name("ManagerEmail")
164            .prefix("ME-")
165            .build()?,
166        EncryptedPart::builder()
167            .name("AssigneeEmail")
168            .prefix("AE-")
169            .build()?,
170        EncryptedPart::builder()
171            .name("ProjectStatus")
172            .prefix("PSts-")
173            .build()?,
174        EncryptedPart::builder().name("City").prefix("C-").build()?,
175        EncryptedPart::builder()
176            .name("Severity")
177            .prefix("S-")
178            .build()?,
179        EncryptedPart::builder()
180            .name("Building")
181            .prefix("B-")
182            .build()?,
183        EncryptedPart::builder()
184            .name("Floor")
185            .prefix("F-")
186            .build()?,
187        EncryptedPart::builder().name("Room").prefix("R-").build()?,
188        EncryptedPart::builder().name("Desk").prefix("D-").build()?,
189    ];
190
191    // 4. Define signed parts
192    //    These are unencrypted attributes we would like to use in beacon queries.
193    //    In this example, all of these represent dates or times.
194    //    Keeping these attributes unencrypted allows us to use them in comparison-based queries. If a signed
195    //        part is the first part in a compound beacon, then that part can be used in comparison for sorting.
196    let signed_parts_list = vec![
197        SignedPart::builder()
198            .name("TicketModTime")
199            .prefix("M-")
200            .build()?,
201        SignedPart::builder()
202            .name("MeetingStart")
203            .prefix("MS-")
204            .build()?,
205        SignedPart::builder()
206            .name("TimeCardStart")
207            .prefix("TC-")
208            .build()?,
209        SignedPart::builder()
210            .name("ProjectStart")
211            .prefix("PS-")
212            .build()?,
213    ];
214
215    // 5. Create constructor parts
216    //    Constructor parts are used to assemble constructors (constructors described more in next step).
217    //    For each attribute that will be used in a constructor, there must be a corresponding constructor part.
218    //    A constructor part must receive:
219    //     - name: Name of a standard beacon
220    //     - required: Whether this attribute must be present in the item to match a constructor
221    //    In this example, we will define each constructor part once and re-use it across multiple constructors.
222    //    The parts below are defined by working backwards from the constructors in "PK Constructors",
223    //        "SK constructors", etc. sections in Demo.md.
224    let employee_id_constructor_part = ConstructorPart::builder()
225        .name("EmployeeID")
226        .required(true)
227        .build()?;
228    let ticket_number_constructor_part = ConstructorPart::builder()
229        .name("TicketNumber")
230        .required(true)
231        .build()?;
232    let project_name_constructor_part = ConstructorPart::builder()
233        .name("ProjectName")
234        .required(true)
235        .build()?;
236    let ticket_mod_time_constructor_part = ConstructorPart::builder()
237        .name("TicketModTime")
238        .required(true)
239        .build()?;
240    let meeting_start_constructor_part = ConstructorPart::builder()
241        .name("MeetingStart")
242        .required(true)
243        .build()?;
244    let timecard_start_constructor_part = ConstructorPart::builder()
245        .name("TimeCardStart")
246        .required(true)
247        .build()?;
248    let employee_email_constructor_part = ConstructorPart::builder()
249        .name("EmployeeEmail")
250        .required(true)
251        .build()?;
252    let creator_email_constructor_part = ConstructorPart::builder()
253        .name("CreatorEmail")
254        .required(true)
255        .build()?;
256    let project_status_constructor_part = ConstructorPart::builder()
257        .name("ProjectStatus")
258        .required(true)
259        .build()?;
260    let organizer_email_constructor_part = ConstructorPart::builder()
261        .name("OrganizerEmail")
262        .required(true)
263        .build()?;
264    let project_start_constructor_part = ConstructorPart::builder()
265        .name("ProjectStart")
266        .required(true)
267        .build()?;
268    let manager_email_constructor_part = ConstructorPart::builder()
269        .name("ManagerEmail")
270        .required(true)
271        .build()?;
272    let assignee_email_constructor_part = ConstructorPart::builder()
273        .name("AssigneeEmail")
274        .required(true)
275        .build()?;
276    let city_constructor_part = ConstructorPart::builder()
277        .name("City")
278        .required(true)
279        .build()?;
280    let severity_constructor_part = ConstructorPart::builder()
281        .name("Severity")
282        .required(true)
283        .build()?;
284    let building_constructor_part = ConstructorPart::builder()
285        .name("Building")
286        .required(true)
287        .build()?;
288    let floor_constructor_part = ConstructorPart::builder()
289        .name("Floor")
290        .required(true)
291        .build()?;
292    let room_constructor_part = ConstructorPart::builder()
293        .name("Room")
294        .required(true)
295        .build()?;
296    let desk_constructor_part = ConstructorPart::builder()
297        .name("Desk")
298        .required(true)
299        .build()?;
300
301    // 6. Define constructors
302    //    Constructors define how encrypted and signed parts are assembled into compound beacons.
303    //    The constructors below are based off of the "PK Constructors", "SK constructors", etc. sections in Demo.md.
304
305    // The employee ID constructor only requires an employee ID.
306    // If an item has an attribute with name "EmployeeID", it will match this constructor.
307    // If this is the first matching constructor in the constructor list (constructor list described more below),
308    //     the compound beacon will use this constructor, and the compound beacon will be written as `E-X`.
309
310    let employee_id_constructor = Constructor::builder()
311        .parts(vec![employee_id_constructor_part])
312        .build()?;
313    let ticket_number_constructor = Constructor::builder()
314        .parts(vec![ticket_number_constructor_part])
315        .build()?;
316    let project_name_constructor = Constructor::builder()
317        .parts(vec![project_name_constructor_part])
318        .build()?;
319    let ticket_mod_time_constructor = Constructor::builder()
320        .parts(vec![ticket_mod_time_constructor_part])
321        .build()?;
322    let building_constructor = Constructor::builder()
323        .parts(vec![building_constructor_part.clone()])
324        .build()?;
325
326    // This constructor requires all of "MeetingStart", "Location.Floor", and "Location.Room" attributes.
327    // If an item has all of these attributes, it will match this constructor.
328    // If this is the first matching constructor in the constructor list (constructor list described more below),
329    //     the compound beacon will use this constructor, and the compound beacon will be written as `MS-X~F-Y~R-Z`.
330    // In a constructor with multiple constructor parts, the order the constructor parts are added to
331    //     the constructor part list defines how the compound beacon is written.
332    // We can rearrange the beacon parts by changing the order the constructors were added to the list.
333    let meeting_start_floor_room_constructor = Constructor::builder()
334        .parts(vec![
335            meeting_start_constructor_part,
336            floor_constructor_part.clone(),
337            room_constructor_part,
338        ])
339        .build()?;
340
341    let timecard_start_constructor = Constructor::builder()
342        .parts(vec![timecard_start_constructor_part.clone()])
343        .build()?;
344    let timecard_start_employee_email_constructor = Constructor::builder()
345        .parts(vec![
346            timecard_start_constructor_part,
347            employee_email_constructor_part.clone(),
348        ])
349        .build()?;
350    let creator_email_constructor = Constructor::builder()
351        .parts(vec![creator_email_constructor_part])
352        .build()?;
353    let project_status_constructor = Constructor::builder()
354        .parts(vec![project_status_constructor_part])
355        .build()?;
356    let employee_email_constructor = Constructor::builder()
357        .parts(vec![employee_email_constructor_part])
358        .build()?;
359    let organizer_email_constructor = Constructor::builder()
360        .parts(vec![organizer_email_constructor_part])
361        .build()?;
362    let project_start_constructor = Constructor::builder()
363        .parts(vec![project_start_constructor_part])
364        .build()?;
365    let manager_email_constructor = Constructor::builder()
366        .parts(vec![manager_email_constructor_part])
367        .build()?;
368    let assignee_email_constructor = Constructor::builder()
369        .parts(vec![assignee_email_constructor_part])
370        .build()?;
371    let city_constructor = Constructor::builder()
372        .parts(vec![city_constructor_part])
373        .build()?;
374    let severity_constructor = Constructor::builder()
375        .parts(vec![severity_constructor_part])
376        .build()?;
377    let building_floor_desk_constructor = Constructor::builder()
378        .parts(vec![
379            building_constructor_part,
380            floor_constructor_part,
381            desk_constructor_part,
382        ])
383        .build()?;
384
385    // 7. Add constructors to the compound beacon constructor list in desired construction order
386    //    In a compound beacon with multiple constructors, the order the constructors are added to
387    //        the constructor list determines their priority.
388    //    The first constructor added to a constructor list will be the first constructor that is executed.
389    //    The client will evaluate constructors until one matches, and will use the first one that matches.
390    //    If no constructors match, an attribute value is not written for that beacon.
391    //    A general strategy is to add constructors with unique conditions at the beginning of the list,
392    //       and add constructors with general conditions at the end of the list. This would allow a given
393    //       item to trigger the constructor most specific to its attributes.
394    let pk0_constructor_list = vec![
395        employee_id_constructor.clone(),
396        building_constructor,
397        ticket_number_constructor,
398        project_name_constructor.clone(),
399    ];
400    let sk0_constructor_list = vec![
401        ticket_mod_time_constructor.clone(),
402        meeting_start_floor_room_constructor.clone(),
403        timecard_start_employee_email_constructor,
404        project_name_constructor,
405        employee_id_constructor.clone(),
406    ];
407    let pk1_constructor_list = vec![
408        creator_email_constructor,
409        employee_email_constructor,
410        project_status_constructor,
411        organizer_email_constructor,
412    ];
413    let sk1_constructor_list = vec![
414        meeting_start_floor_room_constructor,
415        timecard_start_constructor,
416        ticket_mod_time_constructor.clone(),
417        project_start_constructor,
418        employee_id_constructor,
419    ];
420    let pk2_constructor_list = vec![manager_email_constructor, assignee_email_constructor];
421    let pk3_constructor_list = vec![city_constructor, severity_constructor];
422    let sk3_constructor_list = vec![building_floor_desk_constructor, ticket_mod_time_constructor];
423
424    // 8. Define compound beacons
425    //    Compound beacon construction is defined in more detail in CompoundBeaconSearchableEncryptionExample.
426    //    Note that the split character must be a character that is not used in any attribute value.
427    let compound_beacon_list = vec![
428        CompoundBeacon::builder()
429            .name("PK")
430            .split("~")
431            .constructors(pk0_constructor_list)
432            .build()?,
433        CompoundBeacon::builder()
434            .name("SK")
435            .split("~")
436            .constructors(sk0_constructor_list)
437            .build()?,
438        CompoundBeacon::builder()
439            .name("PK1")
440            .split("~")
441            .constructors(pk1_constructor_list)
442            .build()?,
443        CompoundBeacon::builder()
444            .name("SK1")
445            .split("~")
446            .constructors(sk1_constructor_list)
447            .build()?,
448        CompoundBeacon::builder()
449            .name("PK2")
450            .split("~")
451            .constructors(pk2_constructor_list)
452            .build()?,
453        CompoundBeacon::builder()
454            .name("PK3")
455            .split("~")
456            .constructors(pk3_constructor_list)
457            .build()?,
458        CompoundBeacon::builder()
459            .name("SK3")
460            .split("~")
461            .constructors(sk3_constructor_list)
462            .build()?,
463    ];
464
465    // 9. Create BeaconVersion
466    let beacon_versions = BeaconVersion::builder()
467        .standard_beacons(standard_beacon_list)
468        .compound_beacons(compound_beacon_list)
469        .encrypted_parts(encrypted_parts_list)
470        .signed_parts(signed_parts_list)
471        .version(1)
472        .key_store(key_store.clone())
473        .key_source(BeaconKeySource::Single(
474            SingleKeyStore::builder()
475                .key_id(branch_key_id)
476                .cache_ttl(6000)
477                .build()?,
478        ))
479        .build()?;
480    let beacon_versions = vec![beacon_versions];
481
482    // 10. Create a Hierarchical Keyring
483    let mpl_config = MaterialProvidersConfig::builder().build()?;
484    let mpl = mpl_client::Client::from_conf(mpl_config)?;
485    let kms_keyring = mpl
486        .create_aws_kms_hierarchical_keyring()
487        .branch_key_id(branch_key_id)
488        .key_store(key_store)
489        .ttl_seconds(600)
490        .send()
491        .await?;
492
493    // 11. Define crypto actions
494    let attribute_actions_on_encrypt = HashMap::from([
495        // Our partition key must be configured as SIGN_ONLY
496        ("partition_key".to_string(), CryptoAction::SignOnly),
497        // Attributes used in beacons must be configured as ENCRYPT_AND_SIGN
498        ("EmployeeID".to_string(), CryptoAction::EncryptAndSign),
499        ("TicketNumber".to_string(), CryptoAction::EncryptAndSign),
500        ("ProjectName".to_string(), CryptoAction::EncryptAndSign),
501        ("EmployeeName".to_string(), CryptoAction::EncryptAndSign),
502        ("EmployeeEmail".to_string(), CryptoAction::EncryptAndSign),
503        ("CreatorEmail".to_string(), CryptoAction::EncryptAndSign),
504        ("ProjectStatus".to_string(), CryptoAction::EncryptAndSign),
505        ("OrganizerEmail".to_string(), CryptoAction::EncryptAndSign),
506        ("ManagerEmail".to_string(), CryptoAction::EncryptAndSign),
507        ("AssigneeEmail".to_string(), CryptoAction::EncryptAndSign),
508        ("City".to_string(), CryptoAction::EncryptAndSign),
509        ("Severity".to_string(), CryptoAction::EncryptAndSign),
510        ("Location".to_string(), CryptoAction::EncryptAndSign),
511        // These are not beaconized attributes, but are sensitive data that must be encrypted
512        ("Attendees".to_string(), CryptoAction::EncryptAndSign),
513        ("Subject".to_string(), CryptoAction::EncryptAndSign),
514        // Signed parts and unencrypted attributes can be configured as SIGN_ONLY or DO_NOTHING
515        // For this example, we will set these to SIGN_ONLY to ensure authenticity
516        ("TicketModTime".to_string(), CryptoAction::SignOnly),
517        ("MeetingStart".to_string(), CryptoAction::SignOnly),
518        ("TimeCardStart".to_string(), CryptoAction::SignOnly),
519        ("EmployeeTitle".to_string(), CryptoAction::SignOnly),
520        ("Description".to_string(), CryptoAction::SignOnly),
521        ("ProjectTarget".to_string(), CryptoAction::SignOnly),
522        ("Hours".to_string(), CryptoAction::SignOnly),
523        ("Role".to_string(), CryptoAction::SignOnly),
524        ("Message".to_string(), CryptoAction::SignOnly),
525        ("ProjectStart".to_string(), CryptoAction::SignOnly),
526        ("Duration".to_string(), CryptoAction::SignOnly),
527    ]);
528
529    // 12. Set up table config
530    let table_config = DynamoDbTableEncryptionConfig::builder()
531        .logical_table_name(ddb_table_name)
532        .partition_key_name("partition_key")
533        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
534        .keyring(kms_keyring)
535        .search(
536            SearchConfig::builder()
537                .write_version(1)
538                .versions(beacon_versions)
539                .build()?,
540        )
541        .build()?;
542
543    let table_configs = DynamoDbTablesEncryptionConfig::builder()
544        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
545        .build()?;
546
547    // 13. Create a new AWS SDK DynamoDb client using the config above
548    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
549    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
550        .interceptor(DbEsdkInterceptor::new(table_configs)?)
551        .build();
552
553    Ok(aws_sdk_dynamodb::Client::from_conf(dynamo_config))
554}
Source§

impl Client

Source

pub fn create_aws_kms_rsa_keyring(&self) -> CreateAwsKmsRsaKeyringFluentBuilder

Examples found in repository?
examples/keyring/kms_rsa_keyring.rs (line 82)
47pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
48    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
49    let rsa_key_arn = test_utils::TEST_KMS_RSA_KEY_ID;
50
51    // You may provide your own RSA public key at EXAMPLE_RSA_PUBLIC_KEY_FILENAME.
52    // This must be the public key for the RSA key represented at rsaKeyArn.
53    // If this file is not present, this will write a UTF-8 encoded PEM file for you.
54    if should_get_new_public_key(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME) {
55        write_public_key_pem_for_rsa_key(
56            test_utils::TEST_KMS_RSA_KEY_ID,
57            DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME,
58        )
59        .await?;
60    }
61
62    // 1. Load UTF-8 encoded public key PEM file.
63    //    You may have an RSA public key file already defined.
64    //    If not, the main method in this class will call
65    //    the KMS RSA key, retrieve its public key, and store it
66    //    in a PEM file for example use.
67    let mut file = File::open(Path::new(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?;
68    let mut public_key_utf8_bytes = Vec::new();
69    file.read_to_end(&mut public_key_utf8_bytes)?;
70
71    // 2. Create a KMS RSA keyring.
72    //    This keyring takes in:
73    //     - kmsClient
74    //     - kmsKeyId: Must be an ARN representing a KMS RSA key
75    //     - publicKey: A ByteBuffer of a UTF-8 encoded PEM file representing the public
76    //                  key for the key passed into kmsKeyId
77    //     - encryptionAlgorithm: Must be either RSAES_OAEP_SHA_256 or RSAES_OAEP_SHA_1
78    let mpl_config = MaterialProvidersConfig::builder().build()?;
79    let mpl = mpl_client::Client::from_conf(mpl_config)?;
80    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
81    let kms_rsa_keyring = mpl
82        .create_aws_kms_rsa_keyring()
83        .kms_key_id(rsa_key_arn)
84        .public_key(public_key_utf8_bytes)
85        .encryption_algorithm(aws_sdk_kms::types::EncryptionAlgorithmSpec::RsaesOaepSha256)
86        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
87        .send()
88        .await?;
89
90    // 3. Configure which attributes are encrypted and/or signed when writing new items.
91    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
92    //    we must explicitly configure how they should be treated during item encryption:
93    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
94    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
95    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
96    let attribute_actions_on_encrypt = HashMap::from([
97        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
98        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
99        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
100    ]);
101
102    // 4. Configure which attributes we expect to be included in the signature
103    //    when reading items. There are two options for configuring this:
104    //
105    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
106    //      When defining your DynamoDb schema and deciding on attribute names,
107    //      choose a distinguishing prefix (such as ":") for all attributes that
108    //      you do not want to include in the signature.
109    //      This has two main benefits:
110    //      - It is easier to reason about the security and authenticity of data within your item
111    //        when all unauthenticated data is easily distinguishable by their attribute name.
112    //      - If you need to add new unauthenticated attributes in the future,
113    //        you can easily make the corresponding update to your `attributeActions`
114    //        and immediately start writing to that new attribute, without
115    //        any other configuration update needed.
116    //      Once you configure this field, it is not safe to update it.
117    //
118    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
119    //      a set of attributes that should be considered unauthenticated when encountered
120    //      on read. Be careful if you use this configuration. Do not remove an attribute
121    //      name from this configuration, even if you are no longer writing with that attribute,
122    //      as old items may still include this attribute, and our configuration needs to know
123    //      to continue to exclude this attribute from the signature scope.
124    //      If you add new attribute names to this field, you must first deploy the update to this
125    //      field to all readers in your host fleet before deploying the update to start writing
126    //      with that new attribute.
127    //
128    //   For this example, we currently authenticate all attributes. To make it easier to
129    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
130    const UNSIGNED_ATTR_PREFIX: &str = ":";
131
132    // 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
133    //    Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite
134    //    that does not use asymmetric signing.
135    let table_config = DynamoDbTableEncryptionConfig::builder()
136        .logical_table_name(ddb_table_name)
137        .partition_key_name("partition_key")
138        .sort_key_name("sort_key")
139        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
140        .keyring(kms_rsa_keyring)
141        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
142        // Specify algorithmSuite without asymmetric signing here
143        // As of v3.0.0, the only supported algorithmSuite without asymmetric signing is
144        // ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384.
145        .algorithm_suite_id(DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeySymsigHmacSha384)
146        .build()?;
147
148    let table_configs = DynamoDbTablesEncryptionConfig::builder()
149        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
150        .build()?;
151
152    // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
153    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
154        .interceptor(DbEsdkInterceptor::new(table_configs)?)
155        .build();
156    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
157
158    // 7. Put an item into our table using the above client.
159    //    Before the item gets sent to DynamoDb, it will be encrypted
160    //    client-side, according to our configuration.
161    let item = HashMap::from([
162        (
163            "partition_key".to_string(),
164            AttributeValue::S("awsKmsRsaKeyringItem".to_string()),
165        ),
166        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
167        (
168            "sensitive_data".to_string(),
169            AttributeValue::S("encrypt and sign me!".to_string()),
170        ),
171    ]);
172
173    ddb.put_item()
174        .table_name(ddb_table_name)
175        .set_item(Some(item.clone()))
176        .send()
177        .await?;
178
179    // 8. Get the item back from our table using the client.
180    //    The client will decrypt the item client-side using the RSA keyring
181    //    and return the original item.
182    let key_to_get = HashMap::from([
183        (
184            "partition_key".to_string(),
185            AttributeValue::S("awsKmsRsaKeyringItem".to_string()),
186        ),
187        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
188    ]);
189
190    let resp = ddb
191        .get_item()
192        .table_name(ddb_table_name)
193        .set_key(Some(key_to_get))
194        .consistent_read(true)
195        .send()
196        .await?;
197
198    assert_eq!(resp.item, Some(item));
199    println!("kms_rsa_keyring successful.");
200    Ok(())
201}
Source§

impl Client

Source§

impl Client

Source

pub fn create_multi_keyring(&self) -> CreateMultiKeyringFluentBuilder

Examples found in repository?
examples/keyring/multi_keyring.rs (line 82)
46pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
47    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
48    let key_arn = test_utils::TEST_KMS_KEY_ID;
49    let aes_key_bytes = generate_aes_key_bytes();
50
51    // 1. Create the raw AES keyring.
52    let mpl_config = MaterialProvidersConfig::builder().build()?;
53    let mpl = mpl_client::Client::from_conf(mpl_config)?;
54    let raw_aes_keyring = mpl
55        .create_raw_aes_keyring()
56        .key_name("my-aes-key-name")
57        .key_namespace("my-key-namespace")
58        .wrapping_key(aes_key_bytes)
59        .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16)
60        .send()
61        .await?;
62
63    // 2. Create the AWS KMS keyring.
64    //    We create a MRK multi keyring, as this interface also supports
65    //    single-region KMS keys (standard KMS keys),
66    //    and creates the KMS client for us automatically.
67    let aws_kms_mrk_multi_keyring = mpl
68        .create_aws_kms_mrk_multi_keyring()
69        .generator(key_arn)
70        .send()
71        .await?;
72
73    // 3. Create the multi-keyring.
74    //    We will label the AWS KMS keyring as the generator and the raw AES keyring as the
75    //        only child keyring.
76    //    You must provide a generator keyring to encrypt data.
77    //    You may provide additional child keyrings. Each child keyring will be able to
78    //        decrypt data encrypted with the multi-keyring on its own. It does not need
79    //        knowledge of any other child keyrings or the generator keyring to decrypt.
80
81    let multi_keyring = mpl
82        .create_multi_keyring()
83        .generator(aws_kms_mrk_multi_keyring)
84        .child_keyrings(vec![raw_aes_keyring.clone()])
85        .send()
86        .await?;
87
88    // 4. Configure which attributes are encrypted and/or signed when writing new items.
89    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
90    //    we must explicitly configure how they should be treated during item encryption:
91    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
92    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
93    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
94    let attribute_actions_on_encrypt = HashMap::from([
95        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
96        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
97        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
98    ]);
99
100    // 5. Configure which attributes we expect to be included in the signature
101    //    when reading items. There are two options for configuring this:
102    //
103    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
104    //      When defining your DynamoDb schema and deciding on attribute names,
105    //      choose a distinguishing prefix (such as ":") for all attributes that
106    //      you do not want to include in the signature.
107    //      This has two main benefits:
108    //      - It is easier to reason about the security and authenticity of data within your item
109    //        when all unauthenticated data is easily distinguishable by their attribute name.
110    //      - If you need to add new unauthenticated attributes in the future,
111    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
112    //        and immediately start writing to that new attribute, without
113    //        any other configuration update needed.
114    //      Once you configure this field, it is not safe to update it.
115    //
116    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
117    //      a set of attributes that should be considered unauthenticated when encountered
118    //      on read. Be careful if you use this configuration. Do not remove an attribute
119    //      name from this configuration, even if you are no longer writing with that attribute,
120    //      as old items may still include this attribute, and our configuration needs to know
121    //      to continue to exclude this attribute from the signature scope.
122    //      If you add new attribute names to this field, you must first deploy the update to this
123    //      field to all readers in your host fleet before deploying the update to start writing
124    //      with that new attribute.
125    //
126    //   For this example, we currently authenticate all attributes. To make it easier to
127    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
128    const UNSIGNED_ATTR_PREFIX: &str = ":";
129
130    // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
131    //    Note that this example creates one config/client combination for PUT, and another
132    //        for GET. The PUT config uses the multi-keyring, while the GET config uses the
133    //        raw AES keyring. This is solely done to demonstrate that a keyring included as
134    //        a child of a multi-keyring can be used to decrypt data on its own.
135    let table_config = DynamoDbTableEncryptionConfig::builder()
136        .logical_table_name(ddb_table_name)
137        .partition_key_name("partition_key")
138        .sort_key_name("sort_key")
139        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
140        .keyring(multi_keyring)
141        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
142        .build()?;
143
144    let table_configs = DynamoDbTablesEncryptionConfig::builder()
145        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
146        .build()?;
147
148    // 7. Create a new AWS SDK DynamoDb client using the config above
149    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
150    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
151        .interceptor(DbEsdkInterceptor::new(table_configs)?)
152        .build();
153    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
154
155    // 8. Put an item into our table using the above client.
156    //    Before the item gets sent to DynamoDb, it will be encrypted
157    //    client-side using the multi-keyring.
158    //    The item will be encrypted with all wrapping keys in the keyring,
159    //    so that it can be decrypted with any one of the keys.
160    let item = HashMap::from([
161        (
162            "partition_key".to_string(),
163            AttributeValue::S("multiKeyringItem".to_string()),
164        ),
165        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
166        (
167            "sensitive_data".to_string(),
168            AttributeValue::S("encrypt and sign me!".to_string()),
169        ),
170    ]);
171
172    ddb.put_item()
173        .table_name(ddb_table_name)
174        .set_item(Some(item.clone()))
175        .send()
176        .await?;
177
178    // 9. Get the item back from our table using the above client.
179    //     The client will decrypt the item client-side using the AWS KMS
180    //     keyring, and return back the original item.
181    //     Since the generator key is the first available key in the keyring,
182    //     that is the key that will be used to decrypt this item.
183    let key_to_get = HashMap::from([
184        (
185            "partition_key".to_string(),
186            AttributeValue::S("multiKeyringItem".to_string()),
187        ),
188        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
189    ]);
190
191    let resp = ddb
192        .get_item()
193        .table_name(ddb_table_name)
194        .set_key(Some(key_to_get.clone()))
195        .consistent_read(true)
196        .send()
197        .await?;
198
199    assert_eq!(resp.item, Some(item.clone()));
200
201    // 10. Create a new config and client with only the raw AES keyring to GET the item
202    //     This is the same setup as above, except the config uses the `rawAesKeyring`.
203    let only_aes_table_config = DynamoDbTableEncryptionConfig::builder()
204        .logical_table_name(ddb_table_name)
205        .partition_key_name("partition_key")
206        .sort_key_name("sort_key")
207        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
208        .keyring(raw_aes_keyring)
209        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
210        .build()?;
211
212    let only_aes_table_configs = DynamoDbTablesEncryptionConfig::builder()
213        .table_encryption_configs(HashMap::from([(
214            ddb_table_name.to_string(),
215            only_aes_table_config,
216        )]))
217        .build()?;
218
219    let only_aes_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
220        .interceptor(DbEsdkInterceptor::new(only_aes_table_configs)?)
221        .build();
222    let only_aes_ddb = aws_sdk_dynamodb::Client::from_conf(only_aes_dynamo_config);
223
224    // 11. Get the item back from our table using the client
225    //     configured with only the raw AES keyring.
226    //     The client will decrypt the item client-side using the raw
227    //     AES keyring, and return back the original item.
228    let resp = only_aes_ddb
229        .get_item()
230        .table_name(ddb_table_name)
231        .set_key(Some(key_to_get.clone()))
232        .consistent_read(true)
233        .send()
234        .await?;
235
236    assert_eq!(resp.item, Some(item.clone()));
237
238    println!("multi_keyring successful.");
239    Ok(())
240}
Source§

impl Client

Source

pub fn create_raw_aes_keyring(&self) -> CreateRawAesKeyringFluentBuilder

Examples found in repository?
examples/keyring/raw_aes_keyring.rs (line 49)
40pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
41    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
42    let aes_key_bytes = generate_aes_key_bytes();
43
44    // 1. Create the keyring.
45    //    The DynamoDb encryption client uses this to encrypt and decrypt items.
46    let mpl_config = MaterialProvidersConfig::builder().build()?;
47    let mpl = mpl_client::Client::from_conf(mpl_config)?;
48    let raw_aes_keyring = mpl
49        .create_raw_aes_keyring()
50        .key_name("my-aes-key-name")
51        .key_namespace("my-key-namespace")
52        .wrapping_key(aes_key_bytes)
53        .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16)
54        .send()
55        .await?;
56
57    // 2. Configure which attributes are encrypted and/or signed when writing new items.
58    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
59    //    we must explicitly configure how they should be treated during item encryption:
60    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
61    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
62    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
63    let attribute_actions_on_encrypt = HashMap::from([
64        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
65        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
66        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
67    ]);
68
69    // 3. Configure which attributes we expect to be included in the signature
70    //    when reading items. There are two options for configuring this:
71    //
72    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
73    //      When defining your DynamoDb schema and deciding on attribute names,
74    //      choose a distinguishing prefix (such as ":") for all attributes that
75    //      you do not want to include in the signature.
76    //      This has two main benefits:
77    //      - It is easier to reason about the security and authenticity of data within your item
78    //        when all unauthenticated data is easily distinguishable by their attribute name.
79    //      - If you need to add new unauthenticated attributes in the future,
80    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
81    //        and immediately start writing to that new attribute, without
82    //        any other configuration update needed.
83    //      Once you configure this field, it is not safe to update it.
84    //
85    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
86    //      a set of attributes that should be considered unauthenticated when encountered
87    //      on read. Be careful if you use this configuration. Do not remove an attribute
88    //      name from this configuration, even if you are no longer writing with that attribute,
89    //      as old items may still include this attribute, and our configuration needs to know
90    //      to continue to exclude this attribute from the signature scope.
91    //      If you add new attribute names to this field, you must first deploy the update to this
92    //      field to all readers in your host fleet before deploying the update to start writing
93    //      with that new attribute.
94    //
95    //   For this example, we currently authenticate all attributes. To make it easier to
96    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
97    const UNSIGNED_ATTR_PREFIX: &str = ":";
98
99    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
100    let table_config = DynamoDbTableEncryptionConfig::builder()
101        .logical_table_name(ddb_table_name)
102        .partition_key_name("partition_key")
103        .sort_key_name("sort_key")
104        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
105        .keyring(raw_aes_keyring)
106        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
107        .build()?;
108
109    let table_configs = DynamoDbTablesEncryptionConfig::builder()
110        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
111        .build()?;
112
113    // 5. Create a new AWS SDK DynamoDb client using the Config above
114    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
115    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
116        .interceptor(DbEsdkInterceptor::new(table_configs)?)
117        .build();
118    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
119
120    // 6. Put an item into our table using the above client.
121    //    Before the item gets sent to DynamoDb, it will be encrypted
122    //    client-side, according to our configuration.
123    let item = HashMap::from([
124        (
125            "partition_key".to_string(),
126            AttributeValue::S("rawAesKeyringItem".to_string()),
127        ),
128        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
129        (
130            "sensitive_data".to_string(),
131            AttributeValue::S("encrypt and sign me!".to_string()),
132        ),
133    ]);
134
135    ddb.put_item()
136        .table_name(ddb_table_name)
137        .set_item(Some(item.clone()))
138        .send()
139        .await?;
140
141    // 7. Get the item back from our table using the same client.
142    //    The client will decrypt the item client-side, and return
143    //    back the original item.
144    let key_to_get = HashMap::from([
145        (
146            "partition_key".to_string(),
147            AttributeValue::S("rawAesKeyringItem".to_string()),
148        ),
149        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
150    ]);
151
152    let resp = ddb
153        .get_item()
154        .table_name(ddb_table_name)
155        .set_key(Some(key_to_get))
156        // In this example we configure a strongly consistent read
157        // because we perform a read immediately after a write (for demonstrative purposes).
158        // By default, reads are only eventually consistent.
159        // Read our docs to determine which read consistency to use for your application:
160        // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html
161        .consistent_read(true)
162        .send()
163        .await?;
164
165    assert_eq!(resp.item, Some(item));
166
167    println!("raw_aes_keyring successful.");
168    Ok(())
169}
More examples
Hide additional examples
examples/keyring/multi_keyring.rs (line 55)
46pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
47    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
48    let key_arn = test_utils::TEST_KMS_KEY_ID;
49    let aes_key_bytes = generate_aes_key_bytes();
50
51    // 1. Create the raw AES keyring.
52    let mpl_config = MaterialProvidersConfig::builder().build()?;
53    let mpl = mpl_client::Client::from_conf(mpl_config)?;
54    let raw_aes_keyring = mpl
55        .create_raw_aes_keyring()
56        .key_name("my-aes-key-name")
57        .key_namespace("my-key-namespace")
58        .wrapping_key(aes_key_bytes)
59        .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16)
60        .send()
61        .await?;
62
63    // 2. Create the AWS KMS keyring.
64    //    We create a MRK multi keyring, as this interface also supports
65    //    single-region KMS keys (standard KMS keys),
66    //    and creates the KMS client for us automatically.
67    let aws_kms_mrk_multi_keyring = mpl
68        .create_aws_kms_mrk_multi_keyring()
69        .generator(key_arn)
70        .send()
71        .await?;
72
73    // 3. Create the multi-keyring.
74    //    We will label the AWS KMS keyring as the generator and the raw AES keyring as the
75    //        only child keyring.
76    //    You must provide a generator keyring to encrypt data.
77    //    You may provide additional child keyrings. Each child keyring will be able to
78    //        decrypt data encrypted with the multi-keyring on its own. It does not need
79    //        knowledge of any other child keyrings or the generator keyring to decrypt.
80
81    let multi_keyring = mpl
82        .create_multi_keyring()
83        .generator(aws_kms_mrk_multi_keyring)
84        .child_keyrings(vec![raw_aes_keyring.clone()])
85        .send()
86        .await?;
87
88    // 4. Configure which attributes are encrypted and/or signed when writing new items.
89    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
90    //    we must explicitly configure how they should be treated during item encryption:
91    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
92    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
93    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
94    let attribute_actions_on_encrypt = HashMap::from([
95        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
96        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
97        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
98    ]);
99
100    // 5. Configure which attributes we expect to be included in the signature
101    //    when reading items. There are two options for configuring this:
102    //
103    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
104    //      When defining your DynamoDb schema and deciding on attribute names,
105    //      choose a distinguishing prefix (such as ":") for all attributes that
106    //      you do not want to include in the signature.
107    //      This has two main benefits:
108    //      - It is easier to reason about the security and authenticity of data within your item
109    //        when all unauthenticated data is easily distinguishable by their attribute name.
110    //      - If you need to add new unauthenticated attributes in the future,
111    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
112    //        and immediately start writing to that new attribute, without
113    //        any other configuration update needed.
114    //      Once you configure this field, it is not safe to update it.
115    //
116    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
117    //      a set of attributes that should be considered unauthenticated when encountered
118    //      on read. Be careful if you use this configuration. Do not remove an attribute
119    //      name from this configuration, even if you are no longer writing with that attribute,
120    //      as old items may still include this attribute, and our configuration needs to know
121    //      to continue to exclude this attribute from the signature scope.
122    //      If you add new attribute names to this field, you must first deploy the update to this
123    //      field to all readers in your host fleet before deploying the update to start writing
124    //      with that new attribute.
125    //
126    //   For this example, we currently authenticate all attributes. To make it easier to
127    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
128    const UNSIGNED_ATTR_PREFIX: &str = ":";
129
130    // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
131    //    Note that this example creates one config/client combination for PUT, and another
132    //        for GET. The PUT config uses the multi-keyring, while the GET config uses the
133    //        raw AES keyring. This is solely done to demonstrate that a keyring included as
134    //        a child of a multi-keyring can be used to decrypt data on its own.
135    let table_config = DynamoDbTableEncryptionConfig::builder()
136        .logical_table_name(ddb_table_name)
137        .partition_key_name("partition_key")
138        .sort_key_name("sort_key")
139        .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone())
140        .keyring(multi_keyring)
141        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
142        .build()?;
143
144    let table_configs = DynamoDbTablesEncryptionConfig::builder()
145        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
146        .build()?;
147
148    // 7. Create a new AWS SDK DynamoDb client using the config above
149    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
150    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
151        .interceptor(DbEsdkInterceptor::new(table_configs)?)
152        .build();
153    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
154
155    // 8. Put an item into our table using the above client.
156    //    Before the item gets sent to DynamoDb, it will be encrypted
157    //    client-side using the multi-keyring.
158    //    The item will be encrypted with all wrapping keys in the keyring,
159    //    so that it can be decrypted with any one of the keys.
160    let item = HashMap::from([
161        (
162            "partition_key".to_string(),
163            AttributeValue::S("multiKeyringItem".to_string()),
164        ),
165        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
166        (
167            "sensitive_data".to_string(),
168            AttributeValue::S("encrypt and sign me!".to_string()),
169        ),
170    ]);
171
172    ddb.put_item()
173        .table_name(ddb_table_name)
174        .set_item(Some(item.clone()))
175        .send()
176        .await?;
177
178    // 9. Get the item back from our table using the above client.
179    //     The client will decrypt the item client-side using the AWS KMS
180    //     keyring, and return back the original item.
181    //     Since the generator key is the first available key in the keyring,
182    //     that is the key that will be used to decrypt this item.
183    let key_to_get = HashMap::from([
184        (
185            "partition_key".to_string(),
186            AttributeValue::S("multiKeyringItem".to_string()),
187        ),
188        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
189    ]);
190
191    let resp = ddb
192        .get_item()
193        .table_name(ddb_table_name)
194        .set_key(Some(key_to_get.clone()))
195        .consistent_read(true)
196        .send()
197        .await?;
198
199    assert_eq!(resp.item, Some(item.clone()));
200
201    // 10. Create a new config and client with only the raw AES keyring to GET the item
202    //     This is the same setup as above, except the config uses the `rawAesKeyring`.
203    let only_aes_table_config = DynamoDbTableEncryptionConfig::builder()
204        .logical_table_name(ddb_table_name)
205        .partition_key_name("partition_key")
206        .sort_key_name("sort_key")
207        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
208        .keyring(raw_aes_keyring)
209        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
210        .build()?;
211
212    let only_aes_table_configs = DynamoDbTablesEncryptionConfig::builder()
213        .table_encryption_configs(HashMap::from([(
214            ddb_table_name.to_string(),
215            only_aes_table_config,
216        )]))
217        .build()?;
218
219    let only_aes_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
220        .interceptor(DbEsdkInterceptor::new(only_aes_table_configs)?)
221        .build();
222    let only_aes_ddb = aws_sdk_dynamodb::Client::from_conf(only_aes_dynamo_config);
223
224    // 11. Get the item back from our table using the client
225    //     configured with only the raw AES keyring.
226    //     The client will decrypt the item client-side using the raw
227    //     AES keyring, and return back the original item.
228    let resp = only_aes_ddb
229        .get_item()
230        .table_name(ddb_table_name)
231        .set_key(Some(key_to_get.clone()))
232        .consistent_read(true)
233        .send()
234        .await?;
235
236    assert_eq!(resp.item, Some(item.clone()));
237
238    println!("multi_keyring successful.");
239    Ok(())
240}
Source§

impl Client

Source

pub fn create_raw_rsa_keyring(&self) -> CreateRawRsaKeyringFluentBuilder

Examples found in repository?
examples/keyring/raw_rsa_keyring.rs (line 86)
57pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
58    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
59
60    // You may provide your own RSA key pair in the files located at
61    //  - EXAMPLE_RSA_PRIVATE_KEY_FILENAME
62    //  - EXAMPLE_RSA_PUBLIC_KEY_FILENAME
63    // If these files are not present, this will generate a pair for you
64    if should_generate_new_rsa_key_pair()? {
65        generate_rsa_key_pair()?;
66    }
67
68    // 1. Load key pair from UTF-8 encoded PEM files.
69    //    You may provide your own PEM files to use here.
70    //    If you do not, the main method in this class will generate PEM
71    //    files for example use. Do not use these files for any other purpose.
72
73    let mut file = File::open(Path::new(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?;
74    let mut public_key_utf8_bytes = Vec::new();
75    file.read_to_end(&mut public_key_utf8_bytes)?;
76
77    let mut file = File::open(Path::new(EXAMPLE_RSA_PRIVATE_KEY_FILENAME))?;
78    let mut private_key_utf8_bytes = Vec::new();
79    file.read_to_end(&mut private_key_utf8_bytes)?;
80
81    // 2. Create the keyring.
82    //    The DynamoDb encryption client uses this to encrypt and decrypt items.
83    let mpl_config = MaterialProvidersConfig::builder().build()?;
84    let mpl = mpl_client::Client::from_conf(mpl_config)?;
85    let raw_rsa_keyring = mpl
86        .create_raw_rsa_keyring()
87        .key_name("my-rsa-key-name")
88        .key_namespace("my-key-namespace")
89        .padding_scheme(PaddingScheme::OaepSha256Mgf1)
90        .public_key(public_key_utf8_bytes)
91        .private_key(private_key_utf8_bytes)
92        .send()
93        .await?;
94
95    // 3. Configure which attributes are encrypted and/or signed when writing new items.
96    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
97    //    we must explicitly configure how they should be treated during item encryption:
98    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
99    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
100    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
101    let attribute_actions_on_encrypt = HashMap::from([
102        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
103        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
104        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
105    ]);
106
107    // 4. Configure which attributes we expect to be included in the signature
108    //    when reading items. There are two options for configuring this:
109    //
110    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
111    //      When defining your DynamoDb schema and deciding on attribute names,
112    //      choose a distinguishing prefix (such as ":") for all attributes that
113    //      you do not want to include in the signature.
114    //      This has two main benefits:
115    //      - It is easier to reason about the security and authenticity of data within your item
116    //        when all unauthenticated data is easily distinguishable by their attribute name.
117    //      - If you need to add new unauthenticated attributes in the future,
118    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
119    //        and immediately start writing to that new attribute, without
120    //        any other configuration update needed.
121    //      Once you configure this field, it is not safe to update it.
122    //
123    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
124    //      a set of attributes that should be considered unauthenticated when encountered
125    //      on read. Be careful if you use this configuration. Do not remove an attribute
126    //      name from this configuration, even if you are no longer writing with that attribute,
127    //      as old items may still include this attribute, and our configuration needs to know
128    //      to continue to exclude this attribute from the signature scope.
129    //      If you add new attribute names to this field, you must first deploy the update to this
130    //      field to all readers in your host fleet before deploying the update to start writing
131    //      with that new attribute.
132    //
133    //   For this example, we currently authenticate all attributes. To make it easier to
134    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
135    const UNSIGNED_ATTR_PREFIX: &str = ":";
136
137    // 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
138    let table_config = DynamoDbTableEncryptionConfig::builder()
139        .logical_table_name(ddb_table_name)
140        .partition_key_name("partition_key")
141        .sort_key_name("sort_key")
142        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
143        .keyring(raw_rsa_keyring)
144        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
145        .build()?;
146
147    let table_configs = DynamoDbTablesEncryptionConfig::builder()
148        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
149        .build()?;
150
151    // 6. Create a new AWS SDK DynamoDb client using the config above
152    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
153    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
154        .interceptor(DbEsdkInterceptor::new(table_configs)?)
155        .build();
156    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
157
158    // 7. Put an item into our table using the above client.
159    //    Before the item gets sent to DynamoDb, it will be encrypted
160    //    client-side, according to our configuration.
161    let item = HashMap::from([
162        (
163            "partition_key".to_string(),
164            AttributeValue::S("rawRsaKeyringItem".to_string()),
165        ),
166        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
167        (
168            "sensitive_data".to_string(),
169            AttributeValue::S("encrypt and sign me!".to_string()),
170        ),
171    ]);
172
173    ddb.put_item()
174        .table_name(ddb_table_name)
175        .set_item(Some(item.clone()))
176        .send()
177        .await?;
178
179    // 8. Get the item back from our table using the same client.
180    //    The client will decrypt the item client-side, and return
181    //    back the original item.
182
183    let key_to_get = HashMap::from([
184        (
185            "partition_key".to_string(),
186            AttributeValue::S("rawRsaKeyringItem".to_string()),
187        ),
188        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
189    ]);
190
191    let resp = ddb
192        .get_item()
193        .table_name(ddb_table_name)
194        .set_key(Some(key_to_get))
195        // In this example we configure a strongly consistent read
196        // because we perform a read immediately after a write (for demonstrative purposes).
197        // By default, reads are only eventually consistent.
198        // Read our docs to determine which read consistency to use for your application:
199        // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html
200        .consistent_read(true)
201        .send()
202        .await?;
203
204    assert_eq!(resp.item, Some(item));
205    println!("raw_rsa_keyring successful.");
206    Ok(())
207}
Source§

impl Client

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source

pub fn create_default_client_supplier( &self, ) -> CreateDefaultClientSupplierFluentBuilder

Constructs a fluent builder for the CreateDefaultClientSupplier operation.

Source§

impl Client

Source

pub fn initialize_encryption_materials( &self, ) -> InitializeEncryptionMaterialsFluentBuilder

Constructs a fluent builder for the InitializeEncryptionMaterials operation.

Source§

impl Client

Source

pub fn initialize_decryption_materials( &self, ) -> InitializeDecryptionMaterialsFluentBuilder

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source

pub fn encryption_materials_has_plaintext_data_key( &self, ) -> EncryptionMaterialsHasPlaintextDataKeyFluentBuilder

Constructs a fluent builder for the EncryptionMaterialsHasPlaintextDataKey operation.

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source

pub fn valid_algorithm_suite_info(&self) -> ValidAlgorithmSuiteInfoFluentBuilder

Constructs a fluent builder for the ValidAlgorithmSuiteInfo operation.

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source

pub fn from_conf(input: MaterialProvidersConfig) -> Result<Self, Error>

Creates a new client from the service Config.

Examples found in repository?
examples/keyring/raw_aes_keyring.rs (line 47)
40pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
41    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
42    let aes_key_bytes = generate_aes_key_bytes();
43
44    // 1. Create the keyring.
45    //    The DynamoDb encryption client uses this to encrypt and decrypt items.
46    let mpl_config = MaterialProvidersConfig::builder().build()?;
47    let mpl = mpl_client::Client::from_conf(mpl_config)?;
48    let raw_aes_keyring = mpl
49        .create_raw_aes_keyring()
50        .key_name("my-aes-key-name")
51        .key_namespace("my-key-namespace")
52        .wrapping_key(aes_key_bytes)
53        .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16)
54        .send()
55        .await?;
56
57    // 2. Configure which attributes are encrypted and/or signed when writing new items.
58    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
59    //    we must explicitly configure how they should be treated during item encryption:
60    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
61    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
62    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
63    let attribute_actions_on_encrypt = HashMap::from([
64        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
65        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
66        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
67    ]);
68
69    // 3. Configure which attributes we expect to be included in the signature
70    //    when reading items. There are two options for configuring this:
71    //
72    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
73    //      When defining your DynamoDb schema and deciding on attribute names,
74    //      choose a distinguishing prefix (such as ":") for all attributes that
75    //      you do not want to include in the signature.
76    //      This has two main benefits:
77    //      - It is easier to reason about the security and authenticity of data within your item
78    //        when all unauthenticated data is easily distinguishable by their attribute name.
79    //      - If you need to add new unauthenticated attributes in the future,
80    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
81    //        and immediately start writing to that new attribute, without
82    //        any other configuration update needed.
83    //      Once you configure this field, it is not safe to update it.
84    //
85    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
86    //      a set of attributes that should be considered unauthenticated when encountered
87    //      on read. Be careful if you use this configuration. Do not remove an attribute
88    //      name from this configuration, even if you are no longer writing with that attribute,
89    //      as old items may still include this attribute, and our configuration needs to know
90    //      to continue to exclude this attribute from the signature scope.
91    //      If you add new attribute names to this field, you must first deploy the update to this
92    //      field to all readers in your host fleet before deploying the update to start writing
93    //      with that new attribute.
94    //
95    //   For this example, we currently authenticate all attributes. To make it easier to
96    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
97    const UNSIGNED_ATTR_PREFIX: &str = ":";
98
99    // 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
100    let table_config = DynamoDbTableEncryptionConfig::builder()
101        .logical_table_name(ddb_table_name)
102        .partition_key_name("partition_key")
103        .sort_key_name("sort_key")
104        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
105        .keyring(raw_aes_keyring)
106        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
107        .build()?;
108
109    let table_configs = DynamoDbTablesEncryptionConfig::builder()
110        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
111        .build()?;
112
113    // 5. Create a new AWS SDK DynamoDb client using the Config above
114    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
115    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
116        .interceptor(DbEsdkInterceptor::new(table_configs)?)
117        .build();
118    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
119
120    // 6. Put an item into our table using the above client.
121    //    Before the item gets sent to DynamoDb, it will be encrypted
122    //    client-side, according to our configuration.
123    let item = HashMap::from([
124        (
125            "partition_key".to_string(),
126            AttributeValue::S("rawAesKeyringItem".to_string()),
127        ),
128        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
129        (
130            "sensitive_data".to_string(),
131            AttributeValue::S("encrypt and sign me!".to_string()),
132        ),
133    ]);
134
135    ddb.put_item()
136        .table_name(ddb_table_name)
137        .set_item(Some(item.clone()))
138        .send()
139        .await?;
140
141    // 7. Get the item back from our table using the same client.
142    //    The client will decrypt the item client-side, and return
143    //    back the original item.
144    let key_to_get = HashMap::from([
145        (
146            "partition_key".to_string(),
147            AttributeValue::S("rawAesKeyringItem".to_string()),
148        ),
149        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
150    ]);
151
152    let resp = ddb
153        .get_item()
154        .table_name(ddb_table_name)
155        .set_key(Some(key_to_get))
156        // In this example we configure a strongly consistent read
157        // because we perform a read immediately after a write (for demonstrative purposes).
158        // By default, reads are only eventually consistent.
159        // Read our docs to determine which read consistency to use for your application:
160        // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html
161        .consistent_read(true)
162        .send()
163        .await?;
164
165    assert_eq!(resp.item, Some(item));
166
167    println!("raw_aes_keyring successful.");
168    Ok(())
169}
More examples
Hide additional examples
examples/itemencryptor/item_encrypt_decrypt.rs (line 43)
34pub async fn encrypt_decrypt() -> Result<(), crate::BoxError> {
35    let kms_key_id = test_utils::TEST_KMS_KEY_ID;
36    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
37
38    // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
39    //    For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
40    //    We will use the `CreateMrkMultiKeyring` method to create this keyring,
41    //    as it will correctly handle both single region and Multi-Region KMS Keys.
42    let provider_config = MaterialProvidersConfig::builder().build()?;
43    let mat_prov = mpl_client::Client::from_conf(provider_config)?;
44    let kms_keyring = mat_prov
45        .create_aws_kms_mrk_multi_keyring()
46        .generator(kms_key_id)
47        .send()
48        .await?;
49
50    // 2. Configure which attributes are encrypted and/or signed when writing new items.
51    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
52    //    we must explicitly configure how they should be treated during item encryption:
53    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
54    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
55    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
56    let attribute_actions_on_encrypt = HashMap::from([
57        ("partition_key".to_string(), CryptoAction::SignOnly),
58        ("sort_key".to_string(), CryptoAction::SignOnly),
59        ("attribute1".to_string(), CryptoAction::EncryptAndSign),
60        ("attribute2".to_string(), CryptoAction::SignOnly),
61        (":attribute3".to_string(), CryptoAction::DoNothing),
62    ]);
63
64    // 3. Configure which attributes we expect to be included in the signature
65    //    when reading items. There are two options for configuring this:
66    //
67    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
68    //      When defining your DynamoDb schema and deciding on attribute names,
69    //      choose a distinguishing prefix (such as ":") for all attributes that
70    //      you do not want to include in the signature.
71    //      This has two main benefits:
72    //      - It is easier to reason about the security and authenticity of data within your item
73    //        when all unauthenticated data is easily distinguishable by their attribute name.
74    //      - If you need to add new unauthenticated attributes in the future,
75    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
76    //        and immediately start writing to that new attribute, without
77    //        any other configuration update needed.
78    //      Once you configure this field, it is not safe to update it.
79    //
80    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
81    //      a set of attributes that should be considered unauthenticated when encountered
82    //      on read. Be careful if you use this configuration. Do not remove an attribute
83    //      name from this configuration, even if you are no longer writing with that attribute,
84    //      as old items may still include this attribute, and our configuration needs to know
85    //      to continue to exclude this attribute from the signature scope.
86    //      If you add new attribute names to this field, you must first deploy the update to this
87    //      field to all readers in your host fleet before deploying the update to start writing
88    //      with that new attribute.
89    //
90    //   For this example, we have designed our DynamoDb table such that any attribute name with
91    //   the ":" prefix should be considered unauthenticated.
92    const UNSIGNED_ATTR_PREFIX: &str = ":";
93
94    // 4. Create the configuration for the DynamoDb Item Encryptor
95    let config = DynamoDbItemEncryptorConfig::builder()
96        .logical_table_name(ddb_table_name)
97        .partition_key_name("partition_key")
98        .sort_key_name("sort_key")
99        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
100        .keyring(kms_keyring)
101        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
102        // Specifying an algorithm suite is not required,
103        // but is done here to demonstrate how to do so.
104        // We suggest using the
105        // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
106        // which includes AES-GCM with key derivation, signing, and key commitment.
107        // This is also the default algorithm suite if one is not specified in this config.
108        // For more information on supported algorithm suites, see:
109        //   https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
110        .algorithm_suite_id(
111            DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384,
112        )
113        .build()?;
114
115    // 5. Create the DynamoDb Item Encryptor
116    let item_encryptor = enc_client::Client::from_conf(config)?;
117
118    // 6. Directly encrypt a DynamoDb item using the DynamoDb Item Encryptor
119    let original_item = HashMap::from([
120        (
121            "partition_key".to_string(),
122            AttributeValue::S("ItemEncryptDecryptExample".to_string()),
123        ),
124        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
125        (
126            "attribute1".to_string(),
127            AttributeValue::S("encrypt and sign me!".to_string()),
128        ),
129        (
130            "attribute2".to_string(),
131            AttributeValue::S("sign me!".to_string()),
132        ),
133        (
134            ":attribute3".to_string(),
135            AttributeValue::S("ignore me!".to_string()),
136        ),
137    ]);
138
139    let encrypted_item = item_encryptor
140        .encrypt_item()
141        .plaintext_item(original_item.clone())
142        .send()
143        .await?
144        .encrypted_item
145        .unwrap();
146
147    // Demonstrate that the item has been encrypted
148    assert_eq!(
149        encrypted_item["partition_key"],
150        AttributeValue::S("ItemEncryptDecryptExample".to_string())
151    );
152    assert_eq!(
153        encrypted_item["sort_key"],
154        AttributeValue::N("0".to_string())
155    );
156    assert!(encrypted_item["attribute1"].is_b());
157    assert!(!encrypted_item["attribute1"].is_s());
158
159    // 7. Directly decrypt the encrypted item using the DynamoDb Item Encryptor
160    let decrypted_item = item_encryptor
161        .decrypt_item()
162        .encrypted_item(encrypted_item)
163        .send()
164        .await?
165        .plaintext_item
166        .unwrap();
167
168    // Demonstrate that GetItem succeeded and returned the decrypted item
169    assert_eq!(decrypted_item, original_item);
170    println!("encrypt_decrypt successful.");
171    Ok(())
172}
examples/keyring/raw_rsa_keyring.rs (line 84)
57pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
58    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
59
60    // You may provide your own RSA key pair in the files located at
61    //  - EXAMPLE_RSA_PRIVATE_KEY_FILENAME
62    //  - EXAMPLE_RSA_PUBLIC_KEY_FILENAME
63    // If these files are not present, this will generate a pair for you
64    if should_generate_new_rsa_key_pair()? {
65        generate_rsa_key_pair()?;
66    }
67
68    // 1. Load key pair from UTF-8 encoded PEM files.
69    //    You may provide your own PEM files to use here.
70    //    If you do not, the main method in this class will generate PEM
71    //    files for example use. Do not use these files for any other purpose.
72
73    let mut file = File::open(Path::new(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?;
74    let mut public_key_utf8_bytes = Vec::new();
75    file.read_to_end(&mut public_key_utf8_bytes)?;
76
77    let mut file = File::open(Path::new(EXAMPLE_RSA_PRIVATE_KEY_FILENAME))?;
78    let mut private_key_utf8_bytes = Vec::new();
79    file.read_to_end(&mut private_key_utf8_bytes)?;
80
81    // 2. Create the keyring.
82    //    The DynamoDb encryption client uses this to encrypt and decrypt items.
83    let mpl_config = MaterialProvidersConfig::builder().build()?;
84    let mpl = mpl_client::Client::from_conf(mpl_config)?;
85    let raw_rsa_keyring = mpl
86        .create_raw_rsa_keyring()
87        .key_name("my-rsa-key-name")
88        .key_namespace("my-key-namespace")
89        .padding_scheme(PaddingScheme::OaepSha256Mgf1)
90        .public_key(public_key_utf8_bytes)
91        .private_key(private_key_utf8_bytes)
92        .send()
93        .await?;
94
95    // 3. Configure which attributes are encrypted and/or signed when writing new items.
96    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
97    //    we must explicitly configure how they should be treated during item encryption:
98    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
99    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
100    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
101    let attribute_actions_on_encrypt = HashMap::from([
102        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
103        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
104        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
105    ]);
106
107    // 4. Configure which attributes we expect to be included in the signature
108    //    when reading items. There are two options for configuring this:
109    //
110    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
111    //      When defining your DynamoDb schema and deciding on attribute names,
112    //      choose a distinguishing prefix (such as ":") for all attributes that
113    //      you do not want to include in the signature.
114    //      This has two main benefits:
115    //      - It is easier to reason about the security and authenticity of data within your item
116    //        when all unauthenticated data is easily distinguishable by their attribute name.
117    //      - If you need to add new unauthenticated attributes in the future,
118    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
119    //        and immediately start writing to that new attribute, without
120    //        any other configuration update needed.
121    //      Once you configure this field, it is not safe to update it.
122    //
123    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
124    //      a set of attributes that should be considered unauthenticated when encountered
125    //      on read. Be careful if you use this configuration. Do not remove an attribute
126    //      name from this configuration, even if you are no longer writing with that attribute,
127    //      as old items may still include this attribute, and our configuration needs to know
128    //      to continue to exclude this attribute from the signature scope.
129    //      If you add new attribute names to this field, you must first deploy the update to this
130    //      field to all readers in your host fleet before deploying the update to start writing
131    //      with that new attribute.
132    //
133    //   For this example, we currently authenticate all attributes. To make it easier to
134    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
135    const UNSIGNED_ATTR_PREFIX: &str = ":";
136
137    // 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
138    let table_config = DynamoDbTableEncryptionConfig::builder()
139        .logical_table_name(ddb_table_name)
140        .partition_key_name("partition_key")
141        .sort_key_name("sort_key")
142        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
143        .keyring(raw_rsa_keyring)
144        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
145        .build()?;
146
147    let table_configs = DynamoDbTablesEncryptionConfig::builder()
148        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
149        .build()?;
150
151    // 6. Create a new AWS SDK DynamoDb client using the config above
152    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
153    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
154        .interceptor(DbEsdkInterceptor::new(table_configs)?)
155        .build();
156    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
157
158    // 7. Put an item into our table using the above client.
159    //    Before the item gets sent to DynamoDb, it will be encrypted
160    //    client-side, according to our configuration.
161    let item = HashMap::from([
162        (
163            "partition_key".to_string(),
164            AttributeValue::S("rawRsaKeyringItem".to_string()),
165        ),
166        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
167        (
168            "sensitive_data".to_string(),
169            AttributeValue::S("encrypt and sign me!".to_string()),
170        ),
171    ]);
172
173    ddb.put_item()
174        .table_name(ddb_table_name)
175        .set_item(Some(item.clone()))
176        .send()
177        .await?;
178
179    // 8. Get the item back from our table using the same client.
180    //    The client will decrypt the item client-side, and return
181    //    back the original item.
182
183    let key_to_get = HashMap::from([
184        (
185            "partition_key".to_string(),
186            AttributeValue::S("rawRsaKeyringItem".to_string()),
187        ),
188        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
189    ]);
190
191    let resp = ddb
192        .get_item()
193        .table_name(ddb_table_name)
194        .set_key(Some(key_to_get))
195        // In this example we configure a strongly consistent read
196        // because we perform a read immediately after a write (for demonstrative purposes).
197        // By default, reads are only eventually consistent.
198        // Read our docs to determine which read consistency to use for your application:
199        // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html
200        .consistent_read(true)
201        .send()
202        .await?;
203
204    assert_eq!(resp.item, Some(item));
205    println!("raw_rsa_keyring successful.");
206    Ok(())
207}
examples/basic_get_put_example.rs (line 40)
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}
examples/keyring/kms_rsa_keyring.rs (line 79)
47pub async fn put_item_get_item() -> Result<(), crate::BoxError> {
48    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
49    let rsa_key_arn = test_utils::TEST_KMS_RSA_KEY_ID;
50
51    // You may provide your own RSA public key at EXAMPLE_RSA_PUBLIC_KEY_FILENAME.
52    // This must be the public key for the RSA key represented at rsaKeyArn.
53    // If this file is not present, this will write a UTF-8 encoded PEM file for you.
54    if should_get_new_public_key(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME) {
55        write_public_key_pem_for_rsa_key(
56            test_utils::TEST_KMS_RSA_KEY_ID,
57            DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME,
58        )
59        .await?;
60    }
61
62    // 1. Load UTF-8 encoded public key PEM file.
63    //    You may have an RSA public key file already defined.
64    //    If not, the main method in this class will call
65    //    the KMS RSA key, retrieve its public key, and store it
66    //    in a PEM file for example use.
67    let mut file = File::open(Path::new(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?;
68    let mut public_key_utf8_bytes = Vec::new();
69    file.read_to_end(&mut public_key_utf8_bytes)?;
70
71    // 2. Create a KMS RSA keyring.
72    //    This keyring takes in:
73    //     - kmsClient
74    //     - kmsKeyId: Must be an ARN representing a KMS RSA key
75    //     - publicKey: A ByteBuffer of a UTF-8 encoded PEM file representing the public
76    //                  key for the key passed into kmsKeyId
77    //     - encryptionAlgorithm: Must be either RSAES_OAEP_SHA_256 or RSAES_OAEP_SHA_1
78    let mpl_config = MaterialProvidersConfig::builder().build()?;
79    let mpl = mpl_client::Client::from_conf(mpl_config)?;
80    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
81    let kms_rsa_keyring = mpl
82        .create_aws_kms_rsa_keyring()
83        .kms_key_id(rsa_key_arn)
84        .public_key(public_key_utf8_bytes)
85        .encryption_algorithm(aws_sdk_kms::types::EncryptionAlgorithmSpec::RsaesOaepSha256)
86        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
87        .send()
88        .await?;
89
90    // 3. Configure which attributes are encrypted and/or signed when writing new items.
91    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
92    //    we must explicitly configure how they should be treated during item encryption:
93    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
94    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
95    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
96    let attribute_actions_on_encrypt = HashMap::from([
97        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
98        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
99        ("sensitive_data".to_string(), CryptoAction::EncryptAndSign),
100    ]);
101
102    // 4. Configure which attributes we expect to be included in the signature
103    //    when reading items. There are two options for configuring this:
104    //
105    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
106    //      When defining your DynamoDb schema and deciding on attribute names,
107    //      choose a distinguishing prefix (such as ":") for all attributes that
108    //      you do not want to include in the signature.
109    //      This has two main benefits:
110    //      - It is easier to reason about the security and authenticity of data within your item
111    //        when all unauthenticated data is easily distinguishable by their attribute name.
112    //      - If you need to add new unauthenticated attributes in the future,
113    //        you can easily make the corresponding update to your `attributeActions`
114    //        and immediately start writing to that new attribute, without
115    //        any other configuration update needed.
116    //      Once you configure this field, it is not safe to update it.
117    //
118    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
119    //      a set of attributes that should be considered unauthenticated when encountered
120    //      on read. Be careful if you use this configuration. Do not remove an attribute
121    //      name from this configuration, even if you are no longer writing with that attribute,
122    //      as old items may still include this attribute, and our configuration needs to know
123    //      to continue to exclude this attribute from the signature scope.
124    //      If you add new attribute names to this field, you must first deploy the update to this
125    //      field to all readers in your host fleet before deploying the update to start writing
126    //      with that new attribute.
127    //
128    //   For this example, we currently authenticate all attributes. To make it easier to
129    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
130    const UNSIGNED_ATTR_PREFIX: &str = ":";
131
132    // 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
133    //    Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite
134    //    that does not use asymmetric signing.
135    let table_config = DynamoDbTableEncryptionConfig::builder()
136        .logical_table_name(ddb_table_name)
137        .partition_key_name("partition_key")
138        .sort_key_name("sort_key")
139        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
140        .keyring(kms_rsa_keyring)
141        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
142        // Specify algorithmSuite without asymmetric signing here
143        // As of v3.0.0, the only supported algorithmSuite without asymmetric signing is
144        // ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384.
145        .algorithm_suite_id(DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeySymsigHmacSha384)
146        .build()?;
147
148    let table_configs = DynamoDbTablesEncryptionConfig::builder()
149        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
150        .build()?;
151
152    // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
153    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
154        .interceptor(DbEsdkInterceptor::new(table_configs)?)
155        .build();
156    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
157
158    // 7. Put an item into our table using the above client.
159    //    Before the item gets sent to DynamoDb, it will be encrypted
160    //    client-side, according to our configuration.
161    let item = HashMap::from([
162        (
163            "partition_key".to_string(),
164            AttributeValue::S("awsKmsRsaKeyringItem".to_string()),
165        ),
166        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
167        (
168            "sensitive_data".to_string(),
169            AttributeValue::S("encrypt and sign me!".to_string()),
170        ),
171    ]);
172
173    ddb.put_item()
174        .table_name(ddb_table_name)
175        .set_item(Some(item.clone()))
176        .send()
177        .await?;
178
179    // 8. Get the item back from our table using the client.
180    //    The client will decrypt the item client-side using the RSA keyring
181    //    and return the original item.
182    let key_to_get = HashMap::from([
183        (
184            "partition_key".to_string(),
185            AttributeValue::S("awsKmsRsaKeyringItem".to_string()),
186        ),
187        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
188    ]);
189
190    let resp = ddb
191        .get_item()
192        .table_name(ddb_table_name)
193        .set_key(Some(key_to_get))
194        .consistent_read(true)
195        .send()
196        .await?;
197
198    assert_eq!(resp.item, Some(item));
199    println!("kms_rsa_keyring successful.");
200    Ok(())
201}
examples/keyring/hierarchical_keyring.rs (line 112)
63pub async fn put_item_get_item(
64    tenant1_branch_key_id: &str,
65    tenant2_branch_key_id: &str,
66) -> Result<(), crate::BoxError> {
67    let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME;
68
69    let keystore_table_name = test_utils::TEST_KEYSTORE_NAME;
70    let logical_keystore_name = test_utils::TEST_LOGICAL_KEYSTORE_NAME;
71    let kms_key_id = test_utils::TEST_KEYSTORE_KMS_KEY_ID;
72
73    // Initial KeyStore Setup: This example requires that you have already
74    // created your KeyStore, and have populated it with two new branch keys.
75    // See the "Create KeyStore Table Example" and "Create KeyStore Key Example"
76    // for an example of how to do this.
77
78    // 1. Configure your KeyStore resource.
79    //    This SHOULD be the same configuration that you used
80    //    to initially create and populate your KeyStore.
81    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
82    let key_store_config = KeyStoreConfig::builder()
83        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
84        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
85        .ddb_table_name(keystore_table_name)
86        .logical_key_store_name(logical_keystore_name)
87        .kms_configuration(KmsConfiguration::KmsKeyArn(kms_key_id.to_string()))
88        .build()?;
89
90    let key_store = keystore_client::Client::from_conf(key_store_config)?;
91
92    // 2. Create a Branch Key ID Supplier. See ExampleBranchKeyIdSupplier in this directory.
93    let dbesdk_config = DynamoDbEncryptionConfig::builder().build()?;
94    let dbesdk = dbesdk_client::Client::from_conf(dbesdk_config)?;
95    let supplier = ExampleBranchKeyIdSupplier::new(tenant1_branch_key_id, tenant2_branch_key_id);
96
97    let branch_key_id_supplier = dbesdk
98        .create_dynamo_db_encryption_branch_key_id_supplier()
99        .ddb_key_branch_key_id_supplier(supplier)
100        .send()
101        .await?
102        .branch_key_id_supplier
103        .unwrap();
104
105    // 3. Create the Hierarchical Keyring, using the Branch Key ID Supplier above.
106    //    With this configuration, the AWS SDK Client ultimately configured will be capable
107    //    of encrypting or decrypting items for either tenant (assuming correct KMS access).
108    //    If you want to restrict the client to only encrypt or decrypt for a single tenant,
109    //    configure this Hierarchical Keyring using `.branchKeyId(tenant1BranchKeyId)` instead
110    //    of `.branchKeyIdSupplier(branchKeyIdSupplier)`.
111    let mpl_config = MaterialProvidersConfig::builder().build()?;
112    let mpl = mpl_client::Client::from_conf(mpl_config)?;
113
114    let hierarchical_keyring = mpl
115        .create_aws_kms_hierarchical_keyring()
116        .branch_key_id_supplier(branch_key_id_supplier)
117        .key_store(key_store)
118        .ttl_seconds(600)
119        .send()
120        .await?;
121
122    // 4. Configure which attributes are encrypted and/or signed when writing new items.
123    //    For each attribute that may exist on the items we plan to write to our DynamoDbTable,
124    //    we must explicitly configure how they should be treated during item encryption:
125    //      - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
126    //      - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
127    //      - DO_NOTHING: The attribute is not encrypted and not included in the signature
128    let attribute_actions_on_encrypt = HashMap::from([
129        ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY
130        ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY
131        (
132            "tenant_sensitive_data".to_string(),
133            CryptoAction::EncryptAndSign,
134        ),
135    ]);
136
137    // 5. Configure which attributes we expect to be included in the signature
138    //    when reading items. There are two options for configuring this:
139    //
140    //    - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
141    //      When defining your DynamoDb schema and deciding on attribute names,
142    //      choose a distinguishing prefix (such as ":") for all attributes that
143    //      you do not want to include in the signature.
144    //      This has two main benefits:
145    //      - It is easier to reason about the security and authenticity of data within your item
146    //        when all unauthenticated data is easily distinguishable by their attribute name.
147    //      - If you need to add new unauthenticated attributes in the future,
148    //        you can easily make the corresponding update to your `attributeActionsOnEncrypt`
149    //        and immediately start writing to that new attribute, without
150    //        any other configuration update needed.
151    //      Once you configure this field, it is not safe to update it.
152    //
153    //    - Configure `allowedUnsignedAttributes`: You may also explicitly list
154    //      a set of attributes that should be considered unauthenticated when encountered
155    //      on read. Be careful if you use this configuration. Do not remove an attribute
156    //      name from this configuration, even if you are no longer writing with that attribute,
157    //      as old items may still include this attribute, and our configuration needs to know
158    //      to continue to exclude this attribute from the signature scope.
159    //      If you add new attribute names to this field, you must first deploy the update to this
160    //      field to all readers in your host fleet before deploying the update to start writing
161    //      with that new attribute.
162    //
163    //   For this example, we currently authenticate all attributes. To make it easier to
164    //   add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
165    const UNSIGNED_ATTR_PREFIX: &str = ":";
166
167    // 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
168    let table_config = DynamoDbTableEncryptionConfig::builder()
169        .logical_table_name(ddb_table_name)
170        .partition_key_name("partition_key")
171        .sort_key_name("sort_key")
172        .attribute_actions_on_encrypt(attribute_actions_on_encrypt)
173        .keyring(hierarchical_keyring)
174        .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX)
175        .build()?;
176
177    let table_configs = DynamoDbTablesEncryptionConfig::builder()
178        .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)]))
179        .build()?;
180
181    // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
182    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
183    let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
184        .interceptor(DbEsdkInterceptor::new(table_configs)?)
185        .build();
186    let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
187
188    // 8. Put an item into our table using the above client.
189    //    Before the item gets sent to DynamoDb, it will be encrypted
190    //    client-side, according to our configuration.
191    //    Because the item we are writing uses "tenantId1" as our partition value,
192    //    based on the code we wrote in the ExampleBranchKeySupplier,
193    //    `tenant1BranchKeyId` will be used to encrypt this item.
194    let item = HashMap::from([
195        (
196            "partition_key".to_string(),
197            AttributeValue::S("tenant1Id".to_string()),
198        ),
199        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
200        (
201            "tenant_sensitive_data".to_string(),
202            AttributeValue::S("encrypt and sign me!".to_string()),
203        ),
204    ]);
205
206    ddb.put_item()
207        .table_name(ddb_table_name)
208        .set_item(Some(item.clone()))
209        .send()
210        .await?;
211
212    // 9. Get the item back from our table using the same client.
213    //     The client will decrypt the item client-side, and return
214    //     back the original item.
215    //     Because the returned item's partition value is "tenantId1",
216    //     based on the code we wrote in the ExampleBranchKeySupplier,
217    //     `tenant1BranchKeyId` will be used to decrypt this item.
218    let key_to_get = HashMap::from([
219        (
220            "partition_key".to_string(),
221            AttributeValue::S("tenant1Id".to_string()),
222        ),
223        ("sort_key".to_string(), AttributeValue::N("0".to_string())),
224    ]);
225
226    let resp = ddb
227        .get_item()
228        .table_name(ddb_table_name)
229        .set_key(Some(key_to_get))
230        .consistent_read(true)
231        .send()
232        .await?;
233
234    assert_eq!(resp.item, Some(item));
235    println!("hierarchical_keyring successful.");
236    Ok(())
237}

Trait Implementations§

Source§

impl Clone for Client

Source§

fn clone(&self) -> Client

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Client

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Client

Source§

fn eq(&self, other: &Client) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl StructuralPartialEq for Client

Auto Trait Implementations§

§

impl Freeze for Client

§

impl !RefUnwindSafe for Client

§

impl Send for Client

§

impl Sync for Client

§

impl Unpin for Client

§

impl !UnwindSafe for Client

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AnyRef for T
where T: 'static,

Source§

fn as_any_ref(&self) -> &(dyn Any + 'static)

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T
where T: ?Sized,

Source§

fn upcast(&self) -> Ptr<T>

Source§

impl<T> UpcastObject<T> for T
where T: ?Sized,

Source§

fn upcast(&self) -> Object<T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,