Struct Client

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

Implementations§

Source§

impl Client

Source§

impl Client

Source

pub fn create_key_store(&self) -> CreateKeyStoreFluentBuilder

Constructs a fluent builder for the CreateKeyStore operation.

Source§

impl Client

Source

pub fn create_key(&self) -> CreateKeyFluentBuilder

Examples found in repository?
examples/create_keystore_key.rs (line 48)
25pub async fn keystore_create_key() -> Result<String, crate::BoxError> {
26    let key_store_table_name = test_utils::TEST_KEYSTORE_NAME;
27    let logical_key_store_name = test_utils::TEST_LOGICAL_KEYSTORE_NAME;
28    let kms_key_arn = test_utils::TEST_KEYSTORE_KMS_KEY_ID;
29
30    // 1. Configure your KeyStore resource.
31    //    This SHOULD be the same configuration that was used to create the DDB table
32    //    in the "Create KeyStore Table Example".
33    let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
34    let key_store_config = KeyStoreConfig::builder()
35        .kms_client(aws_sdk_kms::Client::new(&sdk_config))
36        .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config))
37        .ddb_table_name(key_store_table_name)
38        .logical_key_store_name(logical_key_store_name)
39        .kms_configuration(KmsConfiguration::KmsKeyArn(kms_key_arn.to_string()))
40        .build()?;
41
42    let keystore = keystore_client::Client::from_conf(key_store_config)?;
43
44    // 2. Create a new branch key and beacon key in our KeyStore.
45    //    Both the branch key and the beacon key will share an Id.
46    //    This creation is eventually consistent.
47
48    let new_key = keystore.create_key().send().await?;
49    Ok(new_key.branch_key_identifier.unwrap())
50}
Source§

impl Client

Source

pub fn version_key(&self) -> VersionKeyFluentBuilder

Constructs a fluent builder for the VersionKey operation.

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source§

impl Client

Source

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

Creates a new client from the service Config.

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

Trait Implementations§

Source§

impl Clone for Client

Source§

fn clone(&self) -> Client

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for Client

Source§

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

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

impl PartialEq for Client

Source§

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

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

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

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

impl StructuralPartialEq for Client

Auto Trait Implementations§

§

impl Freeze for Client

§

impl !RefUnwindSafe for Client

§

impl Send for Client

§

impl Sync for Client

§

impl Unpin for Client

§

impl !UnwindSafe for Client

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

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

Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

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

Source§

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

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

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

fn in_current_span(self) -> Instrumented<Self>

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

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

impl<T> IntoEither for T

Source§

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

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

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

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

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

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

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

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

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

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

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

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.
Source§

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

Source§

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

Source§

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

Source§

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

Source§

impl<T> WithSubscriber for T

Source§

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

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

fn with_current_subscriber(self) -> WithDispatch<Self>

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

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