Skip to content

Commit 21d22dd

Browse files
committed
chore(python): DDBEC Legacy Extern
1 parent 489a4fa commit 21d22dd

File tree

28 files changed

+972
-23
lines changed

28 files changed

+972
-23
lines changed

.github/workflows/ci_examples_python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,4 @@ jobs:
9595
# Run simple examples
9696
tox -e dynamodbencryption
9797
# Run migration examples
98-
# tox -e migration
98+
tox -e migration

DynamoDbEncryption/runtimes/python/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ include = ["**/internaldafny/generated/*.py"]
1313
[tool.poetry.dependencies]
1414
python = "^3.11.0"
1515
aws-cryptographic-material-providers = { path = "../../../submodules/MaterialProviders/AwsCryptographicMaterialProviders/runtimes/python", develop = false}
16+
dynamodb_encryption_sdk = "^3.3.0"
1617

1718
# Package testing
1819

DynamoDbEncryption/runtimes/python/src/aws_dbesdk_dynamodb/internaldafny/extern/InternalLegacyOverride.py

Lines changed: 181 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,38 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from aws_dbesdk_dynamodb.internaldafny.generated.AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes import (
44
DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig,
5+
Error_DynamoDbItemEncryptorException,
6+
EncryptItemOutput_EncryptItemOutput,
7+
DecryptItemOutput_DecryptItemOutput,
8+
DecryptItemInput_DecryptItemInput,
9+
EncryptItemInput_EncryptItemInput,
10+
)
11+
from aws_dbesdk_dynamodb.internaldafny.generated.AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes import (
12+
CryptoAction_ENCRYPT__AND__SIGN,
13+
CryptoAction_SIGN__ONLY,
14+
CryptoAction_DO__NOTHING,
15+
)
16+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.references import (
17+
ILegacyDynamoDbEncryptor,
518
)
619
import smithy_dafny_standard_library.internaldafny.generated.Wrappers as Wrappers
720
import _dafny
821

922
import aws_dbesdk_dynamodb.internaldafny.generated.InternalLegacyOverride
23+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.models import (
24+
EncryptItemInput,
25+
EncryptItemOutput,
26+
DecryptItemOutput,
27+
DecryptItemInput,
28+
)
29+
1030

1131
try:
1232
from dynamodb_encryption_sdk.encrypted.client import EncryptedClient
13-
from dynamodb_encryption_sdk.structures import EncryptionContext
33+
from dynamodb_encryption_sdk.structures import EncryptionContext, AttributeActions
34+
from dynamodb_encryption_sdk.identifiers import CryptoAction
35+
from dynamodb_encryption_sdk.encrypted import CryptoConfig
36+
from dynamodb_encryption_sdk.internal.identifiers import ReservedAttributes
1437

