Skip to content

Commit 8107527

Browse files
committed
ecdh & hierarchy resolve dependency
1 parent 668cad0 commit 8107527

9 files changed

+2016
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# # SPDX-License-Identifier: Apache-2.0
3+
# """
4+
# Example implementation of a branch key ID supplier.
5+
#
6+
# Used in the 'HierarchicalKeyringExample'.
7+
# In that example, we have a table where we distinguish multiple tenants
8+
# by a tenant ID that is stored in our partition attribute.
9+
# The expectation is that this does not produce a confused deputy
10+
# because the tenants are separated by partition.
11+
# In order to create a Hierarchical Keyring that is capable of encrypting or
12+
# decrypting data for either tenant, we implement this interface
13+
# to map the correct branch key ID to the correct tenant ID.
14+
# """
15+
# from typing import Dict
16+
#
17+
# # TODO: Resolve dependency
18+
# from aws_dbesdk_dynamodb.structures import IDynamoDbKeyBranchKeyIdSupplier
19+
# from aws_dbesdk_dynamodb.model import (
20+
# GetBranchKeyIdFromDdbKeyInput,
21+
# GetBranchKeyIdFromDdbKeyOutput,
22+
# AttributeValue
23+
# )
24+
# # from aws_dynamodb_encryption_client.dynamodb.model import AttributeValue
25+
#
26+
#
27+
# class ExampleBranchKeyIdSupplier(IDynamoDbKeyBranchKeyIdSupplier):
28+
# """Example implementation of a branch key ID supplier."""
29+
#
30+
# def __init__(self, tenant1_id: str, tenant2_id: str):
31+
# """Example constructor for a branch key ID supplier.
32+
#
33+
# :param tenant1_id: Branch key ID for tenant 1
34+
# :param tenant2_id: Branch key ID for tenant 2
35+
# """
36+
# self.branch_key_id_for_tenant1 = tenant1_id
37+
# self.branch_key_id_for_tenant2 = tenant2_id
38+
#
39+
# def get_branch_key_id_from_ddb_key(
40+
# self,
41+
# input: GetBranchKeyIdFromDdbKeyInput
42+
# ) -> GetBranchKeyIdFromDdbKeyOutput:
43+
# """Returns branch key ID from the tenant ID in input's DDB key.
44+
#
45+
# :param input: Input containing DDB key
46+
# :return: Output containing branch key ID
47+
# :raises ValueError: If DDB key is invalid or contains invalid tenant ID
48+
# """
49+
# key: Dict[str, AttributeValue] = input.ddb_key()
50+
#
51+
# if "partition_key" not in key:
52+
# raise ValueError(
53+
# "Item invalid, does not contain expected partition key attribute."
54+
# )
55+
#
56+
# tenant_key_id = key["partition_key"].s()
57+
#
58+
# if tenant_key_id == "tenant1Id":
59+
# branch_key_id = self.branch_key_id_for_tenant1
60+
# elif tenant_key_id == "tenant2Id":
61+
# branch_key_id = self.branch_key_id_for_tenant2
62+
# else:
63+
# raise ValueError("Item does not contain valid tenant ID")
64+
#
65+
# return GetBranchKeyIdFromDdbKeyOutput(
66+
# branch_key_id=branch_key_id
67+
# )
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# # SPDX-License-Identifier: Apache-2.0
3+
# """
4+
# This example sets up DynamoDb Encryption for the AWS SDK client
5+
# using the Hierarchical Keyring, which establishes a key hierarchy
6+
# where "branch" keys are persisted in DynamoDb.
7+
# These branch keys are used to protect your data keys,
8+
# and these branch keys are themselves protected by a root KMS Key.
9+
#
10+
# Establishing a key hierarchy like this has two benefits:
11+
#
12+
# First, by caching the branch key material, and only calling back
13+
# to KMS to re-establish authentication regularly according to your configured TTL,
14+
# you limit how often you need to call back to KMS to protect your data.
15+
# This is a performance/security tradeoff, where your authentication, audit, and
16+
# logging from KMS is no longer one-to-one with every encrypt or decrypt call.
17+
# However, the benefit is that you no longer have to make a
18+
# network call to KMS for every encrypt or decrypt.
19+
#
20+
# Second, this key hierarchy makes it easy to hold multi-tenant data
21+
# that is isolated per branch key in a single DynamoDb table.
22+
# You can create a branch key for each tenant in your table,
23+
# and encrypt all that tenant's data under that distinct branch key.
24+
# On decrypt, you can either statically configure a single branch key
25+
# to ensure you are restricting decryption to a single tenant,
26+
# or you can implement an interface that lets you map the primary key on your items
27+
# to the branch key that should be responsible for decrypting that data.
28+
#
29+
# This example then demonstrates configuring a Hierarchical Keyring
30+
# with a Branch Key ID Supplier to encrypt and decrypt data for
31+
# two separate tenants.
32+
#
33+
# Running this example requires access to the DDB Table whose name
34+
# is provided in CLI arguments.
35+
# This table must be configured with the following
36+
# primary key configuration:
37+
# - Partition key is named "partition_key" with type (S)
38+
# - Sort key is named "sort_key" with type (S)
39+
#
40+
# This example also requires using a KMS Key whose ARN
41+
# is provided in CLI arguments. You need the following access
42+
# on this key:
43+
# - GenerateDataKeyWithoutPlaintext
44+
# - Decrypt
45+
# """
46+
# import boto3
47+
# from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders
48+
# from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig
49+
# from aws_cryptographic_material_providers.mpl.models import (
50+
# CacheTypeDefault,
51+
# CreateAwsKmsHierarchicalKeyringInput,
52+
# DefaultCache,
53+
# )
54+
# from aws_cryptographic_material_providers.keystore.client import KeyStore
55+
# from aws_cryptographic_material_providers.keystore.config import KeyStoreConfig
56+
# from aws_cryptographic_material_providers.keystore.models import KMSConfigurationKmsKeyArn
57+
# from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
58+
# from aws_dbesdk_dynamodb.structures.dynamodb import (
59+
# DynamoDbTableEncryptionConfig,
60+
# DynamoDbTablesEncryptionConfig,
61+
# )
62+
# from aws_dbesdk_dynamodb.structures.structured_encryption import (
63+
# CryptoAction,
64+
# )
65+
#
66+
# from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier
67+
#
68+
#
69+
# def hierarchical_keyring_get_item_put_item(
70+
# ddb_table_name: str,
71+
# tenant1_branch_key_id: str,
72+
# tenant2_branch_key_id: str,
73+
# keystore_table_name: str,
74+
# logical_keystore_name: str,
75+
# kms_key_id: str
76+
# ):
77+
# """Demonstrate using a hierarchical keyring with multiple tenants.
78+
#
79+
# :param ddb_table_name: The name of the DynamoDB table
80+
# :param tenant1_branch_key_id: Branch key ID for tenant 1
81+
# :param tenant2_branch_key_id: Branch key ID for tenant 2
82+
# :param keystore_table_name: The name of the KeyStore DynamoDB table
83+
# :param logical_keystore_name: The logical name for this keystore
84+
# :param kms_key_id: The ARN of the KMS key to use
85+
# """
86+
# # Initial KeyStore Setup: This example requires that you have already
87+
# # created your KeyStore, and have populated it with two new branch keys.
88+
# # See the "Create KeyStore Table Example" and "Create KeyStore Key Example"
89+
# # for an example of how to do this.
90+
#
91+
# # 1. Configure your KeyStore resource.
92+
# # This SHOULD be the same configuration that you used
93+
# # to initially create and populate your KeyStore.
94+
# keystore = KeyStore(
95+
# config=KeyStoreConfig(
96+
# ddb_client=boto3.client('dynamodb'),
97+
# ddb_table_name=keystore_table_name,
98+
# logical_key_store_name=logical_keystore_name,
99+
# kms_client=boto3.client('kms'),
100+
# kms_configuration=KMSConfigurationKmsKeyArn(kms_key_id),
101+
# )
102+
# )
103+
#
104+
# # 2. Create a Branch Key ID Supplier. See ExampleBranchKeyIdSupplier in this directory.
105+
# branch_key_id_supplier = ExampleBranchKeyIdSupplier(
106+
# tenant1_branch_key_id,
107+
# tenant2_branch_key_id
108+
# )
109+
#
110+
# # 3. Create the Hierarchical Keyring, using the Branch Key ID Supplier above.
111+
# # With this configuration, the AWS SDK Client ultimately configured will be capable
112+
# # of encrypting or decrypting items for either tenant (assuming correct KMS access).
113+
# # If you want to restrict the client to only encrypt or decrypt for a single tenant,
114+
# # configure this Hierarchical Keyring using `.branch_key_id=tenant1_branch_key_id` instead
115+
# # of `.branch_key_id_supplier=branch_key_id_supplier`.
116+
# mat_prov = AwsCryptographicMaterialProviders(
117+
# config=MaterialProvidersConfig()
118+
# )
119+
#
120+
# keyring_input = CreateAwsKmsHierarchicalKeyringInput(
121+
# key_store=keystore,
122+
# branch_key_id_supplier=branch_key_id_supplier,
123+
# ttl_seconds=600, # This dictates how often we call back to KMS to authorize use of the branch keys
124+
# cache=CacheTypeDefault( # This dictates how many branch keys will be held locally
125+
# value=DefaultCache(
126+
# entry_capacity=100
127+
# )
128+
# )
129+
# )
130+
#
131+
# hierarchical_keyring = mat_prov.create_aws_kms_hierarchical_keyring(
132+
# input=keyring_input
133+
# )
134+
#
135+
# # 4. Configure which attributes are encrypted and/or signed when writing new items.
136+
# # For each attribute that may exist on the items we plan to write to our DynamoDbTable,
137+
# # we must explicitly configure how they should be treated during item encryption:
138+
# # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
139+
# # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
140+
# # - DO_NOTHING: The attribute is not encrypted and not included in the signature
141+
# attribute_actions = {
142+
# "partition_key": CryptoAction.SIGN_ONLY, # Our partition attribute must be SIGN_ONLY
143+
# "sort_key": CryptoAction.SIGN_ONLY, # Our sort attribute must be SIGN_ONLY
144+
# "tenant_sensitive_data": CryptoAction.ENCRYPT_AND_SIGN
145+
# }
146+
#
147+
# # 5. Configure which attributes we expect to be included in the signature
148+
# # when reading items. There are two options for configuring this:
149+
# #
150+
# # - (Recommended) Configure `allowed_unsigned_attribute_prefix`:
151+
# # When defining your DynamoDb schema and deciding on attribute names,
152+
# # choose a distinguishing prefix (such as ":") for all attributes that
153+
# # you do not want to include in the signature.
154+
# # This has two main benefits:
155+
# # - It is easier to reason about the security and authenticity of data within your item
156+
# # when all unauthenticated data is easily distinguishable by their attribute name.
157+
# # - If you need to add new unauthenticated attributes in the future,
158+
# # you can easily make the corresponding update to your `attribute_actions`
159+
# # and immediately start writing to that new attribute, without
160+
# # any other configuration update needed.
161+
# # Once you configure this field, it is not safe to update it.
162+
# #
163+
# # - Configure `allowed_unsigned_attributes`: You may also explicitly list
164+
# # a set of attributes that should be considered unauthenticated when encountered
165+
# # on read. Be careful if you use this configuration. Do not remove an attribute
166+
# # name from this configuration, even if you are no longer writing with that attribute,
167+
# # as old items may still include this attribute, and our configuration needs to know
168+
# # to continue to exclude this attribute from the signature scope.
169+
# # If you add new attribute names to this field, you must first deploy the update to this
170+
# # field to all readers in your host fleet before deploying the update to start writing
171+
# # with that new attribute.
172+
# #
173+
# # For this example, we currently authenticate all attributes. To make it easier to
174+
# # add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
175+
# unsign_attr_prefix = ":"
176+
#
177+
# # 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
178+
# table_config = DynamoDbTableEncryptionConfig(
179+
# logical_table_name=ddb_table_name,
180+
# partition_key_name="partition_key",
181+
# sort_key_name="sort_key",
182+
# attribute_actions_on_encrypt=attribute_actions,
183+
# keyring=hierarchical_keyring,
184+
# allowed_unsigned_attribute_prefix=unsign_attr_prefix
185+
# )
186+
#
187+
# table_configs = {ddb_table_name: table_config}
188+
# tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
189+
#
190+
# # 7. Create the EncryptedClient
191+
# ddb_client = boto3.client('dynamodb')
192+
# encrypted_ddb_client = EncryptedClient(
193+
# client=ddb_client,
194+
# encryption_config=tables_config
195+
# )
196+
#
197+
# # 8. Put an item into our table using the above client.
198+
# # Before the item gets sent to DynamoDb, it will be encrypted
199+
# # client-side, according to our configuration.
200+
# # Because the item we are writing uses "tenantId1" as our partition value,
201+
# # based on the code we wrote in the ExampleBranchKeySupplier,
202+
# # `tenant1_branch_key_id` will be used to encrypt this item.
203+
# item = {
204+
# "partition_key": {"S": "tenant1Id"},
205+
# "sort_key": {"N": "0"},
206+
# "tenant_sensitive_data": {"S": "encrypt and sign me!"}
207+
# }
208+
#
209+
# put_response = encrypted_ddb_client.put_item(
210+
# TableName=ddb_table_name,
211+
# Item=item
212+
# )
213+
#
214+
# # Demonstrate that PutItem succeeded
215+
# assert put_response['ResponseMetadata']['HTTPStatusCode'] == 200
216+
#
217+
# # 9. Get the item back from our table using the same client.
218+
# # The client will decrypt the item client-side, and return
219+
# # back the original item.
220+
# # Because the returned item's partition value is "tenantId1",
221+
# # based on the code we wrote in the ExampleBranchKeySupplier,
222+
# # `tenant1_branch_key_id` will be used to decrypt this item.
223+
# key_to_get = {
224+
# "partition_key": {"S": "tenant1Id"},
225+
# "sort_key": {"N": "0"}
226+
# }
227+
#
228+
# get_response = encrypted_ddb_client.get_item(
229+
# TableName=ddb_table_name,
230+
# Key=key_to_get
231+
# )
232+
#
233+
# # Demonstrate that GetItem succeeded and returned the decrypted item
234+
# assert get_response['ResponseMetadata']['HTTPStatusCode'] == 200
235+
# returned_item = get_response['Item']
236+
# assert returned_item["tenant_sensitive_data"]["S"] == "encrypt and sign me!"

0 commit comments

Comments
 (0)