Skip to content

Commit 4da7006

Browse files
committed
Feedback-1 InternalLegacyOverride
1 parent 0142f15 commit 4da7006

File tree

5 files changed

+122
-135
lines changed

5 files changed

+122
-135
lines changed
Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
from _dafny import Seq
4+
5+
import aws_dbesdk_dynamodb.internaldafny.generated.InternalLegacyOverride
36
from aws_dbesdk_dynamodb.internaldafny.generated.AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes import (
47
DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig,
58
Error_DynamoDbItemEncryptorException,
9+
Error_Opaque,
610
DecryptItemInput_DecryptItemInput,
711
EncryptItemInput_EncryptItemInput,
812
)
@@ -14,20 +18,19 @@
1418
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.references import (
1519
ILegacyDynamoDbEncryptor,
1620
)
17-
import smithy_dafny_standard_library.internaldafny.generated.Wrappers as Wrappers
18-
import _dafny
19-
20-
import aws_dbesdk_dynamodb.internaldafny.generated.InternalLegacyOverride
2121
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.models import (
2222
EncryptItemInput,
2323
EncryptItemOutput,
2424
DecryptItemOutput,
2525
DecryptItemInput,
2626
)
2727

28-
2928
try:
3029
from dynamodb_encryption_sdk.encrypted.client import EncryptedClient
30+
from dynamodb_encryption_sdk.encrypted.table import EncryptedTable
31+
from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource
32+
from dynamodb_encryption_sdk.encrypted.client import EncryptedPaginator
33+
from dynamodb_encryption_sdk.encrypted.item import encrypt_dynamodb_item, decrypt_dynamodb_item
3134
from dynamodb_encryption_sdk.structures import EncryptionContext, AttributeActions
3235
from dynamodb_encryption_sdk.identifiers import CryptoAction
3336
from dynamodb_encryption_sdk.encrypted import CryptoConfig
@@ -39,8 +42,14 @@
3942

4043