1538
_HAS_LEGACY_DDBEC = True
1639
except ImportError:
@@ -25,16 +48,12 @@ def Build(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
2548

2649
legacy_override = config.legacyOverride.value
2750

28-
maybe_encryptor = aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.dafny_to_smithy.aws_cryptography_dbencryptionsdk_dynamodb_LegacyDynamoDbEncryptorReference(
29-
legacy_override.encryptor
30-
)
31-
3251
# Precondition: The encryptor MUST be a DynamoDBEncryptor
3352
if not _HAS_LEGACY_DDBEC:
3453
return InternalLegacyOverride.CreateBuildFailure(
3554
InternalLegacyOverride.CreateError("Could not find aws-dynamodb-encryption-python installation")
3655
)
37-
if not isinstance(maybe_encryptor, EncryptedClient):
56+
if not isinstance(legacy_override.encryptor, EncryptedClient):
3857
return InternalLegacyOverride.CreateBuildFailure(
3958
InternalLegacyOverride.CreateError("Legacy encryptor is not supported")
4059
)
@@ -49,14 +68,35 @@ def Build(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
4968
if maybe_actions.is_Failure:
5069
return InternalLegacyOverride.CreateBuildFailure(maybe_actions.error())
5170

52-
# TODO: Implement this
71+
# Create and return the legacy override instance
72+
legacy_instance = InternalLegacyOverride()
73+
legacy_instance.encryptor = legacy_override.encryptor
74+
legacy_instance.policy = legacy_override.policy
75+
# # Access the value property, not calling it as a function
76+
# legacy_instance.encryption_context = maybe_encryption_context.value
77+
# # Access the value property, not calling it as a function
78+
# legacy_instance.attribute_actions = maybe_actions.value
79+
legacy_instance.crypto_config = CryptoConfig(
80+
materials_provider=legacy_override.encryptor._materials_provider,
81+
encryption_context=maybe_encryption_context.value,
82+
attribute_actions=maybe_actions.value,
83+
)
84+
return InternalLegacyOverride.CreateBuildSuccess(
85+
InternalLegacyOverride.CreateInternalLegacyOverrideSome(legacy_instance)
86+
)
87+
88+
def __init__(self):
89+
super().__init__()
90+
self.encryptor = None
91+
self.crypto_config = None
92+
self.policy = None
5393

5494
@staticmethod
5595
def legacyEncryptionContext(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncryptorConfig):
5696
try:
5797
encryption_context_kwargs = {
5898
"table_name": _dafny.string_of(config.logicalTableName),
59-
"hash_key_name": _dafny.string_of(config.partitionKeyName),
99+
"partition_key_name": _dafny.string_of(config.partitionKeyName),
60100
}
61101
if config.sortKeyName.is_Some:
62102
encryption_context_kwargs["sort_key_name"] = _dafny.string_of(config.sortKeyName.value)
@@ -67,23 +107,143 @@ def legacyEncryptionContext(config: DynamoDbItemEncryptorConfig_DynamoDbItemEncr
67107

68108
@staticmethod
69109
def legacyActions(attribute_actions_on_encrypt):
70-
# TODO: Implement this
71-
pass
110+
try:
111+
# Create a new AttributeActions with default ENCRYPT_AND_SIGN
112+
legacy_actions = AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN)
113+
114+
# Map the action from the config to legacy actions
115+
attribute_actions = {}
116+
for key, action in attribute_actions_on_encrypt.items:
117+
# Convert the string key to Python string
118+
key_str = _dafny.string_of(key)
119+
120+
# Map the action type to the appropriate CryptoAction
121+
if action == CryptoAction_ENCRYPT__AND__SIGN():
122+
attribute_actions[key_str] = CryptoAction.ENCRYPT_AND_SIGN
123+
elif action == CryptoAction_SIGN__ONLY():
124+
attribute_actions[key_str] = CryptoAction.SIGN_ONLY
125+
elif action == CryptoAction_DO__NOTHING():
126+
attribute_actions[key_str] = CryptoAction.DO_NOTHING
127+
else:
128+
return InternalLegacyOverride.CreateBuildFailure(
129+
InternalLegacyOverride.CreateError(f"Unknown action type: {action}")
130+
)
131+
132+
# Update the attribute_actions dictionary
133+
legacy_actions.attribute_actions = attribute_actions
134+
return InternalLegacyOverride.CreateBuildSuccess(legacy_actions)
135+
except Exception as e:
136+
return InternalLegacyOverride.CreateBuildFailure(InternalLegacyOverride.CreateError(str(e)))
72137

73-
@staticmethod
74-
def EncryptItem(input):
75-
# TODO: Implement this
76-
return Wrappers.Result_Failure("TODO-legacy-Encryptitem")
138+
def EncryptItem(self, input: EncryptItemInput_EncryptItemInput):
139+
"""Encrypt an item using the legacy DynamoDB encryptor.
77140
78-
@staticmethod
79-
def DecryptItem(input):
80-
# TODO: Implement this
81-
return Wrappers.Result_Failure("TODO-legacy-Decryptitem")
141+
Args:
142+
input: EncryptItemInput containing the plaintext item to encrypt
143+
144+
Returns:
145+
Result containing the encrypted item or an error
146+
"""
147+
try:
148+
# Check policy
149+
if not self.policy.is_FORCE__LEGACY__ENCRYPT__ALLOW__LEGACY__DECRYPT:
150+
return Wrappers.Result_Failure(
151+
InternalLegacyOverride.CreateError("Legacy policy does not support encrypt")
152+
)
153+
154+
# Get the Native Plaintext Item
155+
native_input = aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.dafny_to_smithy.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_EncryptItemInput(
156+
input
157+
)
158+
159+
# Use the encryptor to encrypt the item using the instance attributes
160+
encrypted_item = self.encryptor._encrypt_item(
161+
item=native_input.plaintext_item, crypto_config=self.crypto_config
162+
)
163+
164+
# Return the encrypted item
165+
# The legacy encryption client returns items in the format that Dafny expects,
166+
# so no additional conversion is needed here
167+
native_output = EncryptItemOutput(encrypted_item=encrypted_item, parsed_header=None)
168+
dafny_output = aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.smithy_to_dafny.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_EncryptItemOutput(
169+
native_output
170+
)
171+
return Wrappers.Result_Success(dafny_output)
172+
173+
except Exception as e:
174+
# Return an appropriate error result with the exception details
175+
return Wrappers.Result_Failure(InternalLegacyOverride.CreateError(f"Error during encryption: {str(e)}"))
176+
177+
def DecryptItem(self, input: DecryptItemInput_DecryptItemInput):
178+
"""Decrypt an item using the legacy DynamoDB encryptor.
179+
180+
Args:
181+
input: DecryptItemInput containing the encrypted item to decrypt
182+
183+
Returns:
184+
Result containing the decrypted item or an error
185+
"""
186+
try:
187+
# Check policy
188+
if not (
189+
self.policy.is_FORCE__LEGACY__ENCRYPT__ALLOW__LEGACY__DECRYPT
190+
or self.policy.is_FORBID__LEGACY__ENCRYPT__ALLOW__LEGACY__DECRYPT
191+
):
192+
return Wrappers.Result_Failure(
193+
InternalLegacyOverride.CreateError("Legacy policy does not support decrypt")
194+
)
195+
196+
# Get the Native DecryptItemInput
197+
native_input: DecryptItemInput = (
198+
aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.dafny_to_smithy.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_DecryptItemInput(
199+
input
200+
)
201+
)
202+
# Use the encryptor to decrypt the item using the instance attributes
203+
decrypted_item = self.encryptor._decrypt_item(
204+
item=native_input.encrypted_item, crypto_config=self.crypto_config
205+
)
206+
207+
native_output = DecryptItemOutput(plaintext_item=decrypted_item, parsed_header=None)
208+
dafny_output = aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.smithy_to_dafny.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_DecryptItemOutput(
209+
native_output
210+
)
211+
return Wrappers.Result_Success(dafny_output)
212+
except Exception as e:
213+
# Return an appropriate error result with the exception details
214+
return Wrappers.Result_Failure(InternalLegacyOverride.CreateError(f"Error during decryption: {str(e)}"))
215+
216+
def IsLegacyInput(self, input: DecryptItemInput_DecryptItemInput):
217+
"""Determine if the input is from a legacy client."""
218+
try:
219+
if not _HAS_LEGACY_DDBEC:
220+
return False
221+
222+
if not input.is_DecryptItemInput:
223+
return False
224+
225+
# Get the Native DecryptItemInput
226+
native_input: DecryptItemInput = (
227+
aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor.dafny_to_smithy.aws_cryptography_dbencryptionsdk_dynamodb_itemencryptor_DecryptItemInput(
228+
input
229+
)
230+
)
231+
# = specification/dynamodb-encryption-client/decrypt-item.md#determining-legacy-items
232+
## An item MUST be determined to be encrypted under the legacy format if it contains
233+
## attributes for the material description and the signature.
234+
return (
235+
"*amzn-ddb-map-desc*" in native_input.encrypted_item
236+
and "*amzn-ddb-map-sig*" in native_input.encrypted_item
237+
)
238+
239+
except Exception as e:
240+
# If we encounter any error during detection, default to not using legacy
241+
return Wrappers.Result_Failure(InternalLegacyOverride.CreateError(f"Error in IsLegacyInput: {e}"))
82242

83243
@staticmethod
84-
def IsLegacyinput(input):
85-
# TODO: Implement this
86-
return False
244+
def CreateError(message):
245+
"""Create an Error with the given message."""
246+
return Error_DynamoDbItemEncryptorException(message)
87247

88248

89249
aws_dbesdk_dynamodb.internaldafny.generated.InternalLegacyOverride.InternalLegacyOverride = InternalLegacyOverride

DynamoDbEncryption/runtimes/python/tox.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ commands =
6262
ruff check \
6363
src/aws_dbesdk_dynamodb/ \
6464
../../../Examples/runtimes/python/DynamoDBEncryption/ \
65+
../../../Examples/runtimes/python/Migration/ \
6566
test/ \
6667
{posargs}
6768

@@ -75,6 +76,7 @@ commands =
7576
black --line-length 120 \
7677
src/aws_dbesdk_dynamodb/ \
7778
../../../Examples/runtimes/python/DynamoDBEncryption/ \
79+
../../../Examples/runtimes/python/Migration/ \
7880
test/ \
7981
{posargs}
8082

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Python build artifacts
2+
__pycache__
3+
**/__pycache__
4+
*.pyc
5+
src/**.egg-info/
6+
build
7+
poetry.lock
8+
**/poetry.lock
9+
dist
10+
11+
# Dafny-generated Python
12+
**/internaldafny/generated/*.py
13+
14+
# Python test artifacts
15+
.tox
16+
.pytest_cache
17+
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: 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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Migration Examples: Legacy DynamoDB Encryption Client to AWS Database Encryption SDK
2+
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.
4+
5+
## Overview
6+
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+
9+
## Migration Steps
10+
11+
### Step 0: Legacy DynamoDB Encryption Client
12+
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.
14+
15+
Key concepts:
16+
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
20+
21+
### Step 1: AWS Database Encryption SDK with Legacy Override
22+
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.
24+
25+
Key concepts:
26+
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
31+
32+
### Step 2: Full Migration to AWS Database Encryption SDK
33+
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.
35+
36+
Key concepts:
37+
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
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: 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."""

0 commit comments

Comments
 (0)