Skip to content

Commit 50c6617

Browse files
chore(python): examples for EncryptedTable (#1934)
Co-authored-by: Lucas McDonald <[email protected]>
1 parent 3aa3f59 commit 50c6617

File tree

114 files changed

+6660
-1034
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+6660
-1034
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Example demonstrating how to get encrypted data key descriptions from DynamoDB items."""
4+
5+
import boto3
6+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.client import DynamoDbEncryption
7+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.config import (
8+
DynamoDbEncryptionConfig,
9+
)
10+
from aws_dbesdk_dynamodb.structures.dynamodb import (
11+
GetEncryptedDataKeyDescriptionInput,
12+
GetEncryptedDataKeyDescriptionUnionItem,
13+
)
14+
15+
16+
def get_encrypted_data_key_description(
17+
table_name: str,
18+
partition_key: str,
19+
partition_key_val: str,
20+
sort_key_name: str,
21+
sort_key_value: str,
22+
expected_key_provider_id: str,
23+
expected_key_provider_info: str,
24+
expected_branch_key_id: str,
25+
expected_branch_key_version: str,
26+
):
27+
"""
28+
Get encrypted data key description from a DynamoDB item.
29+
30+
:param table_name: The name of the DynamoDB table
31+
:param partition_key: The name of the partition key
32+
:param partition_key_val: The value of the partition key
33+
:param sort_key_name: The name of the sort key
34+
:param sort_key_value: The value of the sort key
35+
:param expected_key_provider_id: The expected key provider ID
36+
:param expected_key_provider_info: The expected key provider info (optional)
37+
:param expected_branch_key_id: The expected branch key ID (optional)
38+
:param expected_branch_key_version: The expected branch key version (optional)
39+
"""
40+
# 1. Create a new AWS SDK DynamoDb client. This client will be used to get item from the DynamoDB table
41+
ddb = boto3.client("dynamodb")
42+
43+
# 2. Get item from the DynamoDB table. This item will be used to Get Encrypted DataKey Description
44+
key_to_get = {partition_key: {"S": partition_key_val}, sort_key_name: {"N": sort_key_value}}
45+
46+
response = ddb.get_item(TableName=table_name, Key=key_to_get)
47+
48+
returned_item = response.get("Item", {})
49+
if not returned_item:
50+
print(f"No item found with the key {partition_key}!")
51+
return
52+
53+
# 3. Prepare the input for GetEncryptedDataKeyDescription method.
54+
# This input can be a DynamoDB item or a header. For now, we are giving input as a DynamoDB item
55+
# but users can also extract the header from the attribute "aws_dbe_head" in the DynamoDB table
56+
# and use it for GetEncryptedDataKeyDescription method.
57+
ddb_enc = DynamoDbEncryption(config=DynamoDbEncryptionConfig())
58+
59+
input_union = GetEncryptedDataKeyDescriptionUnionItem(returned_item)
60+
61+
input_obj = GetEncryptedDataKeyDescriptionInput(input=input_union)
62+
63+
output = ddb_enc.get_encrypted_data_key_description(input=input_obj)
64+
65+
# In the following code, we are giving input as header instead of a complete DynamoDB item
66+
# This code is provided solely to demo how the alternative approach works. So, it is commented.
67+
68+
# header_attribute = "aws_dbe_head"
69+
# header = returned_item[header_attribute]["B"]
70+
# input_union = GetEncryptedDataKeyDescriptionUnion(
71+
# header=header
72+
# )
73+
74+
# Assert everything
75+
assert output.encrypted_data_key_description_output[0].key_provider_id == expected_key_provider_id
76+
77+
if expected_key_provider_id.startswith("aws-kms"):
78+
assert output.encrypted_data_key_description_output[0].key_provider_info == expected_key_provider_info
79+
80+
if output.encrypted_data_key_description_output[0].key_provider_id == "aws-kms-hierarchy":
81+
assert output.encrypted_data_key_description_output[0].branch_key_id == expected_branch_key_id
82+
assert output.encrypted_data_key_description_output[0].branch_key_version == expected_branch_key_version
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""

Examples/runtimes/python/DynamoDBEncryption/src/keyring/hierarchical_keyring_example.py renamed to Examples/runtimes/python/DynamoDBEncryption/src/keyring/hierarchical_keyring_example/encryption_config.py

Lines changed: 8 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,10 @@
11
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
"""
4-
Example demonstrating DynamoDb Encryption using a Hierarchical Keyring.
4+
Configuration module for hierarchical keyring encryption setup.
55
6-
This example sets up DynamoDb Encryption for the AWS SDK client
7-
using the Hierarchical Keyring, which establishes a key hierarchy
8-
where "branch" keys are persisted in DynamoDb.
9-
These branch keys are used to protect your data keys,
10-
and these branch keys are themselves protected by a root KMS Key.
11-
12-
Establishing a key hierarchy like this has two benefits:
13-
14-
First, by caching the branch key material, and only calling back
15-
to KMS to re-establish authentication regularly according to your configured TTL,
16-
you limit how often you need to call back to KMS to protect your data.
17-
This is a performance/security tradeoff, where your authentication, audit, and
18-
logging from KMS is no longer one-to-one with every encrypt or decrypt call.
19-
However, the benefit is that you no longer have to make a
20-
network call to KMS for every encrypt or decrypt.
21-
22-
Second, this key hierarchy makes it easy to hold multi-tenant data
23-
that is isolated per branch key in a single DynamoDb table.
24-
You can create a branch key for each tenant in your table,
25-
and encrypt all that tenant's data under that distinct branch key.
26-
On decrypt, you can either statically configure a single branch key
27-
to ensure you are restricting decryption to a single tenant,
28-
or you can implement an interface that lets you map the primary key on your items
29-
to the branch key that should be responsible for decrypting that data.
30-
31-
This example then demonstrates configuring a Hierarchical Keyring
32-
with a Branch Key ID Supplier to encrypt and decrypt data for
33-
two separate tenants.
34-
35-
Running this example requires access to the DDB Table whose name
36-
is provided in CLI arguments.
37-
This table must be configured with the following
38-
primary key configuration:
39-
- Partition key is named "partition_key" with type (S)
40-
- Sort key is named "sort_key" with type (S)
41-
42-
This example also requires using a KMS Key whose ARN
43-
is provided in CLI arguments. You need the following access
44-
on this key:
45-
- GenerateDataKeyWithoutPlaintext
46-
- Decrypt
6+
This module provides the common encryption configuration used by both
7+
EncryptedClient and EncryptedTable examples.
478
"""
489

4910
import boto3
@@ -57,7 +18,6 @@
5718
CreateAwsKmsHierarchicalKeyringInput,
5819
DefaultCache,
5920
)
60-
from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
6121
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.client import DynamoDbEncryption
6222
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.config import (
6323
DynamoDbEncryptionConfig,
@@ -74,23 +34,24 @@
7434
from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier
7535

7636

77-
def hierarchical_keyring_get_item_put_item(
37+
def create_encryption_config(
7838
ddb_table_name: str,
7939
tenant1_branch_key_id: str,
8040
tenant2_branch_key_id: str,
8141
keystore_table_name: str,
8242
logical_keystore_name: str,
8343
kms_key_id: str,
84-
):
44+
) -> DynamoDbTablesEncryptionConfig:
8545
"""
86-
Demonstrate using a hierarchical keyring with multiple tenants.
46+
Create the encryption configuration for DynamoDB encryption.
8747
8848
:param ddb_table_name: The name of the DynamoDB table
8949
:param tenant1_branch_key_id: Branch key ID for tenant 1
9050
:param tenant2_branch_key_id: Branch key ID for tenant 2
9151
:param keystore_table_name: The name of the KeyStore DynamoDB table
9252
:param logical_keystore_name: The logical name for this keystore
9353
:param kms_key_id: The ARN of the KMS key to use
54+
:return: The DynamoDB tables encryption configuration
9455
"""
9556
# Initial KeyStore Setup: This example requires that you have already
9657
# created your KeyStore, and have populated it with two new branch keys.
@@ -190,40 +151,4 @@ def hierarchical_keyring_get_item_put_item(
190151
)
191152

192153
table_configs = {ddb_table_name: table_config}
193-
tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
194-
195-
# 7. Create the EncryptedClient
196-
ddb_client = boto3.client("dynamodb")
197-
encrypted_ddb_client = EncryptedClient(client=ddb_client, encryption_config=tables_config)
198-
199-
# 8. Put an item into our table using the above client.
200-
# Before the item gets sent to DynamoDb, it will be encrypted
201-
# client-side, according to our configuration.
202-
# Because the item we are writing uses "tenantId1" as our partition value,
203-
# based on the code we wrote in the ExampleBranchKeySupplier,
204-
# `tenant1_branch_key_id` will be used to encrypt this item.
205-
item = {
206-
"partition_key": {"S": "tenant1Id"},
207-
"sort_key": {"N": "0"},
208-
"tenant_sensitive_data": {"S": "encrypt and sign me!"},
209-
}
210-
211-
put_response = encrypted_ddb_client.put_item(TableName=ddb_table_name, Item=item)
212-
213-
# Demonstrate that PutItem succeeded
214-
assert put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
215-
216-
# 9. Get the item back from our table using the same client.
217-
# The client will decrypt the item client-side, and return
218-
# back the original item.
219-
# Because the returned item's partition value is "tenantId1",
220-
# based on the code we wrote in the ExampleBranchKeySupplier,
221-
# `tenant1_branch_key_id` will be used to decrypt this item.
222-
key_to_get = {"partition_key": {"S": "tenant1Id"}, "sort_key": {"N": "0"}}
223-
224-
get_response = encrypted_ddb_client.get_item(TableName=ddb_table_name, Key=key_to_get)
225-
226-
# Demonstrate that GetItem succeeded and returned the decrypted item
227-
assert get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
228-
returned_item = get_response["Item"]
229-
assert returned_item["tenant_sensitive_data"]["S"] == "encrypt and sign me!"
154+
return DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Example demonstrating DynamoDb Encryption using a Hierarchical Keyring with EncryptedClient.
5+
6+
This example sets up DynamoDb Encryption for the AWS SDK client
7+
using the Hierarchical Keyring, which establishes a key hierarchy
8+
where "branch" keys are persisted in DynamoDb.
9+
These branch keys are used to protect your data keys,
10+
and these branch keys are themselves protected by a root KMS Key.
11+
12+
Establishing a key hierarchy like this has two benefits:
13+
14+
First, by caching the branch key material, and only calling back
15+
to KMS to re-establish authentication regularly according to your configured TTL,
16+
you limit how often you need to call back to KMS to protect your data.
17+
This is a performance/security tradeoff, where your authentication, audit, and
18+
logging from KMS is no longer one-to-one with every encrypt or decrypt call.
19+
However, the benefit is that you no longer have to make a
20+
network call to KMS for every encrypt or decrypt.
21+
22+
Second, this key hierarchy makes it easy to hold multi-tenant data
23+
that is isolated per branch key in a single DynamoDb table.
24+
You can create a branch key for each tenant in your table,
25+
and encrypt all that tenant's data under that distinct branch key.
26+
On decrypt, you can either statically configure a single branch key
27+
to ensure you are restricting decryption to a single tenant,
28+
or you can implement an interface that lets you map the primary key on your items
29+
to the branch key that should be responsible for decrypting that data.
30+
31+
This example then demonstrates configuring a Hierarchical Keyring
32+
with a Branch Key ID Supplier to encrypt and decrypt data for
33+
two separate tenants.
34+
35+
Running this example requires access to the DDB Table whose name
36+
is provided in CLI arguments.
37+
This table must be configured with the following
38+
primary key configuration:
39+
- Partition key is named "partition_key" with type (S)
40+
- Sort key is named "sort_key" with type (S)
41+
42+
This example also requires using a KMS Key whose ARN
43+
is provided in CLI arguments. You need the following access
44+
on this key:
45+
- GenerateDataKeyWithoutPlaintext
46+
- Decrypt
47+
"""
48+
49+
import boto3
50+
from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
51+
52+
from .encryption_config import create_encryption_config
53+
54+
55+
def hierarchical_keyring_client_example(
56+
ddb_table_name: str,
57+
tenant1_branch_key_id: str,
58+
tenant2_branch_key_id: str,
59+
keystore_table_name: str,
60+
logical_keystore_name: str,
61+
kms_key_id: str,
62+
):
63+
"""
64+
Demonstrate using a hierarchical keyring with multiple tenants using EncryptedClient.
65+
66+
:param ddb_table_name: The name of the DynamoDB table
67+
:param tenant1_branch_key_id: Branch key ID for tenant 1
68+
:param tenant2_branch_key_id: Branch key ID for tenant 2
69+
:param keystore_table_name: The name of the KeyStore DynamoDB table
70+
:param logical_keystore_name: The logical name for this keystore
71+
:param kms_key_id: The ARN of the KMS key to use
72+
"""
73+
# 1. Create the DynamoDb Encryption configuration for the table we will be writing to.
74+
# See beacon_config.py in this directory for detailed steps on the encryption configuration.
75+
tables_config = create_encryption_config(
76+
ddb_table_name=ddb_table_name,
77+
tenant1_branch_key_id=tenant1_branch_key_id,
78+
tenant2_branch_key_id=tenant2_branch_key_id,
79+
keystore_table_name=keystore_table_name,
80+
logical_keystore_name=logical_keystore_name,
81+
kms_key_id=kms_key_id,
82+
)
83+
84+
# 2. Create the EncryptedClient
85+
ddb_client = boto3.client("dynamodb")
86+
encrypted_ddb_client = EncryptedClient(client=ddb_client, encryption_config=tables_config)
87+
88+
# 3. Put an item into our table using the above client.
89+
# Before the item gets sent to DynamoDb, it will be encrypted
90+
# client-side, according to our configuration.
91+
# Because the item we are writing uses "tenantId1" as our partition value,
92+
# based on the code we wrote in the ExampleBranchKeySupplier,
93+
# `tenant1_branch_key_id` will be used to encrypt this item.
94+
item = {
95+
"partition_key": {"S": "tenant1Id"},
96+
"sort_key": {"N": "0"},
97+
"tenant_sensitive_data": {"S": "encrypt and sign me!"},
98+
}
99+
100+
put_response = encrypted_ddb_client.put_item(TableName=ddb_table_name, Item=item)
101+
102+
# Demonstrate that PutItem succeeded
103+
assert put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
104+
105+
# 4. Get the item back from our table using the same client.
106+
# The client will decrypt the item client-side, and return
107+
# back the original item.
108+
# Because the returned item's partition value is "tenantId1",
109+
# based on the code we wrote in the ExampleBranchKeySupplier,
110+
# `tenant1_branch_key_id` will be used to decrypt this item.
111+
key_to_get = {"partition_key": {"S": "tenant1Id"}, "sort_key": {"N": "0"}}
112+
113+
get_response = encrypted_ddb_client.get_item(TableName=ddb_table_name, Key=key_to_get)
114+
115+
# Demonstrate that GetItem succeeded and returned the decrypted item
116+
assert get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
117+
returned_item = get_response["Item"]
118+
assert returned_item["tenant_sensitive_data"]["S"] == "encrypt and sign me!"

0 commit comments

Comments
 (0)