pub struct ResolveAttributesFluentBuilder { /* private fields */ }
Expand description
Fluent builder constructing a request to ResolveAttributes
.
Given an Item, show the intermediate values (e.g. compound beacons, virtual fields).
Implementations§
Source§impl ResolveAttributesFluentBuilder
impl ResolveAttributesFluentBuilder
Sourcepub fn as_input(&self) -> &ResolveAttributesInputBuilder
pub fn as_input(&self) -> &ResolveAttributesInputBuilder
Access the ResolveAttributes as a reference.
Sourcepub async fn send(self) -> Result<ResolveAttributesOutput, Error>
pub async fn send(self) -> Result<ResolveAttributesOutput, Error>
Sends the request and returns the response.
Examples found in repository?
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 244)
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
examples/searchableencryption/virtual_beacon_searchable_encryption.rs (line 340)
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}
Sourcepub fn item(self, input: impl Into<HashMap<String, AttributeValue>>) -> Self
pub fn item(self, input: impl Into<HashMap<String, AttributeValue>>) -> Self
The Item to be examined.
Examples found in repository?
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 242)
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
examples/searchableencryption/virtual_beacon_searchable_encryption.rs (line 338)
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}
Sourcepub fn set_item(self, input: Option<HashMap<String, AttributeValue>>) -> Self
pub fn set_item(self, input: Option<HashMap<String, AttributeValue>>) -> Self
The Item to be examined.
Sourcepub fn table_name(self, input: impl Into<String>) -> Self
pub fn table_name(self, input: impl Into<String>) -> Self
Use the config for this Table.
Examples found in repository?
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 241)
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
examples/searchableencryption/virtual_beacon_searchable_encryption.rs (line 337)
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}
Sourcepub fn set_table_name(self, input: Option<String>) -> Self
pub fn set_table_name(self, input: Option<String>) -> Self
Use the config for this Table.
Sourcepub fn get_table_name(&self) -> &Option<String>
pub fn get_table_name(&self) -> &Option<String>
Use the config for this Table.
Sourcepub fn version(self, input: impl Into<i32>) -> Self
pub fn version(self, input: impl Into<i32>) -> Self
The beacon version to use. Defaults to ‘writeVersion’.
Examples found in repository?
examples/searchableencryption/compound_beacon_searchable_encryption.rs (line 243)
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
examples/searchableencryption/virtual_beacon_searchable_encryption.rs (line 339)
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}
Sourcepub fn set_version(self, input: Option<i32>) -> Self
pub fn set_version(self, input: Option<i32>) -> Self
The beacon version to use. Defaults to ‘writeVersion’.
Sourcepub fn get_version(&self) -> &Option<i32>
pub fn get_version(&self) -> &Option<i32>
The beacon version to use. Defaults to ‘writeVersion’.
Trait Implementations§
Source§impl Clone for ResolveAttributesFluentBuilder
impl Clone for ResolveAttributesFluentBuilder
Source§fn clone(&self) -> ResolveAttributesFluentBuilder
fn clone(&self) -> ResolveAttributesFluentBuilder
Returns a duplicate of the value. Read more
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source
. Read moreAuto Trait Implementations§
impl Freeze for ResolveAttributesFluentBuilder
impl !RefUnwindSafe for ResolveAttributesFluentBuilder
impl Send for ResolveAttributesFluentBuilder
impl Sync for ResolveAttributesFluentBuilder
impl Unpin for ResolveAttributesFluentBuilder
impl !UnwindSafe for ResolveAttributesFluentBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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 moreCreates a shared type from an unshared type.