diff --git a/.github/workflows/ci_examples_python.yml b/.github/workflows/ci_examples_python.yml index 83a79ff3a..aeab583eb 100644 --- a/.github/workflows/ci_examples_python.yml +++ b/.github/workflows/ci_examples_python.yml @@ -96,3 +96,5 @@ jobs: tox -e dynamodbencryption # Run legacy migration examples tox -e legacymigration + # Run plaintext migration examples + tox -e plaintextmigration diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/README.md b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/README.md new file mode 100644 index 000000000..9f44c9fe3 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/README.md @@ -0,0 +1,51 @@ +# Plaintext to AWS Database Encryption SDK for DynamoDB Migration + +This project demonstrates the steps necessary +to migrate to the AWS Database Encryption SDK for DynamoDb +from a plaintext database. + +[Step 0](./plaintext/README.md) demonstrates the starting state for your system. + +## Step 1 + +In Step 1, you update your system to do the following: + +- continue to read plaintext items +- continue to write plaintext items +- prepare to read encrypted items + +When you deploy changes in Step 1, +you should not expect any behavior change in your system, +and your dataset still consists of plaintext data. + +You must ensure that the changes in Step 1 make it to all your readers before you proceed to Step 2. + +## Step 2 + +In Step 2, you update your system to do the following: + +- continue to read plaintext items +- start writing encrypted items +- continue to read encrypted items + +Before you deploy changes in Step 2, +you are introducing encrypted items to your system, +and must make sure that all your readers are updated with the changes from Step 1. + +Before you move onto the next step, you will need to encrypt all plaintext items in your dataset. +Once you have completed this step, +while new items are encrypted and will be authenticated on read, +your system will still accept reading plaintext, unauthenticated items. +In order to complete migration to a system where you always authenticate your items, +you should prioritize moving on to Step 3. + +## Step 3 + +Once all plaintext items are encrypted, +update your system to do the following: + +- continue to write encrypted items +- continue to read encrypted items +- do not accept reading plaintext items + +Once you have deployed these changes to your system, you have completed migration. diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/common.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/common.py new file mode 100644 index 000000000..a7baa5cc2 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/common.py @@ -0,0 +1,206 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Common Utilities for Migration Examples.""" +import boto3 +from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig +from aws_cryptographic_material_providers.mpl.models import ( + CreateAwsKmsMrkMultiKeyringInput, + DBEAlgorithmSuiteId, +) +from aws_cryptographic_material_providers.mpl.references import IKeyring +from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient +from aws_dbesdk_dynamodb.structures.dynamodb import ( + DynamoDbTableEncryptionConfig, + DynamoDbTablesEncryptionConfig, +) +from aws_dbesdk_dynamodb.structures.structured_encryption import ( + CryptoAction, +) + + +def setup_awsdbe_client_without_plaintext_override(kms_key_id: str, ddb_table_name: str): + """ + Set up a pure AWS Database Encryption SDK EncryptedClient without plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :returns EncryptedClient for DynamoDB + """ + # 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + # For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + # We will use the `CreateMrkMultiKeyring` method to create this keyring, + # as it will correctly handle both single region and Multi-Region KMS Keys. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig()) + kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput( + generator=kms_key_id, + ) + kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input) + + # 2. Configure which attributes are encrypted and/or signed when writing new items. + # For each attribute that may exist on the items we plan to write to our DynamoDbTable, + # we must explicitly configure how they should be treated during item encryption: + # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + # - DO_NOTHING: The attribute is not encrypted and not included in the signature + attribute_actions_on_encrypt = { + "partition_key": CryptoAction.SIGN_ONLY, + "sort_key": CryptoAction.SIGN_ONLY, + "attribute1": CryptoAction.ENCRYPT_AND_SIGN, + "attribute2": CryptoAction.SIGN_ONLY, + ":attribute3": CryptoAction.DO_NOTHING, + } + + # 3. Configure which attributes we expect to be included in the signature + # when reading items. There are two options for configuring this: + # + # - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + # When defining your DynamoDb schema and deciding on attribute names, + # choose a distinguishing prefix (such as ":") for all attributes that + # you do not want to include in the signature. + # This has two main benefits: + # - It is easier to reason about the security and authenticity of data within your item + # when all unauthenticated data is easily distinguishable by their attribute name. + # - If you need to add new unauthenticated attributes in the future, + # you can easily make the corresponding update to your `attributeActionsOnEncrypt` + # and immediately start writing to that new attribute, without + # any other configuration update needed. + # Once you configure this field, it is not safe to update it. + # + # - Configure `allowedUnsignedAttributes`: You may also explicitly list + # a set of attributes that should be considered unauthenticated when encountered + # on read. Be careful if you use this configuration. Do not remove an attribute + # name from this configuration, even if you are no longer writing with that attribute, + # as old items may still include this attribute, and our configuration needs to know + # to continue to exclude this attribute from the signature scope. + # If you add new attribute names to this field, you must first deploy the update to this + # field to all readers in your host fleet before deploying the update to start writing + # with that new attribute. + # + # For this example, we have designed our DynamoDb table such that any attribute name with + # the ":" prefix should be considered unauthenticated. + unsignAttrPrefix: str = ":" + + # 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + table_configs = {} + table_config = DynamoDbTableEncryptionConfig( + logical_table_name=ddb_table_name, + partition_key_name="partition_key", + sort_key_name="sort_key", + attribute_actions_on_encrypt=attribute_actions_on_encrypt, + keyring=kms_mrk_multi_keyring, + allowed_unsigned_attribute_prefix=unsignAttrPrefix, + # Specifying an algorithm suite is not required, + # but is done here to demonstrate how to do so. + # We suggest using the + # `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + # which includes AES-GCM with key derivation, signing, and key commitment. + # This is also the default algorithm suite if one is not specified in this config. + # For more information on supported algorithm suites, see: + # https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384, + ) + table_configs[ddb_table_name] = table_config + tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs) + + # 5. Create the EncryptedClient + return EncryptedClient( + client=boto3.client("dynamodb"), + encryption_config=tables_config, + ) + + +def setup_awsdbe_client_with_plaintext_override(kms_key_id: str, ddb_table_name: str, policy: str): + """ + Set up an AWS Database Encryption SDK EncryptedClient with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param policy: The policy required for the Plaintext Override configuration + :returns EncryptedClient for DynamoDB + + """ + # 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + # For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + # We will use the `CreateMrkMultiKeyring` method to create this keyring, + # as it will correctly handle both single region and Multi-Region KMS Keys. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig()) + kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput( + generator=kms_key_id, + ) + kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input) + + # 2. Configure which attributes are encrypted and/or signed when writing new items. + # For each attribute that may exist on the items we plan to write to our DynamoDbTable, + # we must explicitly configure how they should be treated during item encryption: + # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + # - DO_NOTHING: The attribute is not encrypted and not included in the signature + attribute_actions_on_encrypt = { + "partition_key": CryptoAction.SIGN_ONLY, + "sort_key": CryptoAction.SIGN_ONLY, + "attribute1": CryptoAction.ENCRYPT_AND_SIGN, + "attribute2": CryptoAction.SIGN_ONLY, + ":attribute3": CryptoAction.DO_NOTHING, + } + + # 3. Configure which attributes we expect to be included in the signature + # when reading items. There are two options for configuring this: + # + # - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + # When defining your DynamoDb schema and deciding on attribute names, + # choose a distinguishing prefix (such as ":") for all attributes that + # you do not want to include in the signature. + # This has two main benefits: + # - It is easier to reason about the security and authenticity of data within your item + # when all unauthenticated data is easily distinguishable by their attribute name. + # - If you need to add new unauthenticated attributes in the future, + # you can easily make the corresponding update to your `attributeActionsOnEncrypt` + # and immediately start writing to that new attribute, without + # any other configuration update needed. + # Once you configure this field, it is not safe to update it. + # + # - Configure `allowedUnsignedAttributes`: You may also explicitly list + # a set of attributes that should be considered unauthenticated when encountered + # on read. Be careful if you use this configuration. Do not remove an attribute + # name from this configuration, even if you are no longer writing with that attribute, + # as old items may still include this attribute, and our configuration needs to know + # to continue to exclude this attribute from the signature scope. + # If you add new attribute names to this field, you must first deploy the update to this + # field to all readers in your host fleet before deploying the update to start writing + # with that new attribute. + # + # For this example, we have designed our DynamoDb table such that any attribute name with + # the ":" prefix should be considered unauthenticated. + unsignAttrPrefix: str = ":" + + # 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + # Include the plaintext override with the provided policy + table_configs = {} + table_config = DynamoDbTableEncryptionConfig( + logical_table_name=ddb_table_name, + partition_key_name="partition_key", + sort_key_name="sort_key", + attribute_actions_on_encrypt=attribute_actions_on_encrypt, + keyring=kms_mrk_multi_keyring, + # Provide plaintext_override policy to the config here + plaintext_override=policy, + allowed_unsigned_attribute_prefix=unsignAttrPrefix, + # Specifying an algorithm suite is not required, + # but is done here to demonstrate how to do so. + # We suggest using the + # `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + # which includes AES-GCM with key derivation, signing, and key commitment. + # This is also the default algorithm suite if one is not specified in this config. + # For more information on supported algorithm suites, see: + # https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384, + ) + table_configs[ddb_table_name] = table_config + tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs) + + # 5. Create the EncryptedClient + return EncryptedClient( + client=boto3.client("dynamodb"), + encryption_config=tables_config, + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_1.py new file mode 100644 index 000000000..23d7882d0 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_1.py @@ -0,0 +1,90 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 1. + +This is an example demonstrating how to start using the +AWS Database Encryption SDK with a pre-existing table with plaintext items. +In this example, we configure an EncryptedClient to do the following: + - Write items only in plaintext + - Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration + +While this step configures your client to be ready to start reading encrypted items, +we do not yet expect to be reading any encrypted items, +as our client still writes plaintext items. +Before you move on to step 2, ensure that these changes have successfully been deployed +to all of your readers. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +""" +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from .common import setup_awsdbe_client_with_plaintext_override + + +def migration_step_1_with_client(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1): + """ + Migration Step 1: Using the AWS Database Encryption SDK with Plaintext Override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedClient with plaintext override. + # For Plaintext Override, use `FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forced to be written as plaintext. + # Items may not be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_client = setup_awsdbe_client_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Put an item into your table using the Encrypted Client. + # This item will be stored in plaintext since we are using a plaintext override + # with FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ policy + item_to_encrypt = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": str(1)}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_request = { + "TableName": ddb_table_name, + "Item": item_to_encrypt, + } + + put_item_response = encrypted_client.put_item(**put_item_request) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the Encrypted Client. + # If this is an item written in plaintext (e.g. any item written + # during Step 0 or 1), then it will be returned as plaintext. + # If this is an encrypted item (e.g. any item written during Step 2 or after), + # then it will be decrypted before being returned. + key_to_get = {"partition_key": {"S": "PlaintextMigrationExample"}, "sort_key": {"N": str(sort_read_value)}} + + get_item_request = {"TableName": ddb_table_name, "Key": key_to_get} + get_item_response = encrypted_client.get_item(**get_item_request) + + # Demonstrate that GetItem succeeded and returned the decrypted item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + returned_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert returned_item["partition_key"]["S"] == "PlaintextMigrationExample" + assert returned_item["sort_key"]["N"] == str(sort_read_value) + assert returned_item["attribute1"]["S"] == "encrypt and sign me!" + assert returned_item["attribute2"]["S"] == "sign me!" + assert returned_item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_2.py new file mode 100644 index 000000000..3e9d44a79 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_2.py @@ -0,0 +1,88 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 2. + +This is an example demonstrating how to update your configuration +to start writing items using the AWS Database Encryption SDK for DynamoDB, +but still continue to read any items written in plaintext. + +Once you deploy this change to your system, you will have a dataset +containing items in both plaintext and encrypted formats. +Because the changes in Step 1 have been deployed to all our readers, +we can be sure that our entire system is ready to read this new data. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +""" + +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from .common import setup_awsdbe_client_with_plaintext_override + + +def migration_step_2_with_client(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 2): + """ + Migration Step 2: Using AWS Database Encryption SDK for DynamoDB with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedClient with plaintext override. + # When configuring our plaintext override, use `FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forbidden to be written as plaintext. + # Items will be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_client = setup_awsdbe_client_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Put an item into your table using the Encrypted Client. + # This item will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + item_to_encrypt = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": str(2)}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_request = { + "TableName": ddb_table_name, + "Item": item_to_encrypt, + } + + put_item_response = encrypted_client.put_item(**put_item_request) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the Encrypted Client. + # If this is an item written in plaintext (e.g. any item written + # during Step 0 or 1), then it will be returned as plaintext. + # If this is an encrypted item (e.g. any item written during Step 2 or after), + # then it will be decrypted before being returned. + key_to_get = {"partition_key": {"S": "PlaintextMigrationExample"}, "sort_key": {"N": str(sort_read_value)}} + + get_item_request = {"TableName": ddb_table_name, "Key": key_to_get} + get_item_response = encrypted_client.get_item(**get_item_request) + + # Demonstrate that GetItem succeeded and returned the decrypted/plaintext item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + returned_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert returned_item["partition_key"]["S"] == "PlaintextMigrationExample" + assert returned_item["sort_key"]["N"] == str(sort_read_value) + assert returned_item["attribute1"]["S"] == "encrypt and sign me!" + assert returned_item["attribute2"]["S"] == "sign me!" + assert returned_item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_3.py new file mode 100644 index 000000000..119f55961 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/client/migration_step_3.py @@ -0,0 +1,72 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 3. + +This is an example demonstrating how to update your configuration +to stop accepting reading items in plaintext format. + +Once you complete Step 3, all items being read by your system are encrypted. + +Before you move onto this step, you will need to encrypt all plaintext items in your dataset. +How you will want to do this depends on your system. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +""" + +from .common import setup_awsdbe_client_without_plaintext_override + + +def migration_step_3_with_client(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 3): + """ + Migration Step 3: Using only pure AWS Database Encryption SDK (no plaintext override) with EncryptedClient. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create the EncryptedClient. + # Do not configure any plaintext override. + encrypted_client = setup_awsdbe_client_without_plaintext_override(kms_key_id, ddb_table_name) + + # 2. Put an item into your table using the Encrypted Client. + # This item will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + item = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": str(3)}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_response = encrypted_client.put_item(TableName=ddb_table_name, Item=item) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the Encrypted Client. + # If this is an item written in plaintext (e.g. any item written + # during Step 0 or 1), then we will fail to process the item. + # If this is an encrypted item (e.g. any item written during Step 2 or after), + # then we will decrypt the item and return it. + key_to_get = {"partition_key": {"S": "PlaintextMigrationExample"}, "sort_key": {"N": str(sort_read_value)}} + + get_item_request = {"TableName": ddb_table_name, "Key": key_to_get} + get_item_response = encrypted_client.get_item(**get_item_request) + + # Demonstrate that GetItem succeeded and returned the decrypted item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + decrypted_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert decrypted_item["partition_key"]["S"] == "PlaintextMigrationExample" + assert decrypted_item["sort_key"]["N"] == str(sort_read_value) + assert decrypted_item["attribute1"]["S"] == "encrypt and sign me!" + assert decrypted_item["attribute2"]["S"] == "sign me!" + assert decrypted_item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_1.py new file mode 100644 index 000000000..16f3bb33d --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_1.py @@ -0,0 +1,98 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 1. + +This is an example demonstrating how to start using the +AWS Database Encryption SDK with a pre-existing table with plaintext items. +In this example, we configure an EncryptedClient to do the following: + - Write items only in plaintext + - Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration + +While this step configures your client to be ready to start reading encrypted items, +we do not yet expect to be reading any encrypted items, +as our client still writes plaintext items. +Before you move on to step 2, ensure that these changes have successfully been deployed +to all of your readers. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from ..client.common import setup_awsdbe_client_with_plaintext_override + + +def migration_step_1_with_paginator(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1): + """ + Migration Step 1: Using the AWS Database Encryption SDK with Plaintext Override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedClient with plaintext override. + # For Plaintext Override, use `FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forced to be written as plaintext. + # Items may not be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_client = setup_awsdbe_client_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Put an example item into our DynamoDb table using the Encrypted Client. + # This item will be stored in plaintext since we are using a plaintext override + # with FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ policy + item = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": "1"}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_request = { + "TableName": ddb_table_name, + "Item": item, + } + + put_item_response = encrypted_client.put_item(**put_item_request) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get a paginator from the encrypted client + encrypted_paginator = encrypted_client.get_paginator("query") + + # 4. Use the paginator to get items from the table + # If these are items written in plaintext (e.g. any items written + # during Step 0 or 1), then they will be returned as plaintext. + # If these are encrypted items (e.g. any items written during Step 2 or after), + # then they will be decrypted before being returned. + items = [] + for page in encrypted_paginator.paginate( + TableName=ddb_table_name, + KeyConditionExpression="partition_key = :partition_key AND sort_key = :sort_key", + ExpressionAttributeValues={ + ":partition_key": {"S": "PlaintextMigrationExample"}, + ":sort_key": {"N": str(sort_read_value)}, + }, + ): + for item in page["Items"]: + items.append(item) + + # 5. Demonstrate we get the expected items back + assert len(items) == 1 # We should have only one item with above key condition + item = next((i for i in items if i["sort_key"]["N"] == str(sort_read_value)), None) + assert item is not None + assert item["partition_key"]["S"] == "PlaintextMigrationExample" + assert item["attribute1"]["S"] == "encrypt and sign me!" + assert item["attribute2"]["S"] == "sign me!" + assert item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_2.py new file mode 100644 index 000000000..31e788538 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_2.py @@ -0,0 +1,96 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 2. + +This is an example demonstrating how to update your configuration +to start writing items using the AWS Database Encryption SDK for DynamoDB, +but still continue to read any items written in plaintext. + +Once you deploy this change to your system, you will have a dataset +containing items in both plaintext and encrypted formats. +Because the changes in Step 1 have been deployed to all our readers, +we can be sure that our entire system is ready to read this new data. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from ..client.common import setup_awsdbe_client_with_plaintext_override + + +def migration_step_2_with_paginator(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 2): + """ + Migration Step 2: Using AWS Database Encryption SDK for DynamoDB with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedClient with plaintext override. + # When configuring our plaintext override, use `FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forbidden to be written as plaintext. + # Items will be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_client = setup_awsdbe_client_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Put an example item into our DynamoDb table using the Encrypted Client. + # This item will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + item = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": "2"}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_request = { + "TableName": ddb_table_name, + "Item": item, + } + + put_item_response = encrypted_client.put_item(**put_item_request) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get the EncryptedPaginator from the EncryptedClient + encrypted_paginator = encrypted_client.get_paginator("query") + + # 4. Use the paginator to get items from the table + # If these are items written in plaintext (e.g. any items written + # during Step 0 or 1), then they will be returned as plaintext. + # If these are encrypted items (e.g. any items written during Step 2 or after), + # then they will be decrypted before being returned. + items = [] + for page in encrypted_paginator.paginate( + TableName=ddb_table_name, + KeyConditionExpression="partition_key = :partition_key AND sort_key = :sort_key", + ExpressionAttributeValues={ + ":partition_key": {"S": "PlaintextMigrationExample"}, + ":sort_key": {"N": str(sort_read_value)}, + }, + ): + for item in page["Items"]: + items.append(item) + + # 5. Demonstrate we get the expected items back + assert len(items) == 1 # We should have only one item with above key condition + item = next((i for i in items if i["sort_key"]["N"] == str(sort_read_value)), None) + assert item is not None + assert item["partition_key"]["S"] == "PlaintextMigrationExample" + assert item["attribute1"]["S"] == "encrypt and sign me!" + assert item["attribute2"]["S"] == "sign me!" + assert item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_3.py new file mode 100644 index 000000000..aa0e839f0 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/paginator/migration_step_3.py @@ -0,0 +1,91 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 3. + +This is an example demonstrating how to update your configuration +to complete your migration from plaintext to encrypted DynamoDB. +In this step, the client will only read items that have been encrypted with +the AWS Database Encryption SDK for DynamoDB. This step assumes that you have +already completed steps 1 and 2, and have converted all plaintext data in +your table to encrypted data. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +from ..client.common import setup_awsdbe_client_without_plaintext_override + + +def migration_step_3_with_paginator(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 3): + """ + Migration Step 3: Using AWS Database Encryption SDK for DynamoDB without plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedClient without plaintext override. + # Without a plaintext override, both reads and writes will use the AWS Database Encryption SDK, + # meaning that we can only read items that have been encrypted and we will always encrypt items on write. + encrypted_client = setup_awsdbe_client_without_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name + ) + + # 2. Put an example item into our DynamoDb table using the Encrypted Client. + # This item will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + item = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": "3"}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_request = { + "TableName": ddb_table_name, + "Item": item, + } + + put_item_response = encrypted_client.put_item(**put_item_request) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get a paginator from the encrypted client + encrypted_paginator = encrypted_client.get_paginator("query") + + # 4. Use the paginator to get items from the table + # If these are items written in plaintext (e.g. any items written + # during Step 0 or 1), then we will fail to process the items. + # If these are encrypted items (e.g. any items written during Step 2 or after), + # then they will be decrypted before being returned. + items = [] + for page in encrypted_paginator.paginate( + TableName=ddb_table_name, + KeyConditionExpression="partition_key = :partition_key AND sort_key = :sort_key", + ExpressionAttributeValues={ + ":partition_key": {"S": "PlaintextMigrationExample"}, + ":sort_key": {"N": str(sort_read_value)}, + }, + ): + for item in page["Items"]: + items.append(item) + + # 5. Demonstrate we get the expected items back + assert len(items) == 1 # We should have only one item with above key condition + item = next((i for i in items if i["sort_key"]["N"] == str(sort_read_value)), None) + assert item is not None + assert item["partition_key"]["S"] == "PlaintextMigrationExample" + assert item["attribute1"]["S"] == "encrypt and sign me!" + assert item["attribute2"]["S"] == "sign me!" + assert item[":attribute3"]["S"] == "ignore me!" + + # Note: If we tried to query for items with sort_key = 1 or sort_key = 2 that were + # written with the legacy format in previous migration steps and haven't been + # re-encrypted, the operation would fail with a verification exception. diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/common.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/common.py new file mode 100644 index 000000000..ce025aabc --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/common.py @@ -0,0 +1,214 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Common Utilities for Migration Examples.""" +import boto3 +from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig +from aws_cryptographic_material_providers.mpl.models import ( + CreateAwsKmsMrkMultiKeyringInput, + DBEAlgorithmSuiteId, +) +from aws_cryptographic_material_providers.mpl.references import IKeyring +from aws_dbesdk_dynamodb.encrypted.resource import EncryptedResource +from aws_dbesdk_dynamodb.structures.dynamodb import ( + DynamoDbTableEncryptionConfig, + DynamoDbTablesEncryptionConfig, +) +from aws_dbesdk_dynamodb.structures.structured_encryption import ( + CryptoAction, +) + + +def setup_awsdbe_resource_without_plaintext_override(kms_key_id: str, ddb_table_name: str): + """ + Set up a pure AWS Database Encryption SDK EncryptedResource without plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :returns EncryptedResource for DynamoDB + """ + # 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + # For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + # We will use the `CreateMrkMultiKeyring` method to create this keyring, + # as it will correctly handle both single region and Multi-Region KMS Keys. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig()) + kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput( + generator=kms_key_id, + ) + kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input) + + # 2. Configure which attributes are encrypted and/or signed when writing new items. + # For each attribute that may exist on the items we plan to write to our DynamoDbTable, + # we must explicitly configure how they should be treated during item encryption: + # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + # - DO_NOTHING: The attribute is not encrypted and not included in the signature + attribute_actions_on_encrypt = { + "partition_key": CryptoAction.SIGN_ONLY, + "sort_key": CryptoAction.SIGN_ONLY, + "attribute1": CryptoAction.ENCRYPT_AND_SIGN, + "attribute2": CryptoAction.SIGN_ONLY, + ":attribute3": CryptoAction.DO_NOTHING, + } + + # 3. Configure which attributes we expect to be included in the signature + # when reading items. There are two options for configuring this: + # + # - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + # When defining your DynamoDb schema and deciding on attribute names, + # choose a distinguishing prefix (such as ":") for all attributes that + # you do not want to include in the signature. + # This has two main benefits: + # - It is easier to reason about the security and authenticity of data within your item + # when all unauthenticated data is easily distinguishable by their attribute name. + # - If you need to add new unauthenticated attributes in the future, + # you can easily make the corresponding update to your `attributeActionsOnEncrypt` + # and immediately start writing to that new attribute, without + # any other configuration update needed. + # Once you configure this field, it is not safe to update it. + # + # - Configure `allowedUnsignedAttributes`: You may also explicitly list + # a set of attributes that should be considered unauthenticated when encountered + # on read. Be careful if you use this configuration. Do not remove an attribute + # name from this configuration, even if you are no longer writing with that attribute, + # as old items may still include this attribute, and our configuration needs to know + # to continue to exclude this attribute from the signature scope. + # If you add new attribute names to this field, you must first deploy the update to this + # field to all readers in your host fleet before deploying the update to start writing + # with that new attribute. + # + # For this example, we have designed our DynamoDb table such that any attribute name with + # the ":" prefix should be considered unauthenticated. + unsignAttrPrefix: str = ":" + + # 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + table_configs = {} + table_config = DynamoDbTableEncryptionConfig( + logical_table_name=ddb_table_name, + partition_key_name="partition_key", + sort_key_name="sort_key", + attribute_actions_on_encrypt=attribute_actions_on_encrypt, + keyring=kms_mrk_multi_keyring, + allowed_unsigned_attribute_prefix=unsignAttrPrefix, + # Specifying an algorithm suite is not required, + # but is done here to demonstrate how to do so. + # We suggest using the + # `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + # which includes AES-GCM with key derivation, signing, and key commitment. + # This is also the default algorithm suite if one is not specified in this config. + # For more information on supported algorithm suites, see: + # https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384, + ) + table_configs[ddb_table_name] = table_config + tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs) + + # 5. Create the EncryptedResource + # Create a DynamoDB resource + ddb_resource = boto3.resource("dynamodb") + + # Create an EncryptedResource using the DynamoDB resource + return EncryptedResource( + resource=ddb_resource, + encryption_config=tables_config, + ) + + +def setup_awsdbe_resource_with_plaintext_override(kms_key_id: str, ddb_table_name: str, policy: str): + """ + Set up an AWS Database Encryption SDK EncryptedResource with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param policy: The policy required for the Plaintext Override configuration + :returns EncryptedResource for DynamoDB + + """ + # 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + # For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + # We will use the `CreateMrkMultiKeyring` method to create this keyring, + # as it will correctly handle both single region and Multi-Region KMS Keys. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig()) + kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput( + generator=kms_key_id, + ) + kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input) + + # 2. Configure which attributes are encrypted and/or signed when writing new items. + # For each attribute that may exist on the items we plan to write to our DynamoDbTable, + # we must explicitly configure how they should be treated during item encryption: + # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + # - DO_NOTHING: The attribute is not encrypted and not included in the signature + attribute_actions_on_encrypt = { + "partition_key": CryptoAction.SIGN_ONLY, + "sort_key": CryptoAction.SIGN_ONLY, + "attribute1": CryptoAction.ENCRYPT_AND_SIGN, + "attribute2": CryptoAction.SIGN_ONLY, + ":attribute3": CryptoAction.DO_NOTHING, + } + + # 3. Configure which attributes we expect to be included in the signature + # when reading items. There are two options for configuring this: + # + # - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + # When defining your DynamoDb schema and deciding on attribute names, + # choose a distinguishing prefix (such as ":") for all attributes that + # you do not want to include in the signature. + # This has two main benefits: + # - It is easier to reason about the security and authenticity of data within your item + # when all unauthenticated data is easily distinguishable by their attribute name. + # - If you need to add new unauthenticated attributes in the future, + # you can easily make the corresponding update to your `attributeActionsOnEncrypt` + # and immediately start writing to that new attribute, without + # any other configuration update needed. + # Once you configure this field, it is not safe to update it. + # + # - Configure `allowedUnsignedAttributes`: You may also explicitly list + # a set of attributes that should be considered unauthenticated when encountered + # on read. Be careful if you use this configuration. Do not remove an attribute + # name from this configuration, even if you are no longer writing with that attribute, + # as old items may still include this attribute, and our configuration needs to know + # to continue to exclude this attribute from the signature scope. + # If you add new attribute names to this field, you must first deploy the update to this + # field to all readers in your host fleet before deploying the update to start writing + # with that new attribute. + # + # For this example, we have designed our DynamoDb table such that any attribute name with + # the ":" prefix should be considered unauthenticated. + unsignAttrPrefix: str = ":" + + # 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + # Include the plaintext override with the provided policy + table_configs = {} + table_config = DynamoDbTableEncryptionConfig( + logical_table_name=ddb_table_name, + partition_key_name="partition_key", + sort_key_name="sort_key", + attribute_actions_on_encrypt=attribute_actions_on_encrypt, + keyring=kms_mrk_multi_keyring, + # Provide plaintext_override policy to the config here + plaintext_override=policy, + allowed_unsigned_attribute_prefix=unsignAttrPrefix, + # Specifying an algorithm suite is not required, + # but is done here to demonstrate how to do so. + # We suggest using the + # `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + # which includes AES-GCM with key derivation, signing, and key commitment. + # This is also the default algorithm suite if one is not specified in this config. + # For more information on supported algorithm suites, see: + # https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384, + ) + table_configs[ddb_table_name] = table_config + tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs) + + # 5. Create the EncryptedResource + # Create a DynamoDB resource + ddb_resource = boto3.resource("dynamodb") + + # Create an EncryptedResource using the DynamoDB resource + return EncryptedResource( + resource=ddb_resource, + encryption_config=tables_config, + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_1.py new file mode 100644 index 000000000..fa660d36f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_1.py @@ -0,0 +1,107 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 1. + +This is an example demonstrating how to start using the +AWS Database Encryption SDK with a pre-existing table with plaintext items. +In this example, we configure an EncryptedResource to do the following: + - Write items only in plaintext + - Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration + +While this step configures your resource to be ready to start reading encrypted items, +we do not yet expect to be reading any encrypted items, +as our resource still writes plaintext items. +Before you move on to step 2, ensure that these changes have successfully been deployed +to all of your readers. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from .common import setup_awsdbe_resource_with_plaintext_override + + +def migration_step_1_with_resource(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1): + """ + Migration Step 1: Using the AWS Database Encryption SDK with Plaintext Override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedResource with plaintext override. + # For Plaintext Override, use `FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forced to be written as plaintext. + # Items may not be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_resource = setup_awsdbe_resource_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Write a batch of items to the table using the Encrypted Resource. + # These items will be stored in plaintext since we are using a plaintext override + # with FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ policy + items = [ + { + "partition_key": "PlaintextMigrationExample-1", + "sort_key": 1, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + { + "partition_key": "PlaintextMigrationExample-2", + "sort_key": 1, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + ] + + batch_write_items_put_request = { + "RequestItems": { + ddb_table_name: [{"PutRequest": {"Item": item}} for item in items], + }, + } + + batch_write_items_put_response = encrypted_resource.batch_write_item(**batch_write_items_put_request) + + # Demonstrate that BatchWriteItem succeeded + assert batch_write_items_put_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Read the items back from the table using the Encrypted Resource. + # If these are items written in plaintext (e.g. any items written + # during Step 0 or 1), then they will be returned as plaintext. + # If these are encrypted items (e.g. any items written during Step 2 or after), + # then they will be decrypted before being returned. + batch_get_items_request = { + "RequestItems": { + ddb_table_name: { + "Keys": [{"partition_key": item["partition_key"], "sort_key": sort_read_value} for item in items], + } + }, + } + + batch_get_items_response = encrypted_resource.batch_get_item(**batch_get_items_request) + + # Demonstrate that BatchGetItem succeeded with the expected result + assert batch_get_items_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + for item in batch_get_items_response["Responses"][ddb_table_name]: + assert ( + item["partition_key"] == "PlaintextMigrationExample-1" + or item["partition_key"] == "PlaintextMigrationExample-2" + ) + assert item["sort_key"] == sort_read_value + assert item["attribute1"] == "encrypt and sign me!" + assert item["attribute2"] == "sign me!" + assert item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_2.py new file mode 100644 index 000000000..9ec1c4951 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_2.py @@ -0,0 +1,105 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 2. + +This is an example demonstrating how to update your configuration +to start writing items using the AWS Database Encryption SDK for DynamoDB, +but still continue to read any items written in plaintext. + +Once you deploy this change to your system, you will have a dataset +containing items in both plaintext and encrypted formats. +Because the changes in Step 1 have been deployed to all our readers, +we can be sure that our entire system is ready to read this new data. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from .common import setup_awsdbe_resource_with_plaintext_override + + +def migration_step_2_with_resource(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 2): + """ + Migration Step 2: Using AWS Database Encryption SDK for DynamoDB with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedResource with plaintext override. + # When configuring our plaintext override, use `FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forbidden to be written as plaintext. + # Items will be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_resource = setup_awsdbe_resource_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Write a batch of items to the table using the Encrypted Resource. + # These items will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + items = [ + { + "partition_key": "PlaintextMigrationExample-1", + "sort_key": 2, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + { + "partition_key": "PlaintextMigrationExample-2", + "sort_key": 2, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + ] + + batch_write_items_put_request = { + "RequestItems": { + ddb_table_name: [{"PutRequest": {"Item": item}} for item in items], + }, + } + + batch_write_items_put_response = encrypted_resource.batch_write_item(**batch_write_items_put_request) + + # Demonstrate that BatchWriteItem succeeded + assert batch_write_items_put_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Read the items back from the table using the Encrypted Resource. + # If these are items written in plaintext (e.g. any items written + # during Step 0 or 1), then they will be returned as plaintext. + # If these are encrypted items (e.g. any items written during Step 2 or after), + # then they will be decrypted before being returned. + batch_get_items_request = { + "RequestItems": { + ddb_table_name: { + "Keys": [{"partition_key": item["partition_key"], "sort_key": sort_read_value} for item in items], + } + }, + } + + batch_get_items_response = encrypted_resource.batch_get_item(**batch_get_items_request) + + # Demonstrate that BatchGetItem succeeded with the expected result + assert batch_get_items_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + for item in batch_get_items_response["Responses"][ddb_table_name]: + assert ( + item["partition_key"] == "PlaintextMigrationExample-1" + or item["partition_key"] == "PlaintextMigrationExample-2" + ) + assert item["sort_key"] == sort_read_value + assert item["attribute1"] == "encrypt and sign me!" + assert item["attribute2"] == "sign me!" + assert item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_3.py new file mode 100644 index 000000000..735ee9b34 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/resource/migration_step_3.py @@ -0,0 +1,94 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 3. + +This is an example demonstrating how to update your configuration +to stop accepting reading items in plaintext format. + +Once you complete Step 3, all items being read by your system are encrypted. + +Before you move onto this step, you will need to encrypt all plaintext items in your dataset. +How you will want to do this depends on your system. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +from .common import setup_awsdbe_resource_without_plaintext_override + + +def migration_step_3_with_resource(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 3): + """ + Migration Step 3: Using only pure AWS Database Encryption SDK (no plaintext override) with EncryptedResource. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create the EncryptedResource. + # Do not configure any plaintext override. + encrypted_resource = setup_awsdbe_resource_without_plaintext_override(kms_key_id, ddb_table_name) + + # 2. Write a batch of items to the table using the Encrypted Resource. + # These items will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + items = [ + { + "partition_key": "PlaintextMigrationExample-1", + "sort_key": 3, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + { + "partition_key": "PlaintextMigrationExample-2", + "sort_key": 3, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + ] + + batch_write_items_put_request = { + "RequestItems": { + ddb_table_name: [{"PutRequest": {"Item": item}} for item in items], + }, + } + + batch_write_items_put_response = encrypted_resource.batch_write_item(**batch_write_items_put_request) + + # Demonstrate that BatchWriteItem succeeded + assert batch_write_items_put_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Read the items back from the table using the Encrypted Resource. + # If these are items written in plaintext (e.g. any items written + # during Step 0 or 1), then we will fail to process the items. + # If these are encrypted items (e.g. any items written during Step 2 or after), + # then we will decrypt the items and return them. + batch_get_items_request = { + "RequestItems": { + ddb_table_name: { + "Keys": [{"partition_key": item["partition_key"], "sort_key": sort_read_value} for item in items], + } + }, + } + + batch_get_items_response = encrypted_resource.batch_get_item(**batch_get_items_request) + + # Demonstrate that BatchGetItem succeeded with the expected result + assert batch_get_items_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + for item in batch_get_items_response["Responses"][ddb_table_name]: + assert ( + item["partition_key"] == "PlaintextMigrationExample-1" + or item["partition_key"] == "PlaintextMigrationExample-2" + ) + assert item["sort_key"] == sort_read_value + assert item["attribute1"] == "encrypt and sign me!" + assert item["attribute2"] == "sign me!" + assert item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/common.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/common.py new file mode 100644 index 000000000..5f5b11f21 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/common.py @@ -0,0 +1,216 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Common Utilities for Migration Examples.""" +import boto3 +from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig +from aws_cryptographic_material_providers.mpl.models import ( + CreateAwsKmsMrkMultiKeyringInput, + DBEAlgorithmSuiteId, +) +from aws_cryptographic_material_providers.mpl.references import IKeyring +from aws_dbesdk_dynamodb.encrypted.table import EncryptedTable +from aws_dbesdk_dynamodb.structures.dynamodb import ( + DynamoDbTableEncryptionConfig, + DynamoDbTablesEncryptionConfig, +) +from aws_dbesdk_dynamodb.structures.structured_encryption import ( + CryptoAction, +) + + +def setup_awsdbe_table_without_plaintext_override(kms_key_id: str, ddb_table_name: str): + """ + Set up a pure AWS Database Encryption SDK EncryptedTable without plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :returns EncryptedTable for DynamoDB + """ + # 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + # For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + # We will use the `CreateMrkMultiKeyring` method to create this keyring, + # as it will correctly handle both single region and Multi-Region KMS Keys. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig()) + kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput( + generator=kms_key_id, + ) + kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input) + + # 2. Configure which attributes are encrypted and/or signed when writing new items. + # For each attribute that may exist on the items we plan to write to our DynamoDbTable, + # we must explicitly configure how they should be treated during item encryption: + # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + # - DO_NOTHING: The attribute is not encrypted and not included in the signature + attribute_actions_on_encrypt = { + "partition_key": CryptoAction.SIGN_ONLY, + "sort_key": CryptoAction.SIGN_ONLY, + "attribute1": CryptoAction.ENCRYPT_AND_SIGN, + "attribute2": CryptoAction.SIGN_ONLY, + ":attribute3": CryptoAction.DO_NOTHING, + } + + # 3. Configure which attributes we expect to be included in the signature + # when reading items. There are two options for configuring this: + # + # - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + # When defining your DynamoDb schema and deciding on attribute names, + # choose a distinguishing prefix (such as ":") for all attributes that + # you do not want to include in the signature. + # This has two main benefits: + # - It is easier to reason about the security and authenticity of data within your item + # when all unauthenticated data is easily distinguishable by their attribute name. + # - If you need to add new unauthenticated attributes in the future, + # you can easily make the corresponding update to your `attributeActionsOnEncrypt` + # and immediately start writing to that new attribute, without + # any other configuration update needed. + # Once you configure this field, it is not safe to update it. + # + # - Configure `allowedUnsignedAttributes`: You may also explicitly list + # a set of attributes that should be considered unauthenticated when encountered + # on read. Be careful if you use this configuration. Do not remove an attribute + # name from this configuration, even if you are no longer writing with that attribute, + # as old items may still include this attribute, and our configuration needs to know + # to continue to exclude this attribute from the signature scope. + # If you add new attribute names to this field, you must first deploy the update to this + # field to all readers in your host fleet before deploying the update to start writing + # with that new attribute. + # + # For this example, we have designed our DynamoDb table such that any attribute name with + # the ":" prefix should be considered unauthenticated. + unsignAttrPrefix: str = ":" + + # 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + table_configs = {} + table_config = DynamoDbTableEncryptionConfig( + logical_table_name=ddb_table_name, + partition_key_name="partition_key", + sort_key_name="sort_key", + attribute_actions_on_encrypt=attribute_actions_on_encrypt, + keyring=kms_mrk_multi_keyring, + allowed_unsigned_attribute_prefix=unsignAttrPrefix, + # Specifying an algorithm suite is not required, + # but is done here to demonstrate how to do so. + # We suggest using the + # `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + # which includes AES-GCM with key derivation, signing, and key commitment. + # This is also the default algorithm suite if one is not specified in this config. + # For more information on supported algorithm suites, see: + # https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384, + ) + table_configs[ddb_table_name] = table_config + tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs) + + # 5. Create the EncryptedTable + # Create a DynamoDB table resource + ddb = boto3.resource("dynamodb") + table = ddb.Table(ddb_table_name) + + # Create an EncryptedTable using the DynamoDB table resource + return EncryptedTable( + table=table, + encryption_config=tables_config, + ) + + +def setup_awsdbe_table_with_plaintext_override(kms_key_id: str, ddb_table_name: str, policy: str): + """ + Set up an AWS Database Encryption SDK EncryptedTable with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param policy: The policy required for the Plaintext Override configuration + :returns EncryptedTable for DynamoDB + + """ + # 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + # For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + # We will use the `CreateMrkMultiKeyring` method to create this keyring, + # as it will correctly handle both single region and Multi-Region KMS Keys. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig()) + kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput = CreateAwsKmsMrkMultiKeyringInput( + generator=kms_key_id, + ) + kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(input=kms_mrk_multi_keyring_input) + + # 2. Configure which attributes are encrypted and/or signed when writing new items. + # For each attribute that may exist on the items we plan to write to our DynamoDbTable, + # we must explicitly configure how they should be treated during item encryption: + # - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + # - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + # - DO_NOTHING: The attribute is not encrypted and not included in the signature + attribute_actions_on_encrypt = { + "partition_key": CryptoAction.SIGN_ONLY, + "sort_key": CryptoAction.SIGN_ONLY, + "attribute1": CryptoAction.ENCRYPT_AND_SIGN, + "attribute2": CryptoAction.SIGN_ONLY, + ":attribute3": CryptoAction.DO_NOTHING, + } + + # 3. Configure which attributes we expect to be included in the signature + # when reading items. There are two options for configuring this: + # + # - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + # When defining your DynamoDb schema and deciding on attribute names, + # choose a distinguishing prefix (such as ":") for all attributes that + # you do not want to include in the signature. + # This has two main benefits: + # - It is easier to reason about the security and authenticity of data within your item + # when all unauthenticated data is easily distinguishable by their attribute name. + # - If you need to add new unauthenticated attributes in the future, + # you can easily make the corresponding update to your `attributeActionsOnEncrypt` + # and immediately start writing to that new attribute, without + # any other configuration update needed. + # Once you configure this field, it is not safe to update it. + # + # - Configure `allowedUnsignedAttributes`: You may also explicitly list + # a set of attributes that should be considered unauthenticated when encountered + # on read. Be careful if you use this configuration. Do not remove an attribute + # name from this configuration, even if you are no longer writing with that attribute, + # as old items may still include this attribute, and our configuration needs to know + # to continue to exclude this attribute from the signature scope. + # If you add new attribute names to this field, you must first deploy the update to this + # field to all readers in your host fleet before deploying the update to start writing + # with that new attribute. + # + # For this example, we have designed our DynamoDb table such that any attribute name with + # the ":" prefix should be considered unauthenticated. + unsignAttrPrefix: str = ":" + + # 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + # Include the plaintext override with the provided policy + table_configs = {} + table_config = DynamoDbTableEncryptionConfig( + logical_table_name=ddb_table_name, + partition_key_name="partition_key", + sort_key_name="sort_key", + attribute_actions_on_encrypt=attribute_actions_on_encrypt, + keyring=kms_mrk_multi_keyring, + # Provide plaintext_override policy to the config here + plaintext_override=policy, + allowed_unsigned_attribute_prefix=unsignAttrPrefix, + # Specifying an algorithm suite is not required, + # but is done here to demonstrate how to do so. + # We suggest using the + # `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + # which includes AES-GCM with key derivation, signing, and key commitment. + # This is also the default algorithm suite if one is not specified in this config. + # For more information on supported algorithm suites, see: + # https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384, + ) + table_configs[ddb_table_name] = table_config + tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs) + + # 5. Create the EncryptedTable + # Create a DynamoDB table resource + ddb = boto3.resource("dynamodb") + table = ddb.Table(ddb_table_name) + + # Create an EncryptedTable using the DynamoDB table resource + return EncryptedTable( + table=table, + encryption_config=tables_config, + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_1.py new file mode 100644 index 000000000..ab7026716 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_1.py @@ -0,0 +1,83 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 1. + +This is an example demonstrating how to start using the +AWS Database Encryption SDK with a pre-existing table with plaintext items. +In this example, we configure an EncryptedTable to do the following: + - Write items only in plaintext + - Read items in plaintext or, if the item is encrypted, decrypt with our encryption configuration + +While this step configures your table to be ready to start reading encrypted items, +we do not yet expect to be reading any encrypted items, +as our table still writes plaintext items. +Before you move on to step 2, ensure that these changes have successfully been deployed +to all of your readers. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from .common import setup_awsdbe_table_with_plaintext_override + + +def migration_step_1_with_table(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 1): + """ + Migration Step 1: Using the AWS Database Encryption SDK with Plaintext Override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedTable with plaintext override. + # For Plaintext Override, use `FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forced to be written as plaintext. + # Items may not be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_table = setup_awsdbe_table_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Put an item into your table using the Encrypted Table. + # This item will be stored in plaintext since we are using a plaintext override + # with FORCE_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ policy + item_to_encrypt = { + "partition_key": "PlaintextMigrationExample", + "sort_key": 1, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + } + + put_item_response = encrypted_table.put_item(Item=item_to_encrypt) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the Encrypted Table. + # If this is an item written in plaintext (e.g. any item written + # during Step 0 or 1), then it will be returned as plaintext. + # If this is an encrypted item (e.g. any item written during Step 2 or after), + # then it will be decrypted before being returned. + key_to_get = {"partition_key": "PlaintextMigrationExample", "sort_key": sort_read_value} + get_item_response = encrypted_table.get_item(Key=key_to_get) + + # Demonstrate that GetItem succeeded and returned the decrypted item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + returned_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert returned_item["partition_key"] == "PlaintextMigrationExample" + assert returned_item["sort_key"] == sort_read_value + assert returned_item["attribute1"] == "encrypt and sign me!" + assert returned_item["attribute2"] == "sign me!" + assert returned_item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_2.py new file mode 100644 index 000000000..897a43f3d --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_2.py @@ -0,0 +1,81 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 2. + +This is an example demonstrating how to update your configuration +to start writing items using the AWS Database Encryption SDK for DynamoDB, +but still continue to read any items written in plaintext. + +Once you deploy this change to your system, you will have a dataset +containing items in both plaintext and encrypted formats. +Because the changes in Step 1 have been deployed to all our readers, +we can be sure that our entire system is ready to read this new data. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +from aws_dbesdk_dynamodb.structures.dynamodb import PlaintextOverride + +from .common import setup_awsdbe_table_with_plaintext_override + + +def migration_step_2_with_table(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 2): + """ + Migration Step 2: Using AWS Database Encryption SDK for DynamoDB with plaintext override. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create an EncryptedTable with plaintext override. + # When configuring our plaintext override, use `FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ`. + # This plaintext override means: + # - Write: Items are forbidden to be written as plaintext. + # Items will be written as encrypted items. + # - Read: Items are allowed to be read as plaintext. + # Items are allowed to be read as encrypted items. + policy = PlaintextOverride.FORBID_PLAINTEXT_WRITE_ALLOW_PLAINTEXT_READ + encrypted_table = setup_awsdbe_table_with_plaintext_override( + kms_key_id=kms_key_id, ddb_table_name=ddb_table_name, policy=policy + ) + + # 2. Put an item into your table using the Encrypted Table. + # This item will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + item_to_encrypt = { + "partition_key": "PlaintextMigrationExample", + "sort_key": 2, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + } + + put_item_response = encrypted_table.put_item(Item=item_to_encrypt) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the Encrypted Table. + # If this is an item written in plaintext (e.g. any item written + # during Step 0 or 1), then it will be returned as plaintext. + # If this is an encrypted item (e.g. any item written during Step 2 or after), + # then it will be decrypted before being returned. + key_to_get = {"partition_key": "PlaintextMigrationExample", "sort_key": sort_read_value} + get_item_response = encrypted_table.get_item(Key=key_to_get) + + # Demonstrate that GetItem succeeded and returned the decrypted/plaintext item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + returned_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert returned_item["partition_key"] == "PlaintextMigrationExample" + assert returned_item["sort_key"] == sort_read_value + assert returned_item["attribute1"] == "encrypt and sign me!" + assert returned_item["attribute2"] == "sign me!" + assert returned_item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_3.py new file mode 100644 index 000000000..426eb3301 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/awsdbe/table/migration_step_3.py @@ -0,0 +1,70 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 3. + +This is an example demonstrating how to update your configuration +to stop accepting reading items in plaintext format. + +Once you complete Step 3, all items being read by your system are encrypted. + +Before you move onto this step, you will need to encrypt all plaintext items in your dataset. +How you will want to do this depends on your system. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +from .common import setup_awsdbe_table_without_plaintext_override + + +def migration_step_3_with_table(kms_key_id: str, ddb_table_name: str, sort_read_value: int = 3): + """ + Migration Step 3: Using only pure AWS Database Encryption SDK (no plaintext override) with EncryptedTable. + + :param kms_key_id: The ARN of the KMS key to use for encryption + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create the EncryptedTable. + # Do not configure any plaintext override. + encrypted_table = setup_awsdbe_table_without_plaintext_override(kms_key_id, ddb_table_name) + + # 2. Put an item into your table using the Encrypted Table. + # This item will be encrypted using the DB Encryption SDK, using the + # configuration to determine which attributes to encrypt and/or sign. + item = { + "partition_key": "PlaintextMigrationExample", + "sort_key": 3, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + } + + put_item_response = encrypted_table.put_item(Item=item) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the Encrypted Table. + # If this is an item written in plaintext (e.g. any item written + # during Step 0 or 1), then we will fail to process the item. + # If this is an encrypted item (e.g. any item written during Step 2 or after), + # then we will decrypt the item and return it. + key_to_get = {"partition_key": "PlaintextMigrationExample", "sort_key": sort_read_value} + get_item_response = encrypted_table.get_item(Key=key_to_get) + + # Demonstrate that GetItem succeeded and returned the decrypted item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + decrypted_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert decrypted_item["partition_key"] == "PlaintextMigrationExample" + assert decrypted_item["sort_key"] == sort_read_value + assert decrypted_item["attribute1"] == "encrypt and sign me!" + assert decrypted_item["attribute2"] == "sign me!" + assert decrypted_item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/README.md b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/README.md new file mode 100644 index 000000000..8389050c3 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/README.md @@ -0,0 +1,13 @@ +# Step 0 + +In Step 0, your system is in the starting state using plaintext DynamoDB operations: + +- continue to read plaintext items +- continue to write plaintext items +- cannot read encrypted items + +This represents the baseline configuration before beginning the migration process. +Your dataset consists only plaintext data. + +When operating in this state, your system has no client-side encryption in place, +and all data is stored in plaintext in your DynamoDB tables. diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/client/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/client/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/client/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/client/migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/client/migration_step_0.py new file mode 100644 index 000000000..58d0655f4 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/client/migration_step_0.py @@ -0,0 +1,71 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 0. + +This is the pre-migration step for the plaintext-to-encrypted database migration, +and is the starting state for our migration from a plaintext database to a +client-side encrypted database encrypted using the AWS Database Encryption SDK for DynamoDB. + +In this example, we use a standard DynamoDB client to write and read plaintext data. +This emulates the starting state of a plaintext-to-encrypted database migration; +i.e., a plaintext database you can read and write to with the standard DynamoDB client. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +""" + +import boto3 + + +def migration_step_0_with_client(ddb_table_name: str, sort_read_value: int = 0): + """ + Migration Step 0: Using a standard DynamoDB client with plaintext data. + + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create a standard AWS SDK DynamoDB client + ddb_client = boto3.client("dynamodb") + + # 2. Put an example item into our DynamoDb table. + # This item will be stored in plaintext. + item_to_put = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": str(0)}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_request = { + "TableName": ddb_table_name, + "Item": item_to_put, + } + + put_item_response = ddb_client.put_item(**put_item_request) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the DynamoDB client. + # This item will be returned in plaintext, as it was stored. + key_to_get = {"partition_key": {"S": "PlaintextMigrationExample"}, "sort_key": {"N": str(sort_read_value)}} + + get_item_request = {"TableName": ddb_table_name, "Key": key_to_get} + get_item_response = ddb_client.get_item(**get_item_request) + + # Demonstrate that GetItem succeeded and returned the plaintext item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + plaintext_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert plaintext_item["partition_key"]["S"] == "PlaintextMigrationExample" + assert plaintext_item["sort_key"]["N"] == str(sort_read_value) + assert "S" in plaintext_item["attribute1"] and plaintext_item["attribute1"]["S"] == "encrypt and sign me!" + assert "S" in plaintext_item["attribute2"] and plaintext_item["attribute2"]["S"] == "sign me!" + assert "S" in plaintext_item[":attribute3"] and plaintext_item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/paginator/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/paginator/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/paginator/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/paginator/migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/paginator/migration_step_0.py new file mode 100644 index 000000000..f026c576f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/paginator/migration_step_0.py @@ -0,0 +1,73 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 0. + +This is the pre-migration step for the plaintext-to-encrypted database migration, +and is the starting state for our migration from a plaintext database to a +client-side encrypted database encrypted using the AWS Database Encryption SDK for DynamoDB. + +In this example, we use a standard DynamoDB client paginator to work with plaintext data. +This emulates the starting state of a plaintext-to-encrypted database migration; +i.e., a plaintext database you can read and write to with the standard DynamoDB client and paginator. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +import boto3 + + +def migration_step_0_with_paginator(ddb_table_name: str, sort_read_value: int = 0): + """ + Migration Step 0: Using a standard DynamoDB client paginator with plaintext data. + + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create a standard AWS SDK DynamoDB client + ddb_client = boto3.client("dynamodb") + + # 2. Put an example item into our DynamoDb table. + # This item will be stored in plaintext. + item = { + "partition_key": {"S": "PlaintextMigrationExample"}, + "sort_key": {"N": str(0)}, + "attribute1": {"S": "encrypt and sign me!"}, + "attribute2": {"S": "sign me!"}, + ":attribute3": {"S": "ignore me!"}, + } + + put_item_response = ddb_client.put_item(TableName=ddb_table_name, Item=item) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get a paginator from the client + paginator = ddb_client.get_paginator("query") + + # 4. Use the paginator to get items from the table + items = [] + for page in paginator.paginate( + TableName=ddb_table_name, + KeyConditionExpression="partition_key = :partition_key AND sort_key = :sort_key", + ExpressionAttributeValues={ + ":partition_key": {"S": "PlaintextMigrationExample"}, + ":sort_key": {"N": str(sort_read_value)}, + }, + ): + for item in page["Items"]: + items.append(item) + + # 5. Demonstrate we get the expected items back. + assert len(items) == 1 # We should have only one item with above key condition + item = next((i for i in items if i["sort_key"]["N"] == str(sort_read_value)), None) + assert item is not None + assert item["partition_key"]["S"] == "PlaintextMigrationExample" + assert "S" in item["attribute1"] and item["attribute1"]["S"] == "encrypt and sign me!" + assert "S" in item["attribute2"] and item["attribute2"]["S"] == "sign me!" + assert "S" in item[":attribute3"] and item[":attribute3"]["S"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/resource/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/resource/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/resource/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/resource/migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/resource/migration_step_0.py new file mode 100644 index 000000000..dff35f7c5 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/resource/migration_step_0.py @@ -0,0 +1,88 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 0. + +This is the pre-migration step for the plaintext-to-encrypted database migration, +and is the starting state for our migration from a plaintext database to a +client-side encrypted database encrypted using the AWS Database Encryption SDK for DynamoDB. + +In this example, we use a standard DynamoDB resource to write and read plaintext data. +This emulates the starting state of a plaintext-to-encrypted database migration; +i.e., a plaintext database you can read and write to with the standard DynamoDB resource. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +import boto3 + + +def migration_step_0_with_resource(ddb_table_name: str, sort_read_value: int = 0): + """ + Migration Step 0: Using a standard DynamoDB resource with plaintext data. + + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create a standard AWS SDK DynamoDB resource + ddb_resource = boto3.resource("dynamodb") + + # 2. Write a batch of items to the table. + # These items will be stored in plaintext. + items = [ + { + "partition_key": "PlaintextMigrationExample-1", + "sort_key": 0, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + { + "partition_key": "PlaintextMigrationExample-2", + "sort_key": 0, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + }, + ] + + batch_write_items_put_request = { + "RequestItems": { + ddb_table_name: [{"PutRequest": {"Item": item}} for item in items], + }, + } + + batch_write_items_put_response = ddb_resource.batch_write_item(**batch_write_items_put_request) + + # Demonstrate that BatchWriteItem succeeded + assert batch_write_items_put_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Read the items back from the table. + # The items will be returned in plaintext, as they were stored. + batch_get_items_request = { + "RequestItems": { + ddb_table_name: { + "Keys": [{"partition_key": item["partition_key"], "sort_key": sort_read_value} for item in items], + } + }, + } + + batch_get_items_response = ddb_resource.batch_get_item(**batch_get_items_request) + + # Demonstrate that BatchGetItem succeeded with the expected result + assert batch_get_items_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + for item in batch_get_items_response["Responses"][ddb_table_name]: + assert ( + item["partition_key"] == "PlaintextMigrationExample-1" + or item["partition_key"] == "PlaintextMigrationExample-2" + ) + assert item["sort_key"] == sort_read_value + assert item["attribute1"] == "encrypt and sign me!" + assert item["attribute2"] == "sign me!" + assert item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/table/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/table/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/table/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/table/migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/table/migration_step_0.py new file mode 100644 index 000000000..e543e8178 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/src/plaintext/table/migration_step_0.py @@ -0,0 +1,65 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Migration Step 0. + +This is the pre-migration step for the plaintext-to-encrypted database migration, +and is the starting state for our migration from a plaintext database to a +client-side encrypted database encrypted using the AWS Database Encryption SDK for DynamoDB. + +In this example, we use a standard DynamoDB table resource to write and read plaintext data. +This emulates the starting state of a plaintext-to-encrypted database migration; +i.e., a plaintext database you can read and write to with the standard DynamoDB table. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +""" + +import boto3 + + +def migration_step_0_with_table(ddb_table_name: str, sort_read_value: int = 0): + """ + Migration Step 0: Using a standard DynamoDB table with plaintext data. + + :param ddb_table_name: The name of the DynamoDB table + :param sort_read_value: The sort key value to read + """ + # 1. Create a standard AWS SDK DynamoDB table resource + ddb = boto3.resource("dynamodb") + table = ddb.Table(ddb_table_name) + + # 2. Put an example item into our DynamoDb table. + # This item will be stored in plaintext. + item_to_put = { + "partition_key": "PlaintextMigrationExample", + "sort_key": 0, + "attribute1": "encrypt and sign me!", + "attribute2": "sign me!", + ":attribute3": "ignore me!", + } + + put_item_response = table.put_item(Item=item_to_put) + # Demonstrate that PutItem succeeded + assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # 3. Get an item back from the table using the DynamoDB table. + # This item will be returned in plaintext, as it was stored. + key_to_get = {"partition_key": "PlaintextMigrationExample", "sort_key": sort_read_value} + get_item_response = table.get_item(Key=key_to_get) + + # Demonstrate that GetItem succeeded and returned the plaintext item + assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200 + plaintext_item = get_item_response["Item"] + + # Demonstrate we get the expected item back + assert plaintext_item["partition_key"] == "PlaintextMigrationExample" + assert plaintext_item["sort_key"] == sort_read_value + assert plaintext_item["attribute1"] == "encrypt and sign me!" + assert plaintext_item["attribute2"] == "sign me!" + assert plaintext_item[":attribute3"] == "ignore me!" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_1.py new file mode 100644 index 000000000..e1b983c88 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_1.py @@ -0,0 +1,56 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 1. + +This test validates the compatibility between different stages of migration +and ensures that step 1 (using AWS DBESDK with plaintext override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.client import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.client import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_1_with_client(): + """Test migration step 1 compatibility with different data formats.""" + # Successfully executes Step 1 + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 1 with sort_read_value=0 + # Then: Success (i.e. can read values in plaintext format) + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 1 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 1 with sort_read_value=3 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_2.py new file mode 100644 index 000000000..18dc1dfcd --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_2.py @@ -0,0 +1,56 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 2. + +This test validates the compatibility between different stages of migration +and ensures that step 2 (using AWS DBESDK with plaintext override for reads only) +behaves correctly with data from different migration stages. +""" +import pytest + +from ....src.awsdbe.client import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.client import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_2_with_client(): + """Test migration step 2 compatibility with different data formats.""" + # Successfully executes Step 2 + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 2 with sort_read_value=0 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 2 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 2 with sort_read_value=3 + # Then: Success (i.e. can read values in encrypted format) + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_3.py new file mode 100644 index 000000000..4ebe27d44 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/client/test_migration_step_3.py @@ -0,0 +1,61 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 3. + +This test validates the compatibility between different stages of migration +and ensures that step 3 (using only AWS DBESDK) behaves correctly with data +from different migration stages. +""" +import pytest +from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.errors import ( + DynamoDbItemEncryptor, +) + +from ....src.awsdbe.client import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.client import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_3_with_client(): + """Test migration step 3 compatibility with different data formats.""" + # Successfully executes Step 3 + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 3 with sort_read_value=0 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 3 with sort_read_value=1 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 3 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_1.py new file mode 100644 index 000000000..9d4f686e7 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_1.py @@ -0,0 +1,44 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 1. + +This test validates the compatibility between different stages of migration +and ensures that step 1 (using AWS DBESDK with plaintext override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.paginator import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.paginator import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_1_with_paginator(): + """Test migration step 1 compatibility with different data formats.""" + # Successfully executes Step 1 + migration_step_1.migration_step_1_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME) + # When: Execute Step 1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_1.migration_step_1_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + # When: Execute Step 1 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + # When: Execute Step 1 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_2.py new file mode 100644 index 000000000..88412b5e5 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_2.py @@ -0,0 +1,44 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 2. + +This test validates the compatibility between different stages of migration +and ensures that step 2 (using AWS DBESDK with FORBID_PLAINTEXT_WRITE override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.paginator import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.paginator import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_2_with_paginator(): + """Test migration step 2 compatibility with different data formats.""" + # Successfully executes Step 2 + migration_step_2.migration_step_2_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME) + # When: Execute Step 2 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + # When: Execute Step 2 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) + # When: Execute Step 2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_2.migration_step_2_with_paginator(kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_3.py new file mode 100644 index 000000000..5def4f83d --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/paginator/test_migration_step_3.py @@ -0,0 +1,60 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 3. + +This test validates the compatibility behavior for step 3 (pure AWS DBESDK without plaintext override). +Step 3 can only read encrypted items and will fail with plaintext items. +""" +import pytest +from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.errors import ( + DynamoDbItemEncryptor, +) + +from ....src.awsdbe.paginator import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.paginator import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_3_with_paginator(): + """Test migration step 3 compatibility with encrypted data formats only.""" + # Successfully executes Step 3 + migration_step_3.migration_step_3_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 3 with sort_read_value=0 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 3 with sort_read_value=1 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 3 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_3.migration_step_3_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_1.py new file mode 100644 index 000000000..31d5af085 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_1.py @@ -0,0 +1,56 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 1. + +This test validates the compatibility between different stages of migration +and ensures that step 1 (using AWS DBESDK with plaintext override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.resource import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.resource import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_1_with_resource(): + """Test migration step 1 compatibility with different data formats.""" + # Successfully executes Step 1 + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 1 with sort_read_value=0 + # Then: Success (i.e. can read values in plaintext format) + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 1 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 1 with sort_read_value=3 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_2.py new file mode 100644 index 000000000..54e9f2c23 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_2.py @@ -0,0 +1,56 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 2. + +This test validates the compatibility between different stages of migration +and ensures that step 2 (using AWS DBESDK with FORBID_PLAINTEXT_WRITE override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.resource import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.resource import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_2_with_resource(): + """Test migration step 2 compatibility with different data formats.""" + # Successfully executes Step 2 + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 2 with sort_read_value=0 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 2 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 2 with sort_read_value=3 + # Then: Success (i.e. can read values in encrypted format) + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_3.py new file mode 100644 index 000000000..6f8040af6 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/resource/test_migration_step_3.py @@ -0,0 +1,60 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 3. + +This test validates the compatibility behavior for step 3 (pure AWS DBESDK without plaintext override). +Step 3 can only read encrypted items and will fail with plaintext items. +""" +import pytest +from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.errors import ( + DynamoDbItemEncryptor, +) + +from ....src.awsdbe.resource import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.resource import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_3_with_resource(): + """Test migration step 3 compatibility with encrypted data formats only.""" + # Successfully executes Step 3 + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 3 with sort_read_value=0 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 3 with sort_read_value=1 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 3 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_1.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_1.py new file mode 100644 index 000000000..bc8d0d14d --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_1.py @@ -0,0 +1,56 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 1. + +This test validates the compatibility between different stages of migration +and ensures that step 1 (using AWS DBESDK with plaintext override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.table import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.table import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_1_with_table(): + """Test migration step 1 compatibility with different data formats.""" + # Successfully executes Step 1 + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 1 with sort_read_value=0 + # Then: Success (i.e. can read values in plaintext format) + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 1 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 1 with sort_read_value=3 + # Then: Success (i.e. can read values in encrypted format) + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_2.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_2.py new file mode 100644 index 000000000..69303427b --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_2.py @@ -0,0 +1,56 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 2. + +This test validates the compatibility between different stages of migration +and ensures that step 2 (using AWS DBESDK with FORBID_PLAINTEXT_WRITE override) can read data +from all other migration steps. +""" +import pytest + +from ....src.awsdbe.table import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.table import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_2_with_table(): + """Test migration step 2 compatibility with different data formats.""" + # Successfully executes Step 2 + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 2 with sort_read_value=0 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 2 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 2 with sort_read_value=3 + # Then: Success (i.e. can read values in encrypted format) + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_3.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_3.py new file mode 100644 index 000000000..79d2d4835 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/awsdbe/table/test_migration_step_3.py @@ -0,0 +1,60 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 3. + +This test validates the compatibility behavior for step 3 (pure AWS DBESDK without plaintext override). +Step 3 can only read encrypted items and will fail with plaintext items. +""" +import pytest +from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.errors import ( + DynamoDbItemEncryptor, +) + +from ....src.awsdbe.table import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.table import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_3_with_table(): + """Test migration step 3 compatibility with encrypted data formats only.""" + # Successfully executes Step 3 + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + + # Given: Step 0 has succeeded + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + # When: Execute Step 3 with sort_read_value=0 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0 + ) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 3 with sort_read_value=1 + # Then: throws DynamoDbItemEncryptor Exception (i.e. cannot read values in plaintext format) + with pytest.raises(DynamoDbItemEncryptor): + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 3 with sort_read_value=2 + # Then: Success (i.e. can read values in encrypted format) + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/client/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/client/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/client/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/client/test_migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/client/test_migration_step_0.py new file mode 100644 index 000000000..d89dec838 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/client/test_migration_step_0.py @@ -0,0 +1,53 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 0. + +This test validates the initial plaintext state for DynamoDB operations with Client interface. +It verifies basic functionality of the plaintext DynamoDB Client operations. +""" +import pytest + +from ....src.awsdbe.client import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.client import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_0_with_client(): + """Test plaintext DynamoDB Client operations.""" + # Successfully executes Step 0 + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 0 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 0 with sort_read_value=2 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError is raised when attributes have type "B" (binary) instead of "S" (string) + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_client( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 0 with sort_read_value=3 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError is raised when attributes have type "B" (binary) instead of "S" (string) + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_client(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/paginator/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/paginator/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/paginator/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/paginator/test_migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/paginator/test_migration_step_0.py new file mode 100644 index 000000000..c15484d35 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/paginator/test_migration_step_0.py @@ -0,0 +1,53 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 0. + +This test validates the initial plaintext state for DynamoDB operations with Paginator interface. +It verifies basic functionality of the plaintext DynamoDB Paginator operations. +""" +import pytest + +from ....src.awsdbe.paginator import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.paginator import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_0_with_paginator(): + """Test plaintext DynamoDB Paginator operations.""" + # Successfully executes Step 0 + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 0 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 0 with sort_read_value=2 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError is raised when attributes have type "B" (binary) instead of "S" (string) + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_paginator( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 0 with sort_read_value=3 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError is raised when attributes have type "B" (binary) instead of "S" (string) + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_paginator(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/resource/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/resource/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/resource/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/resource/test_migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/resource/test_migration_step_0.py new file mode 100644 index 000000000..f337f7ba4 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/resource/test_migration_step_0.py @@ -0,0 +1,53 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 0. + +This test validates the initial plaintext state for DynamoDB operations with Resource interface. +It verifies basic functionality of the plaintext DynamoDB Resource operations. +""" +import pytest + +from ....src.awsdbe.resource import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.resource import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_0_with_resource(): + """Test plaintext DynamoDB Resource operations.""" + # Successfully executes Step 0 + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 0 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext format) + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 0 with sort_read_value=2 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError: attribute1 now has type (binary) value instead of (string) value + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_resource( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 0 with sort_read_value=3 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError: attribute1 now has type (binary) value instead of (string) value + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_resource(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/table/__init__.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/table/__init__.py new file mode 100644 index 000000000..fa977e22f --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/table/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Stub to allow relative imports of examples from tests.""" diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/table/test_migration_step_0.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/table/test_migration_step_0.py new file mode 100644 index 000000000..6a302b5cd --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/plaintext/table/test_migration_step_0.py @@ -0,0 +1,53 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Test for Migration Step 0. + +This test validates the initial plaintext state for DynamoDB operations with Table interface. +It verifies basic functionality of the plaintext DynamoDB Table operations. +""" +import pytest + +from ....src.awsdbe.table import ( + migration_step_1, + migration_step_2, + migration_step_3, +) +from ....src.plaintext.table import migration_step_0 +from ...test_utils import TEST_DDB_TABLE_NAME, TEST_KMS_KEY_ID + +pytestmark = [pytest.mark.examples] + + +def test_migration_step_0_with_table(): + """Test plaintext DynamoDB Table Resource operations.""" + # Successfully executes Step 0 + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=0) + + # Given: Step 1 has succeeded + migration_step_1.migration_step_1_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1 + ) + # When: Execute Step 0 with sort_read_value=1 + # Then: Success (i.e. can read values in plaintext values) + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=1) + + # Given: Step 2 has succeeded + migration_step_2.migration_step_2_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2 + ) + # When: Execute Step 0 with sort_read_value=2 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError: attribute1 now has type (binary) value instead of (string) value + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=2) + + # Given: Step 3 has succeeded + migration_step_3.migration_step_3_with_table( + kms_key_id=TEST_KMS_KEY_ID, ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3 + ) + # When: Execute Step 0 with sort_read_value=3 + # Then: throws AssertionError (i.e. cannot read encrypted values) + # AssertionError: attribute1 now has type (binary) value instead of (string) value + with pytest.raises(AssertionError): + migration_step_0.migration_step_0_with_table(ddb_table_name=TEST_DDB_TABLE_NAME, sort_read_value=3) diff --git a/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/test_utils.py b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/test_utils.py new file mode 100644 index 000000000..7e9962bf4 --- /dev/null +++ b/Examples/runtimes/python/Migration/plaintext_to_awsdbe/test/test_utils.py @@ -0,0 +1,9 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test constants.""" + +# This is a public KMS Key that MUST only be used for testing, and MUST NOT be used for any production data +TEST_KMS_KEY_ID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + +# Our tests require access to DDB Table with this name +TEST_DDB_TABLE_NAME = "DynamoDbEncryptionInterceptorTestTable" diff --git a/Examples/runtimes/python/tox.ini b/Examples/runtimes/python/tox.ini index eaf7153ac..2aa46b68c 100644 --- a/Examples/runtimes/python/tox.ini +++ b/Examples/runtimes/python/tox.ini @@ -1,7 +1,7 @@ [tox] isolated_build = True envlist = - py{311,312,313}-{dynamodbencryption,legacymigration} + py{311,312,313}-{dynamodbencryption,legacymigration,plaintextmigration} [testenv:base-command] commands = poetry run pytest -s -v -l {posargs} @@ -15,4 +15,5 @@ commands_pre = poetry install --with test --no-root commands = dynamodbencryption: {[testenv:base-command]commands} DynamoDBEncryption/test/ - legacymigration: {[testenv:base-command]commands} Migration/ddbec_to_awsdbe/test/ \ No newline at end of file + legacymigration: {[testenv:base-command]commands} Migration/ddbec_to_awsdbe/test/ + plaintextmigration: {[testenv:base-command]commands} Migration/plaintext_to_awsdbe/test/ \ No newline at end of file