Skip to content

Commit 814558b

Browse files
committed
migration examples for encrypted table
1 parent af8c9c2 commit 814558b

20 files changed

+896
-49
lines changed

Examples/runtimes/python/Migration/src/ddbec_to_awsdbe/awsdbe/client/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def setup_awsdbe_client_with_legacy_override(kms_key_id: str, ddb_table_name: st
129129
# 0. Create AWS SDK DynamoDB Client
130130
ddb_client = boto3.client("dynamodb")
131131

132-
# 1. Create the legacy DynamoDB Encryption Client
132+
# 1. Create the legacy EncryptedClient
133133
cmp = AwsKmsCryptographicMaterialsProvider(key_id=kms_key_id)
134134
legacy_encrypted_client = LegacyEncryptedClient(
135135
client=ddb_client,

Examples/runtimes/python/Migration/src/ddbec_to_awsdbe/awsdbe/client/migration_step_1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .common import setup_awsdbe_client_with_legacy_override
2828

2929

30-
def migration_step_1(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1):
30+
def migration_step_1_with_client(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1):
3131
"""
3232
Migration Step 1: Using the AWS Database Encryption SDK with Legacy Override.
3333
@@ -36,7 +36,7 @@ def migration_step_1(kms_key_id: str, ddb_table_name: str, sort_read_value: int
3636
:param sort_read_value: The sort key value to read
3737
3838
"""
39-
# 1. Create a DynamoDB Encryption SDK client with legacy override.
39+
# 1. Create a EncryptedClient with legacy override.
4040
# For Legacy Policy, use `FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`.
4141
# With this policy, you will continue to read and write items using the old format,
4242
# but will be able to start reading new items in the new format as soon as they appear

Examples/runtimes/python/Migration/src/ddbec_to_awsdbe/awsdbe/client/migration_step_2.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@
2727
from .common import setup_awsdbe_client_with_legacy_override
2828

2929

30-
def migration_step_2(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 2):
30+
def migration_step_2_with_client(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 2):
3131
"""
32-
Migration Step 2: Using pure AWS DBESDK and legacy override together.
32+
Migration Step 2: Using pure AWS DBESDK and legacy override together with EncryptedClient.
3333
3434
:param kms_key_id: The ARN of the KMS key to use for encryption
3535
:param ddb_table_name: The name of the DynamoDB table
3636
:param sort_read_value: The sort key value to read
3737
3838
"""
39-
# 1. Create a DynamoDB Encryption SDK client with legacy override.
39+
# 1. Create a EncryptedClient with legacy override.
4040
# When configuring our legacy behavior, use `FORBID_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`.
4141
# With this policy, you will continue to read items in both formats,
4242
# but will only write new items using the new format.

Examples/runtimes/python/Migration/src/ddbec_to_awsdbe/awsdbe/client/migration_step_3.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
from .common import setup_pure_awsdbe_client
2424

2525

26-
def migration_step_3(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 3):
26+
def migration_step_3_with_client(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 3):
2727
"""
28-
Migration Step 3: Using only pure AWS DBESDK (no legacy override).
28+
Migration Step 3: Using only pure AWS DBESDK (no legacy override) with EncryptedClient.
2929
3030
:param kms_key_id: The ARN of the KMS key to use for encryption
3131
:param ddb_table_name: The name of the DynamoDB table
3232
:param sort_read_value: The sort key value to read
3333
"""
34-
# 1. Create the DynamoDb Encryption Interceptor with the above configuration.
34+
# 1. Create the EncryptedClient.
3535
# Do not configure any legacy behavior.
3636
encrypted_client = setup_pure_awsdbe_client(kms_key_id, ddb_table_name)
3737

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.table import EncryptedTable
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.table import EncryptedTable as LegacyEncryptedTable
24+
from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider
25+
26+
27+
def setup_pure_awsdbe_table(kms_key_id: str, ddb_table_name: str):
28+
"""
29+
Set up a pure AWS Database Encryption SDK EncryptedTable 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 EncryptedTable 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 DB-ESDK EncryptedTable
113+
return EncryptedTable(
114+
table=boto3.resource("dynamodb").Table(ddb_table_name),
115+
encryption_config=tables_config,
116+
)
117+
118+
119+
def setup_awsdbe_table_with_legacy_override(kms_key_id: str, ddb_table_name: str, policy: str):
120+
"""
121+
Set up an AWS Database Encryption SDK EncryptedTable 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 EncryptedTable for DynamoDB
127+
128+
"""
129+
# 0. Create AWS SDK DynamoDB Client
130+
ddb_table = boto3.resource("dynamodb").Table(ddb_table_name)
131+
132+
# 1. Create the legacy EncryptedTable
133+
cmp = AwsKmsCryptographicMaterialsProvider(key_id=kms_key_id)
134+
legacy_encrypted_table = LegacyEncryptedTable(
135+
table=ddb_table,
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_table,
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+
# without 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 DB-ESDK EncryptedTable
232+
return EncryptedTable(
233+
table=boto3.resource("dynamodb").Table(ddb_table_name),
234+
encryption_config=tables_config,
235+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 EncryptedTable 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 client 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 (S)
24+
"""
25+
from aws_dbesdk_dynamodb.structures.dynamodb import LegacyPolicy
26+
27+
from .common import setup_awsdbe_table_with_legacy_override
28+
29+
30+
def migration_step_1_with_table(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 EncryptedTable 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_table = setup_awsdbe_table_with_legacy_override(
45+
kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy
46+
)
47+
48+
# 2. Put an item in the old format since we are using a legacy override
49+
# with FORCE_LEGACY_ENCRYPT_ALLOW_DECRYPT policy
50+
item_to_encrypt = {
51+
"partition_key": "MigrationExampleForPythonTable",
52+
"sort_key": 1,
53+
"attribute1": "encrypt and sign me!",
54+
"attribute2": "sign me!",
55+
":attribute3": "ignore me!",
56+
}
57+
58+
put_item_response = encrypted_table.put_item(Item=item_to_encrypt)
59+
60+
# Demonstrate that PutItem succeeded
61+
assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200
62+
63+
# 3. Get an item back from the table using the EncryptedTable
64+
# If this is an item written in the old format (e.g. any item written
65+
# during Step 0 or 1), then we will attempt to decrypt the item
66+
# using the legacy behavior.
67+
# If this is an item written in the new format (e.g. any item written
68+
# during Step 2 or after), then we will attempt to decrypt the item using
69+
# the non-legacy behavior.
70+
key_to_get = {"partition_key": "MigrationExampleForPythonTable", "sort_key": sort_read_value}
71+
get_item_response = encrypted_table.get_item(Key=key_to_get)
72+
73+
# Demonstrate that GetItem succeeded and returned the decrypted item
74+
assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200
75+
decrypted_item = get_item_response["Item"]
76+
# Demonstrate we get the expected item back
77+
assert decrypted_item["partition_key"] == "MigrationExampleForPythonTable"
78+
assert decrypted_item["sort_key"] == sort_read_value
79+
assert decrypted_item["attribute1"] == "encrypt and sign me!"
80+
assert decrypted_item["attribute2"] == "sign me!"
81+
assert decrypted_item[":attribute3"] == "ignore me!"

0 commit comments

Comments
 (0)