4144
class InternalLegacyOverride(aws_dbesdk_dynamodb.internaldafny.generated.InternalLegacyOverride.InternalLegacyOverride):
45+
def __init__(self):
46+
super().__init__()
47+
self.crypto_config = None
48+
self.policy = None
49+
4250
@staticmethod
4351
def Build(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
52+
# Check for early return (Postcondition): If there is no legacyOverride there is nothing to do.
4453
if config.legacyOverride.is_None:
4554
return InternalLegacyOverride.CreateBuildSuccess(InternalLegacyOverride.CreateInternalLegacyOverrideNone())
4655

@@ -51,7 +60,14 @@ def Build(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
5160
return InternalLegacyOverride.CreateBuildFailure(
5261
InternalLegacyOverride.CreateError("Could not find aws-dynamodb-encryption-python installation")
5362
)
54-
if not isinstance(legacy_override.encryptor, EncryptedClient):
63+
64+
# Precondition: The encryptor MUST be one of the supported legacy types
65+
if not (
66+
isinstance(legacy_override.encryptor, EncryptedClient)
67+
or isinstance(legacy_override.encryptor, EncryptedTable)
68+
or isinstance(legacy_override.encryptor, EncryptedResource)
69+
or isinstance(legacy_override.encryptor, EncryptedPaginator)
70+
):
5571
return InternalLegacyOverride.CreateBuildFailure(
5672
InternalLegacyOverride.CreateError("Legacy encryptor is not supported")
5773
)
@@ -68,7 +84,6 @@ def Build(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
6884

6985
# Create and return the legacy override instance
7086
legacy_instance = InternalLegacyOverride()
71-
legacy_instance.encryptor = legacy_override.encryptor
7287
legacy_instance.policy = legacy_override.policy
7388
legacy_instance.crypto_config = CryptoConfig(
7489
materials_provider=legacy_override.encryptor._materials_provider,
@@ -79,20 +94,16 @@ def Build(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
7994
InternalLegacyOverride.CreateInternalLegacyOverrideSome(legacy_instance)
8095
)
8196

82-
def __init__(self):
83-
super().__init__()
84-
self.encryptor = None
85-
self.crypto_config = None
86-
self.policy = None
87-
8897
@staticmethod
8998
def legacyEncryptionContext(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
9099
"""Create the legacy encryption context from the config."""
91100
try:
92101
# Convert Dafny types to Python strings for the encryption context
93-
table_name = _dafny.string_of(config.logicalTableName)
94-
partition_key_name = _dafny.string_of(config.partitionKeyName)
95-
sort_key_name = _dafny.string_of(config.sortKeyName.value) if config.sortKeyName.is_Some else None
102+
table_name = InternalLegacyOverride.ToNative(config.logicalTableName)
103+
partition_key_name = InternalLegacyOverride.ToNative(config.partitionKeyName)
104+
sort_key_name = (
105+
InternalLegacyOverride.ToNative(config.sortKeyName.value) if config.sortKeyName.is_Some else None
106+
)
96107

97108
# Create the legacy encryption context with the extracted values
98109
encryption_context = EncryptionContext(
@@ -102,22 +113,22 @@ def legacyEncryptionContext(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncr
102113
)
103114

104115
return InternalLegacyOverride.CreateBuildSuccess(encryption_context)
105-
except Exception as e:
106-
# Return a failure with the error message if any exception occurs
107-
return InternalLegacyOverride.CreateBuildFailure(InternalLegacyOverride.CreateError(str(e)))
116+
except Exception as ex:
117+
return InternalLegacyOverride.CreateBuildFailure(Error_Opaque(ex))
108118

109119
@staticmethod
110120
def legacyActions(attribute_actions_on_encrypt):
111121
"""Create the legacy attribute actions from the config."""
112122
try:
113123
# Create a new AttributeActions with default ENCRYPT_AND_SIGN
124+
# Default Action to take if no specific action is defined in ``attribute_actions``
125+
# https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/DDBEC-legacy-concepts.html#legacy-attribute-actions
114126
legacy_actions = AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN)
115127

116128
# Map the action from the config to legacy actions
117129
attribute_actions = {}
118130
for key, action in attribute_actions_on_encrypt.items:
119-
# Convert the string key to Python string
120-
key_str = _dafny.string_of(key)
131+
key_str = InternalLegacyOverride.ToNative(key)
121132

122133
# Map the action type to the appropriate CryptoAction
123134
if action == CryptoAction_ENCRYPT__AND__SIGN():
@@ -134,8 +145,8 @@ def legacyActions(attribute_actions_on_encrypt):
134145
# Update the attribute_actions dictionary
135146
legacy_actions.attribute_actions = attribute_actions
136147
return InternalLegacyOverride.CreateBuildSuccess(legacy_actions)
137-
except Exception as e:
138-
return InternalLegacyOverride.CreateBuildFailure(InternalLegacyOverride.CreateError(str(e)))
148+
except Exception as ex:
149+
return InternalLegacyOverride.CreateBuildFailure(Error_Opaque(ex))
139150

140151
def EncryptItem(self, input: EncryptItemInput_EncryptItemInput):
141152
"""Encrypt an item using the legacy DynamoDB encryptor.
@@ -147,9 +158,9 @@ def EncryptItem(self, input: EncryptItemInput_EncryptItemInput):
147158
Result containing the encrypted item or an error
148159
"""
149160
try:
150-
# Check policy
161+
# Precondition: Policy MUST allow the caller to encrypt.
151162
if not self.policy.is_FORCE__LEGACY__ENCRYPT__ALLOW__LEGACY__DECRYPT:
152-
return Wrappers.Result_Failure(
163+
return self.CreateEncryptItemFailure(
153164
InternalLegacyOverride.CreateError("Legacy policy does not support encrypt")
154165
)
155166

@@ -158,24 +169,23 @@ def EncryptItem(self, input: EncryptItemInput_EncryptItemInput):
158169
input
159170
)
160171

161-
# Use the encryptor to encrypt the item using the instance attributes
162-
encrypted_item = self.encryptor._encrypt_item(
172+
# Encrypt the item using the instance attributes
173+
encrypted_item = encrypt_dynamodb_item(
163174
item=native_input.plaintext_item,
164175
crypto_config=self.crypto_config.with_item(native_input.plaintext_item),
165176
)
166177

167178
# Return the encrypted item
168-
# The legacy encryption client returns items in the format that Dafny expects,
179+
# The legacy encryption method returns items in the format that Dafny expects,
169180
# so no additional conversion is needed here
170181
native_output = EncryptItemOutput(encrypted_item=encrypted_item, parsed_header=None)
171182
dafny_output = aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.smithy_to_dafny.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_EncryptItemOutput(
172183
native_output
173184
)
174-
return Wrappers.Result_Success(dafny_output)
185+
return self.CreateEncryptItemSuccess(dafny_output)
175186

176-
except Exception as e:
177-
# Return an appropriate error result with the exception details
178-
return Wrappers.Result_Failure(InternalLegacyOverride.CreateError(f"Error during encryption: {str(e)}"))
187+
except Exception as ex:
188+
return self.CreateEncryptItemFailure(InternalLegacyOverride.CreateError(Error_Opaque(ex)))
179189

180190
def DecryptItem(self, input: DecryptItemInput_DecryptItemInput):
181191
"""Decrypt an item using the legacy DynamoDB encryptor.
@@ -187,12 +197,17 @@ def DecryptItem(self, input: DecryptItemInput_DecryptItemInput):
187197
Result containing the decrypted item or an error
188198
"""
189199
try:
190-
# Check policy
200+
# Precondition: Policy MUST allow the caller to decrypt.
201+
# = specification/dynamodb-encryption-client/decrypt-item.md#behavior
202+
## If a [Legacy Policy](./ddb-table-encryption-config.md#legacy-policy) of
203+
## `FORBID_LEGACY_ENCRYPT_FORBID_LEGACY_DECRYPT` is configured,
204+
## and the input item [is an item written in the legacy format](#determining-legacy-items),
205+
## this operation MUST fail.
191206
if not (
192207
self.policy.is_FORCE__LEGACY__ENCRYPT__ALLOW__LEGACY__DECRYPT
193208
or self.policy.is_FORBID__LEGACY__ENCRYPT__ALLOW__LEGACY__DECRYPT
194209
):
195-
return Wrappers.Result_Failure(
210+
return self.CreateDecryptItemFailure(
196211
InternalLegacyOverride.CreateError("Legacy policy does not support decrypt")
197212
)
198213

@@ -202,8 +217,8 @@ def DecryptItem(self, input: DecryptItemInput_DecryptItemInput):
202217
input
203218
)
204219
)
205-
# Use the encryptor to decrypt the item using the instance attributes
206-
decrypted_item = self.encryptor._decrypt_item(
220+
# Decrypt the item using the instance attributes
221+
decrypted_item = decrypt_dynamodb_item(
207222
item=native_input.encrypted_item,
208223
crypto_config=self.crypto_config.with_item(native_input.encrypted_item),
209224
)
@@ -212,10 +227,9 @@ def DecryptItem(self, input: DecryptItemInput_DecryptItemInput):
212227
dafny_output = aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.smithy_to_dafny.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_DecryptItemOutput(
213228
native_output
214229
)
215-
return Wrappers.Result_Success(dafny_output)
216-
except Exception as e:
217-
# Return an appropriate error result with the exception details
218-
return Wrappers.Result_Failure(InternalLegacyOverride.CreateError(f"Error during decryption: {str(e)}"))
230+
return self.CreateDecryptItemSuccess(dafny_output)
231+
except Exception as ex:
232+
return self.CreateDecryptItemFailure(InternalLegacyOverride.CreateError(Error_Opaque(ex)))
219233

220234
def IsLegacyInput(self, input: DecryptItemInput_DecryptItemInput):
221235
"""
@@ -227,35 +241,36 @@ def IsLegacyInput(self, input: DecryptItemInput_DecryptItemInput):
227241
Returns:
228242
Boolean indicating if the input is from a legacy client
229243
"""
230-
try:
231-
if not _HAS_LEGACY_DDBEC:
232-
return False
244+
if not input.is_DecryptItemInput:
245+
return False
233246

234-
if not input.is_DecryptItemInput:
235-
return False
236-
237-
# Get the Native DecryptItemInput
238-
native_input: DecryptItemInput = (
239-
aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.dafny_to_smithy.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_DecryptItemInput(
240-
input
241-
)
242-
)
243-
# = specification/dynamodb-encryption-client/decrypt-item.md#determining-legacy-items
244-
## An item MUST be determined to be encrypted under the legacy format if it contains
245-
## attributes for the material description and the signature.
246-
return (
247-
"*amzn-ddb-map-desc*" in native_input.encrypted_item
248-
and "*amzn-ddb-map-sig*" in native_input.encrypted_item
247+
# Get the Native DecryptItemInput
248+
native_input: DecryptItemInput = (
249+
aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.dafny_to_smithy.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_DecryptItemInput(
250+
input
249251
)
252+
)
253+
# = specification/dynamodb-encryption-client/decrypt-item.md#determining-legacy-items
254+
## An item MUST be determined to be encrypted under the legacy format if it contains
255+
## attributes for the material description and the signature.
256+
return (
257+
"*amzn-ddb-map-desc*" in native_input.encrypted_item and "*amzn-ddb-map-sig*" in native_input.encrypted_item
258+
)
250259

251-
except Exception as e:
252-
# If we encounter any error during detection, default to not using legacy
253-
return Wrappers.Result_Failure(InternalLegacyOverride.CreateError(f"Error in IsLegacyInput: {e}"))
260+
@staticmethod
261+
def ToNative(dafny_input):
262+
return b"".join(ord(c).to_bytes(2, "big") for c in dafny_input).decode("utf-16-be")
263+
264+
@staticmethod
265+
def ToDafny(native_input):
266+
return Seq(
267+
"".join([chr(int.from_bytes(pair, "big")) for pair in zip(*[iter(native_input.encode("utf-16-be"))] * 2)])
268+
)
254269

255270
@staticmethod
256271
def CreateError(message):
257272
"""Create an Error with the given message."""
258-
return Error_DynamoDbItemEncryptorException(message)
273+
return Error_DynamoDbItemEncryptorException(InternalLegacyOverride.ToDafny(message))
259274

260275

261276
aws_dbesdk_dynamodb.internaldafny.generated.InternalLegacyOverride.InternalLegacyOverride = InternalLegacyOverride
Lines changed: 29 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,45 @@
1-
# Migration Examples: Legacy DynamoDB Encryption Client to AWS Database Encryption SDK
1+
# DyanmoDb Encryption Client to AWS Database Encryption SDK for DynamoDb Migration
22

3-
These examples demonstrate a complete migration path from the legacy AWS DynamoDB Encryption Client Python library to the new AWS Database Encryption SDK for DynamoDB.
3+
This projects demonstrates the three Steps necessary to migration to the AWS Database Encryption SDK for DynamoDb
4+
if you are currently using the DynamoDb Encryption Client.
45

5-
## Overview
6+
[Step 0](./ddbec/README.md) demonstrates the starting state for your system.
67

7-
The migration process is demonstrated through a series of example steps that show how to gradually transition from the legacy client to the new SDK while maintaining compatibility with previously encrypted data.
8+
## Step 1
89

9-
## Migration Steps
10+
In Step 1, you update your system to do the following:
1011

11-
### Step 0: Legacy DynamoDB Encryption Client
12+
- continue to read items in the old format
13+
- continue to write items in the old format
14+
- prepare to read items in the new format
1215

13-
[migration_step_0.py](./ddbec/migration_step_0.py) demonstrates using the legacy DynamoDB Encryption Client to encrypt and decrypt items. This represents the starting point for migration.
16+
When you deploy changes in Step 1, you should not expect any behavior change in your system,
17+
and your dataset still consists of data written in the old format.
1418

15-
Key concepts:
19+
You must ensure that the changes in Step 1 make it to all your reads before you proceed to step 2.
1620

17-
- Setting up the legacy client with an AWS KMS cryptographic materials provider
18-
- Defining attribute actions for encryption/signing
19-
- Storing and retrieving encrypted items
21+
## Step 2
2022

21-
### Step 1: AWS Database Encryption SDK with Legacy Override
23+
In Step 2, you update your system to do the following:
2224

23-
[migration_step_1.py](./awsdbe/migration_step_1.py) demonstrates how to start using the AWS Database Encryption SDK with a pre-existing table used with the DynamoDB Encryption Client.
25+
- continue to read items in the old format
26+
- start writing items in the new format
27+
- continue to read items in the new format
2428

25-
Key concepts:
29+
When you deploy changes in Step 2, you are introducing a new encryption format to your system,
30+
and must make sure that all your readers are updated with the changes from Step 1.
2631

27-
- Configure AWS DBESDK to read items encrypted in the legacy format
28-
- Continue to encrypt items in the legacy format (FORCE_LEGACY_ENCRYPT_ALLOW_DECRYPT policy)
29-
- Read items encrypted in the new format
30-
- Deploy this step to all readers before moving to step 2
32+
Before you move onto the next step, you will need to re-encrypt all old items in your dataset
33+
to use the newest format. How you will want to do this, and how long you may want to remain in this Step,
34+
depends on your system and your desired security properties for old and new items.
3135

32-
### Step 2: Full Migration to AWS Database Encryption SDK
36+
## Step 3
3337

34-
[migration_step_2.py](./awsdbe/migration_step_2.py) demonstrates the next step in the migration process, using both the pure AWS DBESDK client and the legacy-override client side by side.
38+
Once all old items are re-encrypted to use the new format,
39+
you may update your system to do the following:
3540

36-
Key concepts:
41+
- continue to write items in the new format
42+
- continue to read items in the new format
43+
- do not accept reading items in the old format
3744

38-
- Create a pure AWS DBESDK client for new data
39-
- Keep using legacy-override client when needed for legacy data
40-
- Re-encrypt legacy data with the new client
41-
- Demonstrate that the legacy-override client can read both formats
42-
43-
### Step 3: Complete Migration - Using Only AWS DBESDK
44-
45-
[migration_step_3.py](./awsdbe/migration_step_3.py) demonstrates the final state of the migration, where all data has been re-encrypted using the new format.
46-
47-
Key concepts:
48-
49-
- Use only the pure AWS DBESDK client (no more legacy override)
50-
- Verify all previously re-encrypted data is readable
51-
- Add new data using the pure client
52-
53-
## Prerequisites
54-
55-
Before running these examples:
56-
57-
1. Replace `common.KMS_KEY_ID` with a valid AWS KMS key ID or alias
58-
2. Ensure you have AWS credentials configured with permissions for:
59-
- DynamoDB (CreateTable, PutItem, GetItem, etc.)
60-
- KMS (GenerateDataKey, Decrypt)
61-
3. Have both libraries installed:
62-
- Legacy library: `pip install dynamodb-encryption-sdk`
63-
- New SDK: `pip install aws-dbesdk-dynamodb`
64-
65-
## Important Notes
66-
67-
- These examples create a real DynamoDB table and perform actual AWS KMS operations, which may incur AWS charges
68-
- By default, the examples leave the created table intact when they finish - uncomment the table deletion code in the example scripts if you want to clean up resources
69-
- These examples are focused on demonstrating a migration path and are not production-ready code
45+
Once you have deployed these changes to your system, you have completed migration.

0 commit comments

Comments
 (0)