Struct StandardBeacon

Source
#[non_exhaustive]
pub struct StandardBeacon { pub length: Option<i32>, pub loc: Option<String>, pub name: Option<String>, pub style: Option<BeaconStyle>, }
Expand description

The configuration for a Standard Beacon.

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.
§length: Option<i32>

The length of the calculated beacon.

§loc: Option<String>

The DynamoDB document path to the value this beacon will calculate over. If not specified, the beacon will calculate values for the attribute with the name specified in ‘name’.

§name: Option<String>

The name for this Standard Beacon.

§style: Option<BeaconStyle>

Optional augmented behavior.

Implementations§

Source§

impl StandardBeacon

Source

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

The length of the calculated beacon.

Source

pub fn loc(&self) -> &Option<String>

The DynamoDB document path to the value this beacon will calculate over. If not specified, the beacon will calculate values for the attribute with the name specified in ‘name’.

Source

pub fn name(&self) -> &Option<String>

The name for this Standard Beacon.

Source

pub fn style(&self) -> &Option<BeaconStyle>

Optional augmented behavior.

Source§

impl StandardBeacon

Source

pub fn builder() -> StandardBeaconBuilder

Creates a new builder-style object to manufacture StandardBeacon.

Examples found in repository?
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 71)
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 67)
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 200)
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 114)
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 66)
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 StandardBeacon

Source§

fn clone(&self) -> StandardBeacon

Returns a copy 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 StandardBeacon

Source§

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

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

impl PartialEq for StandardBeacon

Source§

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

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> 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,

Source§

impl<T> MaybeSendSync for T