Struct SearchConfig

Source
#[non_exhaustive]
pub struct SearchConfig { pub versions: Option<Vec<BeaconVersion>>, pub write_version: Option<i32>, }
Expand description

The configuration for searchable encryption.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§versions: Option<Vec<BeaconVersion>>

The versions of searchable encryption to support reading. Currently must contain a single configuration with version ‘1’.

§write_version: Option<i32>

The searchable encryption version to use when writing new items. Must be ‘1’.

Implementations§

Source§

impl SearchConfig

Source

pub fn versions(&self) -> &Option<Vec<BeaconVersion>>

The versions of searchable encryption to support reading. Currently must contain a single configuration with version ‘1’.

Source

pub fn write_version(&self) -> &Option<i32>

The searchable encryption version to use when writing new items. Must be ‘1’.

Source§

impl SearchConfig

Source

pub fn builder() -> SearchConfigBuilder

Creates a new builder-style object to manufacture SearchConfig.

Examples found in repository?
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 205)
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}
More examples
Hide additional examples
examples/searchableencryption/beacon_styles_searchable_encryption.rs (line 192)
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 291)
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 240)
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 536)
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}

Trait Implementations§

Source§

impl Clone for SearchConfig

Source§

fn clone(&self) -> SearchConfig

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 SearchConfig

Source§

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

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

impl PartialEq for SearchConfig

Source§

fn eq(&self, other: &SearchConfig) -> 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 SearchConfig

Auto Trait Implementations§

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,