Skip to content

Commit f14737d

Browse files
committed
migration examples for Encrypted Resource
1 parent 814558b commit f14737d

File tree

13 files changed

+902
-0
lines changed

13 files changed

+902
-0
lines changed
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."""
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Common Utilities for Migration Examples."""
4+
import boto3
5+
from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders
6+
from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig
7+
from aws_cryptographic_material_providers.mpl.models import (
8+
CreateAwsKmsMrkMultiKeyringInput,
9+
DBEAlgorithmSuiteId,
10+
)
11+
from aws_cryptographic_material_providers.mpl.references import IKeyring
12+
from aws_dbesdk_dynamodb.encrypted.resource import EncryptedResource
13+
from aws_dbesdk_dynamodb.structures.dynamodb import (
14+
DynamoDbTableEncryptionConfig,
15+
DynamoDbTablesEncryptionConfig,
16+
LegacyOverride,
17+
)
18+
from aws_dbesdk_dynamodb.structures.structured_encryption import (
19+
CryptoAction,
20+
)
21+
22+
# Import from legacy DynamoDB Encryption Client
23+
from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource as LegacyEncryptedResource
24+
from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider
25+
26+
27+
def setup_pure_awsdbe_resource(kms_key_id: str, ddb_table_name: str):
28+
"""
29+
Set up a pure AWS Database Encryption SDK EncryptedResource without legacy override.
30+
31+
:param kms_key_id: The ARN of the KMS key to use for encryption
32+
:param ddb_table_name: The name of the DynamoDB table
33+
:returns EncryptedResource for DynamoDB
34+
"""
35+
# 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
36+
# For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
37+
# We will use the `CreateMrkMultiKeyring` method to create this keyring,
38+
# as it will correctly handle both single region and Multi-Region KMS Keys.
39+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig())
40+
kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput(
41+
generator=kms_key_id,
42+
)
43+
kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input)
44+
45+
# 2. Configure which attributes are encrypted and/or signed when writing new items.
46+
# For each attribute that may exist on the items we plan to write to our DynamoDbTable,
47+
# we must explicitly configure how they should be treated during item encryption:
48+
# - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
49+
# - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
50+
# - DO_NOTHING: The attribute is not encrypted and not included in the signature
51+
attribute_actions_on_encrypt = {
52+
"partition_key": CryptoAction.SIGN_ONLY,
53+
"sort_key": CryptoAction.SIGN_ONLY,
54+
"attribute1": CryptoAction.ENCRYPT_AND_SIGN,
55+
"attribute2": CryptoAction.SIGN_ONLY,
56+
":attribute3": CryptoAction.DO_NOTHING,
57+
}
58+
59+
# 3. Configure which attributes we expect to be included in the signature
60+
# when reading items. There are two options for configuring this:
61+
#
62+
# - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
63+
# When defining your DynamoDb schema and deciding on attribute names,
64+
# choose a distinguishing prefix (such as ":") for all attributes that
65+
# you do not want to include in the signature.
66+
# This has two main benefits:
67+
# - It is easier to reason about the security and authenticity of data within your item
68+
# when all unauthenticated data is easily distinguishable by their attribute name.
69+
# - If you need to add new unauthenticated attributes in the future,
70+
# you can easily make the corresponding update to your `attributeActionsOnEncrypt`
71+
# and immediately start writing to that new attribute, without
72+
# any other configuration update needed.
73+
# Once you configure this field, it is not safe to update it.
74+
#
75+
# - Configure `allowedUnsignedAttributes`: You may also explicitly list
76+
# a set of attributes that should be considered unauthenticated when encountered
77+
# on read. Be careful if you use this configuration. Do not remove an attribute
78+
# name from this configuration, even if you are no longer writing with that attribute,
79+
# as old items may still include this attribute, and our configuration needs to know
80+
# to continue to exclude this attribute from the signature scope.
81+
# If you add new attribute names to this field, you must first deploy the update to this
82+
# field to all readers in your host fleet before deploying the update to start writing
83+
# with that new attribute.
84+
#
85+
# For this example, we have designed our DynamoDb table such that any attribute name with
86+
# the ":" prefix should be considered unauthenticated.
87+
unsignAttrPrefix: str = ":"
88+
89+
# 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
90+
# without the legacy override
91+
table_configs = {}
92+
table_config = DynamoDbTableEncryptionConfig(
93+
logical_table_name=ddb_table_name,
94+
partition_key_name="partition_key",
95+
sort_key_name="sort_key",
96+
attribute_actions_on_encrypt=attribute_actions_on_encrypt,
97+
keyring=kms_mrk_multi_keyring,
98+
allowed_unsigned_attribute_prefix=unsignAttrPrefix,
99+
# Specifying an algorithm suite is not required,
100+
# but is done here to demonstrate how to do so.
101+
# We suggest using the
102+
# `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
103+
# which includes AES-GCM with key derivation, signing, and key commitment.
104+
# This is also the default algorithm suite if one is not specified in this config.
105+
# For more information on supported algorithm suites, see:
106+
# https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
107+
algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384,
108+
)
109+
table_configs[ddb_table_name] = table_config
110+
tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
111+
112+
# 5. Create the EncryptedResource
113+
return EncryptedResource(
114+
resource=boto3.resource("dynamodb"),
115+
encryption_config=tables_config,
116+
)
117+
118+
119+
def setup_awsdbe_resource_with_legacy_override(kms_key_id: str, ddb_table_name: str, policy: str):
120+
"""
121+
Set up an AWS Database Encryption SDK EncryptedResource with legacy override.
122+
123+
:param kms_key_id: The ARN of the KMS key to use for encryption
124+
:param ddb_table_name: The name of the DynamoDB table
125+
:param policy: The policy required for the Legacy Override configuration
126+
:returns EncryptedResource for DynamoDB
127+
128+
"""
129+
# 0. Create AWS SDK DynamoDB Resource
130+
ddb_resource = boto3.resource("dynamodb")
131+
132+
# 1. Create the legacy EncryptedResource
133+
cmp = AwsKmsCryptographicMaterialsProvider(key_id=kms_key_id)
134+
legacy_encrypted_resource = LegacyEncryptedResource(
135+
resource=ddb_resource,
136+
materials_provider=cmp,
137+
)
138+
139+
# 2. Configure our legacy behavior, inputting the DynamoDBEncryptor, attribute actions
140+
# created above, and legacy policy.
141+
legacy_override = LegacyOverride(
142+
encryptor=legacy_encrypted_resource,
143+
attribute_actions_on_encrypt={
144+
"partition_key": CryptoAction.SIGN_ONLY,
145+
"sort_key": CryptoAction.SIGN_ONLY,
146+
"attribute1": CryptoAction.ENCRYPT_AND_SIGN,
147+
"attribute2": CryptoAction.SIGN_ONLY,
148+
":attribute3": CryptoAction.DO_NOTHING,
149+
},
150+
policy=policy,
151+
)
152+
153+
# 3. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
154+
# For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
155+
# We will use the `CreateMrkMultiKeyring` method to create this keyring,
156+
# as it will correctly handle both single region and Multi-Region KMS Keys.
157+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig())
158+
kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput(
159+
generator=kms_key_id,
160+
)
161+
kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input)
162+
163+
# 4. Configure which attributes are encrypted and/or signed when writing new items.
164+
# For each attribute that may exist on the items we plan to write to our DynamoDbTable,
165+
# we must explicitly configure how they should be treated during item encryption:
166+
# - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
167+
# - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
168+
# - DO_NOTHING: The attribute is not encrypted and not included in the signature
169+
attribute_actions_on_encrypt = {
170+
"partition_key": CryptoAction.SIGN_ONLY,
171+
"sort_key": CryptoAction.SIGN_ONLY,
172+
"attribute1": CryptoAction.ENCRYPT_AND_SIGN,
173+
"attribute2": CryptoAction.SIGN_ONLY,
174+
":attribute3": CryptoAction.DO_NOTHING,
175+
}
176+
177+
# 5. Configure which attributes we expect to be included in the signature
178+
# when reading items. There are two options for configuring this:
179+
#
180+
# - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
181+
# When defining your DynamoDb schema and deciding on attribute names,
182+
# choose a distinguishing prefix (such as ":") for all attributes that
183+
# you do not want to include in the signature.
184+
# This has two main benefits:
185+
# - It is easier to reason about the security and authenticity of data within your item
186+
# when all unauthenticated data is easily distinguishable by their attribute name.
187+
# - If you need to add new unauthenticated attributes in the future,
188+
# you can easily make the corresponding update to your `attributeActionsOnEncrypt`
189+
# and immediately start writing to that new attribute, without
190+
# any other configuration update needed.
191+
# Once you configure this field, it is not safe to update it.
192+
#
193+
# - Configure `allowedUnsignedAttributes`: You may also explicitly list
194+
# a set of attributes that should be considered unauthenticated when encountered
195+
# on read. Be careful if you use this configuration. Do not remove an attribute
196+
# name from this configuration, even if you are no longer writing with that attribute,
197+
# as old items may still include this attribute, and our configuration needs to know
198+
# to continue to exclude this attribute from the signature scope.
199+
# If you add new attribute names to this field, you must first deploy the update to this
200+
# field to all readers in your host fleet before deploying the update to start writing
201+
# with that new attribute.
202+
#
203+
# For this example, we have designed our DynamoDb table such that any attribute name with
204+
# the ":" prefix should be considered unauthenticated.
205+
unsignAttrPrefix: str = ":"
206+
207+
# 6. Create the DynamoDb Encryption configuration for the table we will be writing to.
208+
# with the legacy override
209+
table_configs = {}
210+
table_config = DynamoDbTableEncryptionConfig(
211+
logical_table_name=ddb_table_name,
212+
partition_key_name="partition_key",
213+
sort_key_name="sort_key",
214+
attribute_actions_on_encrypt=attribute_actions_on_encrypt,
215+
keyring=kms_mrk_multi_keyring,
216+
legacy_override=legacy_override,
217+
allowed_unsigned_attribute_prefix=unsignAttrPrefix,
218+
# Specifying an algorithm suite is not required,
219+
# but is done here to demonstrate how to do so.
220+
# We suggest using the
221+
# `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
222+
# which includes AES-GCM with key derivation, signing, and key commitment.
223+
# This is also the default algorithm suite if one is not specified in this config.
224+
# For more information on supported algorithm suites, see:
225+
# https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
226+
algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384,
227+
)
228+
table_configs[ddb_table_name] = table_config
229+
tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
230+
231+
# 7. Create the EncryptedResource
232+
return EncryptedResource(
233+
resource=boto3.resource("dynamodb"),
234+
encryption_config=tables_config,
235+
)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""
5+
Migration Step 1.
6+
7+
This is an example demonstrating how to start using the
8+
AWS Database Encryption SDK with a pre-existing table used with DynamoDB Encryption Client.
9+
In this example, you configure a EncryptedResource to do the following:
10+
- Read items encrypted in the old format
11+
- Continue to encrypt items in the old format on write
12+
- Read items encrypted in the new format
13+
While this step configures your resource to be ready to start reading items encrypted,
14+
we do not yet expect to be reading any items in the new format.
15+
Before you move on to step 2, ensure that these changes have successfully been deployed
16+
to all of your readers.
17+
18+
Running this example requires access to the DDB Table whose name
19+
is provided in CLI arguments.
20+
This table must be configured with the following
21+
primary key configuration:
22+
- Partition key is named "partition_key" with type (S)
23+
- Sort key is named "sort_key" with type (N)
24+
"""
25+
from aws_dbesdk_dynamodb.structures.dynamodb import LegacyPolicy
26+
27+
from .common import setup_awsdbe_resource_with_legacy_override
28+
29+
30+
def migration_step_1_with_resource(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1):
31+
"""
32+
Migration Step 1: Using the AWS Database Encryption SDK with Legacy Override.
33+
34+
:param kms_key_id: The ARN of the KMS key to use for encryption
35+
:param ddb_table_name: The name of the DynamoDB table
36+
:param sort_read_value: The sort key value to read
37+
38+
"""
39+
# 1. Create a EncryptedResource with legacy override.
40+
# For Legacy Policy, use `FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`.
41+
# With this policy, you will continue to read and write items using the old format,
42+
# but will be able to start reading new items in the new format as soon as they appear
43+
policy = LegacyPolicy.FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT
44+
encrypted_resource = setup_awsdbe_resource_with_legacy_override(
45+
kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy
46+
)
47+
48+
# 2. Write a batch of items to the table using the old format since we are using
49+
# a legacy override with FORCE_LEGACY_ENCRYPT_ALLOW_DECRYPT policy
50+
items = [
51+
{
52+
"partition_key": "PythonEncryptedResourceMigrationExample-1",
53+
"sort_key": 1,
54+
"attribute1": "encrypt and sign me!",
55+
"attribute2": "sign me!",
56+
":attribute3": "ignore me!",
57+
},
58+
{
59+
"partition_key": "PythonEncryptedResourceMigrationExample-2",
60+
"sort_key": 1,
61+
"attribute1": "encrypt and sign me!",
62+
"attribute2": "sign me!",
63+
":attribute3": "ignore me!",
64+
},
65+
]
66+
67+
batch_write_items_put_request = {
68+
"RequestItems": {
69+
ddb_table_name: [{"PutRequest": {"Item": item}} for item in items],
70+
},
71+
}
72+
73+
batch_write_items_put_response = encrypted_resource.batch_write_item(**batch_write_items_put_request)
74+
75+
# Demonstrate that BatchWriteItem succeeded
76+
assert batch_write_items_put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
77+
78+
# 3. Read the items back from the table.
79+
# If this is an item written in the old format (e.g. any item written
80+
# during Step 0 or 1), then we will attempt to decrypt the item
81+
# using the legacy behavior.
82+
# If this is an item written in the new format (e.g. any item written
83+
# during Step 2 or after), then we will attempt to decrypt the item using
84+
# the non-legacy behavior.
85+
batch_get_items_request = {
86+
"RequestItems": {
87+
ddb_table_name: {
88+
"Keys": [{"partition_key": item["partition_key"], "sort_key": sort_read_value} for item in items],
89+
}
90+
},
91+
}
92+
93+
batch_get_items_response = encrypted_resource.batch_get_item(**batch_get_items_request)
94+
95+
# Demonstrate that BatchGetItem succeeded with the expected result
96+
assert batch_get_items_response["ResponseMetadata"]["HTTPStatusCode"] == 200
97+
for item in batch_get_items_response["Responses"][ddb_table_name]:
98+
assert (
99+
item["partition_key"] == "PythonEncryptedResourceMigrationExample-1"
100+
or item["partition_key"] == "PythonEncryptedResourceMigrationExample-2"
101+
)
102+
assert item["sort_key"] == sort_read_value
103+
assert item["attribute1"] == "encrypt and sign me!"
104+
assert item["attribute2"] == "sign me!"
105+
assert item[":attribute3"] == "ignore me!"

0 commit comments

Comments
 (0)