main/searchableencryption/complexexample/
beacon_config.rs

1// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use aws_db_esdk::dynamodb::types::BeaconKeySource;
5use aws_db_esdk::dynamodb::types::BeaconVersion;
6use aws_db_esdk::dynamodb::types::CompoundBeacon;
7use aws_db_esdk::dynamodb::types::Constructor;
8use aws_db_esdk::dynamodb::types::ConstructorPart;
9use aws_db_esdk::dynamodb::types::DynamoDbTableEncryptionConfig;
10use aws_db_esdk::dynamodb::types::EncryptedPart;
11use aws_db_esdk::dynamodb::types::SearchConfig;
12use aws_db_esdk::dynamodb::types::SignedPart;
13use aws_db_esdk::dynamodb::types::SingleKeyStore;
14use aws_db_esdk::dynamodb::types::StandardBeacon;
15use aws_db_esdk::intercept::DbEsdkInterceptor;
16use aws_db_esdk::key_store::client as keystore_client;
17use aws_db_esdk::key_store::types::key_store_config::KeyStoreConfig;
18use aws_db_esdk::key_store::types::KmsConfiguration;
19use aws_db_esdk::material_providers::client as mpl_client;
20use aws_db_esdk::material_providers::types::material_providers_config::MaterialProvidersConfig;
21use aws_db_esdk::CryptoAction;
22use aws_db_esdk::DynamoDbTablesEncryptionConfig;
23use std::collections::HashMap;
24
25/*
26 * This file is used in an example to demonstrate complex queries
27 * that you can perform using beacons.
28 * The example data used is for demonstrative purposes only,
29 * and might not meet the distribution and correlation uniqueness
30 * recommendations for beacons.
31 * See our documentation for whether beacons are
32 * right for your particular data set:
33 * https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me
34 *
35 * This file sets up all the searchable encryption configuration required to execute the examples from
36 * our workshop using the encryption client.
37 */
38
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}