diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..f370f7407 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,94 @@ +# AWS Encryption SDK Examples + +This section features examples that show you +how to use the AWS Encryption SDK. +We demonstrate how to use the encryption and decryption APIs +and how to set up some common configuration patterns. + +## APIs + +The AWS Encryption SDK provides two high-level APIs: +one-step APIs that process the entire operation in memory +and streaming APIs. + +You can find examples that demonstrate these APIs +in the [`examples/src/`](./src) directory. + +## Configuration + +To use the library APIs, +you need to describe how you want the library to protect your data keys. +You can do this using +[keyrings][#keyrings] or [cryptographic materials managers][#cryptographic-materials-managers], +or using [master key providers][#master-key-providers]. +These examples will show you how. + +### Keyrings + +Keyrings are the most common way for you to configure the AWS Encryption SDK. +They determine how the AWS Encryption SDK protects your data. +You can find these examples in [`examples/src/keyring`](./src/keyring). + +### Cryptographic Materials Managers + +Keyrings define how your data keys are protected, +but there is more going on here than just protecting data keys. + +Cryptographic materials managers give you higher-level controls +over how the AWS Encryption SDK protects your data. +This can include things like +enforcing the use of certain algorithm suites or encryption context settings, +reusing data keys across messages, +or changing how you interact with keyrings. +You can find these examples in +[`examples/src/crypto_materials_manager`](./src/crypto_materials_manager). + +### Master Key Providers + +Before there were keyrings, there were master key providers. +Master key providers were the original configuration structure +that we provided for defining how you want to protect your data keys. +Keyrings provide a simpler experience and often more powerful configuration options, +but if you need to use master key providers, +need help migrating from master key providers to keyrings, +or simply want to see the difference between these configuration experiences, +you can find these examples in [`examples/src/master_key_provider`](./src/master_key_provider). + +## Legacy + +This section includes older examples, including examples of using master keys and master key providers in Java and Python. +You can use them as a reference, +but we recommend looking at the newer examples, which explain the preferred ways of using this library. +You can find these examples in [`examples/src/legacy`](./src/legacy). + +# Writing Examples + +If you want to contribute a new example, that's awesome! +To make sure that your example is tested in our CI, +please make sure that it meets the following requirements: + +1. The example MUST be a distinct module in the [`examples/src/`](./src) directory. +1. The example MAY be nested arbitrarily deeply, + but every intermediate directory MUST contain a `__init__.py` file + so that CPython 2.7 will recognize it as a module. +1. Every example MUST be CPython 2.7 compatible. +1. Each example file MUST contain exactly one example. +1. Each example file MUST contain a function called `run` that runs the example. +1. If your `run` function needs any of the following inputs, + the parameters MUST have the following names: + * `aws_kms_cmk` (`str`) : A single AWS KMS CMK ARN. + * NOTE: You can assume that automatically discovered credentials have + `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` permissions on this CMK. + * `aws_kms_generator_cmk` (`str`) : A single AWS KMS CMK ARN to use as a generator key. + * NOTE: You can assume that automatically discovered credentials have + `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` permissions on this CMK. + * `aws_kms_additional_cmks` (`List[str]`) : + A list of AWS KMS CMK ARNs to use for encrypting and decrypting data keys. + * NOTE: You can assume that automatically discovered credentials have + `kms:Encrypt` and `kms:Decrypt` permissions on these CMKs. + * `source_plaintext` (`bytes`) : Plaintext data to encrypt. + * `source_plaintext_filename` (`str`) : A path to a file containing plaintext to encrypt. + * NOTE: You can assume that you have write access to the parent directory + and that anything you do in that directory will be cleaned up + by our test runners. +1. Any additional parameters MUST be optional. diff --git a/examples/src/file_streaming_defaults.py b/examples/src/file_streaming_defaults.py new file mode 100644 index 000000000..983bfc90d --- /dev/null +++ b/examples/src/file_streaming_defaults.py @@ -0,0 +1,82 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example shows how to use the streaming encrypt and decrypt APIs when working with files. + +One benefit of using the streaming API is that +we can check the encryption context in the header before we start decrypting. + +In this example, we use an AWS KMS customer master key (CMK), +but you can use other key management options with the AWS Encryption SDK. +For examples that demonstrate how to use other key management configurations, +see the ``keyring`` and ``master_key_provider`` directories. +""" +import filecmp + +import aws_encryption_sdk +from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring + + +def run(aws_kms_cmk, source_plaintext_filename): + # type: (str, str) -> None + """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs with files. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param str source_plaintext_filename: Path to plaintext file to encrypt + """ + # We assume that you can also write to the directory containing the plaintext file, + # so that is where we will put all of the results. + ciphertext_filename = source_plaintext_filename + ".encrypted" + decrypted_filename = ciphertext_filename + ".decrypted" + + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # Create the keyring that determines how your data keys are protected. + keyring = KmsKeyring(generator_key_id=aws_kms_cmk) + + # Open the files you want to work with. + with open(source_plaintext_filename, "rb") as plaintext, open(ciphertext_filename, "wb") as ciphertext: + # The streaming API provides a context manager. + # You can read from it just as you read from a file. + with aws_encryption_sdk.stream( + mode="encrypt", source=plaintext, encryption_context=encryption_context, keyring=keyring + ) as encryptor: + # Iterate through the segments in the context manager + # and write the results to the ciphertext. + for segment in encryptor: + ciphertext.write(segment) + + # Verify that the ciphertext and plaintext are different. + assert not filecmp.cmp(source_plaintext_filename, ciphertext_filename) + + # Open the files you want to work with. + with open(ciphertext_filename, "rb") as ciphertext, open(decrypted_filename, "wb") as decrypted: + # Decrypt your encrypted data using the same keyring you used on encrypt. + # + # We do not need to specify the encryption context on decrypt + # because the message header includes the encryption context. + with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor: + # Check the encryption context in the header before we start decrypting. + # + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decryptor.header.encryption_context.items()) + + # Now that we are more confident that we will decrypt the right message, + # we can start decrypting. + for segment in decryptor: + decrypted.write(segment) + + # Verify that the decrypted plaintext is identical to the original plaintext. + assert filecmp.cmp(source_plaintext_filename, decrypted_filename) diff --git a/examples/src/in_memory_streaming_defaults.py b/examples/src/in_memory_streaming_defaults.py new file mode 100644 index 000000000..1a2824b94 --- /dev/null +++ b/examples/src/in_memory_streaming_defaults.py @@ -0,0 +1,79 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example shows how to use the streaming encrypt and decrypt APIs on data in memory. + +One benefit of using the streaming API is that +we can check the encryption context in the header before we start decrypting. + +In this example, we use an AWS KMS customer master key (CMK), +but you can use other key management options with the AWS Encryption SDK. +For examples that demonstrate how to use other key management configurations, +see the ``keyring`` and ``mater_key_provider`` directories. +""" +import io + +import aws_encryption_sdk +from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring + + +def run(aws_kms_cmk, source_plaintext): + # type: (str, bytes) -> None + """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs in-memory. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # Create the keyring that determines how your data keys are protected. + keyring = KmsKeyring(generator_key_id=aws_kms_cmk) + + ciphertext = io.BytesIO() + + # The streaming API provides a context manager. + # You can read from it just as you read from a file. + with aws_encryption_sdk.stream( + mode="encrypt", source=source_plaintext, encryption_context=encryption_context, keyring=keyring + ) as encryptor: + # Iterate through the segments in the context manager + # and write the results to the ciphertext. + for segment in encryptor: + ciphertext.write(segment) + + # Verify that the ciphertext and plaintext are different. + assert ciphertext.getvalue() != source_plaintext + + # Reset the ciphertext stream position so that we can read from the beginning. + ciphertext.seek(0) + + # Decrypt your encrypted data using the same keyring you used on encrypt. + # + # We do not need to specify the encryption context on decrypt + # because the header message includes the encryption context. + decrypted = io.BytesIO() + with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor: + # Check the encryption context in the header before we start decrypting. + # + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decryptor.header.encryption_context.items()) + + # Now that we are more confident that we will decrypt the right message, + # we can start decrypting. + for segment in decryptor: + decrypted.write(segment) + + # Verify that the decrypted plaintext is identical to the original plaintext. + assert decrypted.getvalue() == source_plaintext diff --git a/examples/src/legacy/__init__.py b/examples/src/legacy/__init__.py new file mode 100644 index 000000000..e4646257c --- /dev/null +++ b/examples/src/legacy/__init__.py @@ -0,0 +1,9 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Legacy examples. + +We keep these older examples as reference material, +but we recommend that you use the new examples. +The new examples reflect our current guidance for using the library. +""" diff --git a/examples/src/basic_encryption.py b/examples/src/legacy/basic_encryption.py similarity index 63% rename from examples/src/basic_encryption.py rename to examples/src/legacy/basic_encryption.py index 6c194e45d..81db7a102 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/legacy/basic_encryption.py @@ -1,29 +1,19 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Example showing basic encryption and decryption of a value already in memory.""" +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Example showing how to encrypt and decrypt a value in memory.""" import aws_encryption_sdk -def cycle_string(key_arn, source_plaintext, botocore_session=None): +def run(aws_kms_cmk, source_plaintext, botocore_session=None): """Encrypts and then decrypts a string under a KMS customer master key (CMK). - :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param str aws_kms_cmk: Amazon Resource Name (ARN) of the AWS KMS CMK :param bytes source_plaintext: Data to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ # Create a KMS master key provider - kms_kwargs = dict(key_ids=[key_arn]) + kms_kwargs = dict(key_ids=[aws_kms_cmk]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session master_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kms_kwargs) diff --git a/examples/src/basic_file_encryption_with_multiple_providers.py b/examples/src/legacy/basic_file_encryption_with_multiple_providers.py similarity index 88% rename from examples/src/basic_file_encryption_with_multiple_providers.py rename to examples/src/legacy/basic_file_encryption_with_multiple_providers.py index 9edceef9e..1319025d9 100644 --- a/examples/src/basic_file_encryption_with_multiple_providers.py +++ b/examples/src/legacy/basic_file_encryption_with_multiple_providers.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example showing creation of a RawMasterKeyProvider, how to use multiple master key providers to encrypt, and demonstrating that each master key provider can then be used independently to decrypt the same encrypted message. @@ -60,12 +50,12 @@ def _get_raw_key(self, key_id): ) -def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): +def run(aws_kms_cmk, source_plaintext_filename, botocore_session=None): """Encrypts and then decrypts a file using a KMS master key provider and a custom static master key provider. Both master key providers are used to encrypt the plaintext file, so either one alone can decrypt it. - :param str key_arn: Amazon Resource Name (ARN) of the KMS Customer Master Key (CMK) + :param str aws_kms_cmk: Amazon Resource Name (ARN) of the KMS Customer Master Key (CMK) (http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html) :param str source_plaintext_filename: Filename of file to encrypt :param botocore_session: existing botocore session instance @@ -77,7 +67,7 @@ def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): cycled_static_plaintext_filename = source_plaintext_filename + ".static.decrypted" # Create a KMS master key provider - kms_kwargs = dict(key_ids=[key_arn]) + kms_kwargs = dict(key_ids=[aws_kms_cmk]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session kms_master_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kms_kwargs) diff --git a/examples/src/basic_file_encryption_with_raw_key_provider.py b/examples/src/legacy/basic_file_encryption_with_raw_key_provider.py similarity index 83% rename from examples/src/basic_file_encryption_with_raw_key_provider.py rename to examples/src/legacy/basic_file_encryption_with_raw_key_provider.py index 91e2a7e9a..1e3eff8e0 100644 --- a/examples/src/basic_file_encryption_with_raw_key_provider.py +++ b/examples/src/legacy/basic_file_encryption_with_raw_key_provider.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example showing creation and use of a RawMasterKeyProvider.""" import filecmp import os @@ -48,7 +38,7 @@ def _get_raw_key(self, key_id): ) -def cycle_file(source_plaintext_filename): +def run(source_plaintext_filename): """Encrypts and then decrypts a file under a custom static master key provider. :param str source_plaintext_filename: Filename of file to encrypt diff --git a/examples/src/data_key_caching_basic.py b/examples/src/legacy/data_key_caching_basic.py similarity index 67% rename from examples/src/data_key_caching_basic.py rename to examples/src/legacy/data_key_caching_basic.py index 1d5445615..899aed294 100644 --- a/examples/src/data_key_caching_basic.py +++ b/examples/src/legacy/data_key_caching_basic.py @@ -1,23 +1,13 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example of encryption with data key caching.""" import aws_encryption_sdk -def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): +def run(aws_kms_cmk, max_age_in_cache=10.0, cache_capacity=10): """Encrypts a string using an AWS KMS customer master key (CMK) and data key caching. - :param str kms_cmk_arn: Amazon Resource Name (ARN) of the KMS customer master key + :param str aws_kms_cmk: Amazon Resource Name (ARN) of the KMS customer master key :param float max_age_in_cache: Maximum time in seconds that a cached entry can be used :param int cache_capacity: Maximum number of entries to retain in cache at once """ @@ -32,7 +22,7 @@ def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): encryption_context = {"purpose": "test"} # Create a master key provider for the KMS customer master key (CMK) - key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[kms_cmk_arn]) + key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[aws_kms_cmk]) # Create a local cache cache = aws_encryption_sdk.LocalCryptoMaterialsCache(cache_capacity) diff --git a/examples/src/legacy/multiple_kms_cmk_regions.py b/examples/src/legacy/multiple_kms_cmk_regions.py new file mode 100644 index 000000000..deefd73e9 --- /dev/null +++ b/examples/src/legacy/multiple_kms_cmk_regions.py @@ -0,0 +1,65 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Example showing basic encryption and decryption of a value already in memory +using multiple KMS CMKs in multiple regions. +""" +import aws_encryption_sdk +from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider + + +def run(aws_kms_generator_cmk, aws_kms_additional_cmks, source_plaintext, botocore_session=None): + """Encrypts and then decrypts a string under multiple KMS customer master keys (CMKs) in multiple regions. + + :param str aws_kms_generator_cmk: Amazon Resource Name (ARN) of the primary KMS CMK + :param List[str] aws_kms_additional_cmks: Additional Amazon Resource Names (ARNs) of secondary KMS CMKs + :param bytes source_plaintext: Data to encrypt + :param botocore_session: existing botocore session instance + :type botocore_session: botocore.session.Session + """ + encrypt_cmk = aws_kms_additional_cmks[0] + + # Check that these keys are in different regions + assert not aws_kms_generator_cmk.split(":")[3] == encrypt_cmk.split(":")[3] + + kwargs = dict(key_ids=[aws_kms_generator_cmk, encrypt_cmk]) + + if botocore_session is not None: + kwargs["botocore_session"] = botocore_session + + # Create master key provider using the ARNs of the keys and the session (botocore_session) + kms_key_provider = KMSMasterKeyProvider(**kwargs) + + # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header + ciphertext, encrypted_message_header = aws_encryption_sdk.encrypt( + key_provider=kms_key_provider, source=source_plaintext + ) + + # Check that both key ARNs are in the message headers + assert len(encrypted_message_header.encrypted_data_keys) == 2 + + # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header + # Either of our keys can be used to decrypt the message + plaintext_1, decrypted_message_header_1 = aws_encryption_sdk.decrypt( + key_provider=KMSMasterKey(key_id=aws_kms_generator_cmk), source=ciphertext + ) + plaintext_2, decrypted_message_header_2 = aws_encryption_sdk.decrypt( + key_provider=KMSMasterKey(key_id=encrypt_cmk), source=ciphertext + ) + + # Check that the original message and the decrypted message are the same + if not isinstance(source_plaintext, bytes): + plaintext_1 = plaintext_1.decode("utf-8") + plaintext_2 = plaintext_2.decode("utf-8") + assert source_plaintext == plaintext_1 + assert source_plaintext == plaintext_2 + + # Check that the headers of the encrypted message and decrypted message match + assert all( + pair in encrypted_message_header.encryption_context.items() + for pair in decrypted_message_header_1.encryption_context.items() + ) + assert all( + pair in encrypted_message_header.encryption_context.items() + for pair in decrypted_message_header_2.encryption_context.items() + ) diff --git a/examples/src/one_kms_cmk.py b/examples/src/legacy/one_kms_cmk.py similarity index 66% rename from examples/src/one_kms_cmk.py rename to examples/src/legacy/one_kms_cmk.py index 1ba1d869f..7fb5f1431 100644 --- a/examples/src/one_kms_cmk.py +++ b/examples/src/legacy/one_kms_cmk.py @@ -1,28 +1,18 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example showing basic encryption and decryption of a value already in memory using one KMS CMK.""" import aws_encryption_sdk -def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): +def run(aws_kms_cmk, source_plaintext, botocore_session=None): """Encrypts and then decrypts a string under one KMS customer master key (CMK). - :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param str aws_kms_cmk: Amazon Resource Name (ARN) of the KMS CMK :param bytes source_plaintext: Data to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ - kwargs = dict(key_ids=[key_arn]) + kwargs = dict(key_ids=[aws_kms_cmk]) if botocore_session is not None: kwargs["botocore_session"] = botocore_session diff --git a/examples/src/one_kms_cmk_streaming_data.py b/examples/src/legacy/one_kms_cmk_streaming_data.py similarity index 72% rename from examples/src/one_kms_cmk_streaming_data.py rename to examples/src/legacy/one_kms_cmk_streaming_data.py index 3edfa82ab..90854398d 100644 --- a/examples/src/one_kms_cmk_streaming_data.py +++ b/examples/src/legacy/one_kms_cmk_streaming_data.py @@ -1,32 +1,22 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example showing basic encryption and decryption of streaming data in memory using one KMS CMK.""" import filecmp import aws_encryption_sdk -def encrypt_decrypt_stream(key_arn, source_plaintext_filename, botocore_session=None): +def run(aws_kms_cmk, source_plaintext_filename, botocore_session=None): """Encrypts and then decrypts streaming data under one KMS customer master key (CMK). - :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param str aws_kms_cmk: Amazon Resource Name (ARN) of the KMS CMK :param str source_plaintext_filename: Filename of file to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ kwargs = dict() - kwargs["key_ids"] = [key_arn] + kwargs["key_ids"] = [aws_kms_cmk] if botocore_session is not None: kwargs["botocore_session"] = botocore_session diff --git a/examples/src/one_kms_cmk_unsigned.py b/examples/src/legacy/one_kms_cmk_unsigned.py similarity index 69% rename from examples/src/one_kms_cmk_unsigned.py rename to examples/src/legacy/one_kms_cmk_unsigned.py index df2f4373d..a127261b7 100644 --- a/examples/src/one_kms_cmk_unsigned.py +++ b/examples/src/legacy/one_kms_cmk_unsigned.py @@ -1,15 +1,5 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example showing basic encryption and decryption of a value already in memory using one KMS CMK with an unsigned algorithm. """ @@ -17,15 +7,15 @@ from aws_encryption_sdk.identifiers import Algorithm -def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): +def run(aws_kms_cmk, source_plaintext, botocore_session=None): """Encrypts and then decrypts a string under one KMS customer master key (CMK) with an unsigned algorithm. - :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param str aws_kms_cmk: Amazon Resource Name (ARN) of the KMS CMK :param bytes source_plaintext: Data to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ - kwargs = dict(key_ids=[key_arn]) + kwargs = dict(key_ids=[aws_kms_cmk]) if botocore_session is not None: kwargs["botocore_session"] = botocore_session diff --git a/examples/src/onestep_defaults.py b/examples/src/onestep_defaults.py new file mode 100644 index 000000000..a1d6e3dbd --- /dev/null +++ b/examples/src/onestep_defaults.py @@ -0,0 +1,57 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example shows how to use the one-step encrypt and decrypt APIs. + +In this example, we use an AWS KMS customer master key (CMK), +but you can use other key management options with the AWS Encryption SDK. +For examples that demonstrate how to use other key management configurations, +see the ``keyring`` and ``mater_key_provider`` directories. +""" +import aws_encryption_sdk +from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring + + +def run(aws_kms_cmk, source_plaintext): + # type: (str, bytes) -> None + """Demonstrate an encrypt/decrypt cycle using the one-step encrypt/decrypt APIs. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # Create the keyring that determines how your data keys are protected. + keyring = KmsKeyring(generator_key_id=aws_kms_cmk) + + # Encrypt your plaintext data. + ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, keyring=keyring + ) + + # Verify that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data using the same keyring you used on encrypt. + # + # We do not need to specify the encryption context on decrypt + # because the header message includes the encryption context. + decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) + + # Verify that the decrypted plaintext is identical to the original plaintext. + assert decrypted == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/onestep_unsigned.py b/examples/src/onestep_unsigned.py new file mode 100644 index 000000000..aafb09feb --- /dev/null +++ b/examples/src/onestep_unsigned.py @@ -0,0 +1,77 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example shows how to specify an algorithm suite +when using the one-step encrypt and decrypt APIs. + +In this example, we use an AWS KMS customer master key (CMK), +but you can use other key management options with the AWS Encryption SDK. +For examples that demonstrate how to use other key management configurations, +see the ``keyring`` and ``mater_key_provider`` directories. + +The default algorithm suite includes a message-level signature +that protects you from an attacker who has *decrypt* but not *encrypt* capability +for a wrapping key that you used when encrypting a message +under multiple wrapping keys. + +However, if all of your readers and writers have the same permissions, +then this additional protection does not always add value. +This example shows you how to select another algorithm suite +that has all of the other properties of the default suite +but does not include a message-level signature. +""" +import aws_encryption_sdk +from aws_encryption_sdk.identifiers import AlgorithmSuite +from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring + + +def run(aws_kms_cmk, source_plaintext): + # type: (str, bytes) -> None + """Demonstrate requesting a specific algorithm suite through the one-step encrypt/decrypt APIs. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # Create the keyring that determines how your data keys are protected. + keyring = KmsKeyring(generator_key_id=aws_kms_cmk) + + # Encrypt your plaintext data. + ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, + encryption_context=encryption_context, + keyring=keyring, + # Here we can specify the algorithm suite that we want to use. + algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256, + ) + + # Verify that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data using the same keyring you used on encrypt. + # + # We do not need to specify the encryption context on decrypt + # because the header message includes the encryption context. + # + # We do not need to specify the algorithm suite on decrypt + # because the header message includes the algorithm suite identifier. + decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) + + # Verify that the decrypted plaintext is identical to the original plaintext. + assert decrypted == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 0984ee684..4f9aadef5 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -1,18 +1,29 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Helper utilities for use while testing examples.""" import os import sys +import inspect + +import pytest +import six + +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Callable, Dict, Iterable, List # noqa pylint: disable=unused-import + + # we only need pathlib here for typehints + from pathlib import Path +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + +HERE = os.path.abspath(os.path.dirname(__file__)) +EXAMPLES_SOURCE = os.path.join(HERE, "..", "src") +SINGLE_CMK_ARG = "aws_kms_cmk" +GENERATOR_CMK_ARG = "aws_kms_generator_cmk" +ADDITIONAL_CMKS_ARG = "aws_kms_additional_cmks" +PLAINTEXT_ARG = "source_plaintext" +PLAINTEXT_FILE_ARG = "source_plaintext_filename" os.environ["AWS_ENCRYPTION_SDK_EXAMPLES_TESTING"] = "yes" sys.path.extend([os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"])]) @@ -47,4 +58,59 @@ ) -from integration_test_utils import get_cmk_arn # noqa pylint: disable=unused-import,import-error +from integration_test_utils import get_all_cmk_arns # noqa pylint: disable=unused-import,import-error + + +def all_examples(): + # type: () -> Iterable[pytest.param] + for (dirpath, _dirnames, filenames) in os.walk(EXAMPLES_SOURCE): + for testfile in filenames: + split_path = testfile.rsplit(".", 1) + if len(split_path) != 2: + continue + stem, suffix = split_path + if suffix == "py" and stem != "__init__": + module_parent = dirpath[len(EXAMPLES_SOURCE) + 1 :].replace("/", ".") + module_name = stem + if module_parent: + import_path = "..src.{base}.{name}".format(base=module_parent, name=module_name) + else: + import_path = "..src.{name}".format(name=module_name) + + yield pytest.param(import_path, id="{base}.{name}".format(base=module_parent, name=module_name)) + + +def get_arg_names(function): + # type: (Callable) -> List[str] + if six.PY2: + # getargspec was deprecated in CPython 3.0 but 2.7 does not have either of the new options + spec = inspect.getargspec(function) # pylint: disable=deprecated-method + return spec.args + + spec = inspect.getfullargspec(function) + return spec.args + + +def build_kwargs(function, temp_dir): + # type: (Callable, Path) -> Dict[str, str] + + plaintext_file = temp_dir / "plaintext" + plaintext_file.write_bytes(static_plaintext) + + cmk_arns = get_all_cmk_arns() + + args = get_arg_names(function) + possible_kwargs = { + SINGLE_CMK_ARG: cmk_arns[0], + GENERATOR_CMK_ARG: cmk_arns[0], + ADDITIONAL_CMKS_ARG: cmk_arns[1:], + PLAINTEXT_ARG: static_plaintext, + PLAINTEXT_FILE_ARG: str(plaintext_file.absolute()), + } + kwargs = {} + for name in args: + try: + kwargs[name] = possible_kwargs[name] + except KeyError: + pass + return kwargs diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py deleted file mode 100644 index f2a4fab51..000000000 --- a/examples/test/test_i_basic_encryption.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the Strings examples in the AWS-hosted documentation.""" -import botocore.session -import pytest - -from ..src.basic_encryption import cycle_string -from .examples_test_utils import get_cmk_arn, static_plaintext - - -pytestmark = [pytest.mark.examples] - - -def test_cycle_string(): - plaintext = static_plaintext - cmk_arn = get_cmk_arn() - cycle_string(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py deleted file mode 100644 index 282a272ab..000000000 --- a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the Bytes Streams Multiple Providers examples in the AWS-hosted documentation.""" -import os -import tempfile - -import botocore.session -import pytest - -from ..src.basic_file_encryption_with_multiple_providers import cycle_file -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - - -pytestmark = [pytest.mark.examples] - - -def test_cycle_file(): - cmk_arn = get_cmk_arn() - handle, filename = tempfile.mkstemp() - with open(filename, "wb") as f: - f.write(static_plaintext) - try: - new_files = cycle_file( - key_arn=cmk_arn, source_plaintext_filename=filename, botocore_session=botocore.session.Session() - ) - for f in new_files: - os.remove(f) - finally: - os.close(handle) - os.remove(filename) diff --git a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py deleted file mode 100644 index 710c0ccac..000000000 --- a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the Bytes Streams examples in the AWS-hosted documentation.""" -import os -import tempfile - -import pytest - -from ..src.basic_file_encryption_with_raw_key_provider import cycle_file -from .examples_test_utils import static_plaintext - - -pytestmark = [pytest.mark.examples] - - -def test_cycle_file(): - handle, filename = tempfile.mkstemp() - with open(filename, "wb") as f: - f.write(static_plaintext) - try: - new_files = cycle_file(source_plaintext_filename=filename) - for f in new_files: - os.remove(f) - finally: - os.close(handle) - os.remove(filename) diff --git a/examples/test/test_i_data_key_caching_basic.py b/examples/test/test_i_data_key_caching_basic.py deleted file mode 100644 index 734c35692..000000000 --- a/examples/test/test_i_data_key_caching_basic.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the basic data key caching example in the AWS-hosted documentation.""" -import pytest - -from ..src.data_key_caching_basic import encrypt_with_caching -from .examples_test_utils import get_cmk_arn - - -pytestmark = [pytest.mark.examples] - - -def test_encrypt_with_caching(): - cmk_arn = get_cmk_arn() - encrypt_with_caching(kms_cmk_arn=cmk_arn, max_age_in_cache=10.0, cache_capacity=10) diff --git a/examples/test/test_i_one_kms_cmk.py b/examples/test/test_i_one_kms_cmk.py deleted file mode 100644 index 71ce74d3d..000000000 --- a/examples/test/test_i_one_kms_cmk.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the encryption and decryption using one KMS CMK example.""" - -import botocore.session -import pytest - -from ..src.one_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - - -pytestmark = [pytest.mark.examples] - - -def test_one_kms_cmk(): - plaintext = static_plaintext - cmk_arn = get_cmk_arn() - encrypt_decrypt(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_i_one_kms_cmk_streaming_data.py b/examples/test/test_i_one_kms_cmk_streaming_data.py deleted file mode 100644 index b22fa4232..000000000 --- a/examples/test/test_i_one_kms_cmk_streaming_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the encryption and decryption of streaming data using one KMS CMK example.""" -import os -import tempfile - -import botocore.session -import pytest - -from ..src.one_kms_cmk_streaming_data import encrypt_decrypt_stream -from .examples_test_utils import get_cmk_arn, static_plaintext - - -pytestmark = [pytest.mark.examples] - - -def test_one_kms_cmk_streaming_data(): - cmk_arn = get_cmk_arn() - handle, filename = tempfile.mkstemp() - with open(filename, "wb") as f: - f.write(static_plaintext) - try: - new_files = encrypt_decrypt_stream( - key_arn=cmk_arn, source_plaintext_filename=filename, botocore_session=botocore.session.Session() - ) - for f in new_files: - os.remove(f) - finally: - os.close(handle) - os.remove(filename) diff --git a/examples/test/test_i_one_kms_cmk_unsigned.py b/examples/test/test_i_one_kms_cmk_unsigned.py deleted file mode 100644 index 8a2758c96..000000000 --- a/examples/test/test_i_one_kms_cmk_unsigned.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite for the encryption and decryption using one KMS CMK with an unsigned algorithm example.""" - -import botocore.session -import pytest - -from ..src.one_kms_cmk_unsigned import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - - -pytestmark = [pytest.mark.examples] - - -def test_one_kms_cmk_unsigned(): - plaintext = static_plaintext - cmk_arn = get_cmk_arn() - encrypt_decrypt(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_run_examples.py b/examples/test/test_run_examples.py new file mode 100644 index 000000000..781f341ad --- /dev/null +++ b/examples/test/test_run_examples.py @@ -0,0 +1,24 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test all examples.""" +from importlib import import_module + +import pytest + +from .examples_test_utils import all_examples, build_kwargs + +pytestmark = [pytest.mark.examples] + + +@pytest.mark.parametrize("import_path", all_examples()) +def test_examples(import_path, tmp_path): + module = import_module(name=import_path, package=__package__) + try: + run_function = module.run + except AttributeError: + pytest.skip("Module lacks 'run' function.") + return + + kwargs = build_kwargs(function=run_function, temp_dir=tmp_path) + + run_function(**kwargs) diff --git a/test/integration/README.rst b/test/integration/README.rst index 33ecbbedd..a7dcdd5ac 100644 --- a/test/integration/README.rst +++ b/test/integration/README.rst @@ -5,8 +5,11 @@ aws-encryption-sdk Integration Tests In order to run these integration tests successfully, these things must be configured. #. Ensure that AWS credentials are available in one of the `automatically discoverable credential locations`_. -#. Set environment variable ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID`` to valid +#. Set environment variable ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID`` + and ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2`` to valid `AWS KMS key id`_ to use for integration tests. + These should be AWS KMS CMK ARNs in two different regions. + They will be used for integration tests. .. _automatically discoverable credential locations: http://boto3.readthedocs.io/en/latest/guide/configuration.html .. _AWS KMS key id: http://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html diff --git a/test/integration/integration_test_utils.py b/test/integration/integration_test_utils.py index 37e0243b7..4928f3637 100644 --- a/test/integration/integration_test_utils.py +++ b/test/integration/integration_test_utils.py @@ -20,25 +20,35 @@ from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID" +AWS_KMS_KEY_ID_2 = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2" _KMS_MKP = None _KMS_MKP_BOTO = None _KMS_KEYRING = None -def get_cmk_arn(): - """Retrieves the target CMK ARN from environment variable.""" - arn = os.environ.get(AWS_KMS_KEY_ID, None) +def _get_single_cmk_arn(name): + # type: (str) -> str + """Retrieve a single target AWS KMS CMK ARN from the specified environment variable name.""" + arn = os.environ.get(name, None) if arn is None: raise ValueError( - 'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format( - AWS_KMS_KEY_ID - ) + 'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format(name) ) if arn.startswith("arn:") and ":alias/" not in arn: return arn raise ValueError("KMS CMK ARN provided for integration tests much be a key not an alias") +def get_cmk_arn(): + """Retrieves the target AWS KMS CMK ARN from environment variable.""" + return _get_single_cmk_arn(AWS_KMS_KEY_ID) + + +def get_all_cmk_arns(): + """Retrieve all known target AWS KMS CMK ARNs from environment variables.""" + return [_get_single_cmk_arn(AWS_KMS_KEY_ID), _get_single_cmk_arn(AWS_KMS_KEY_ID_2)] + + def setup_kms_master_key_provider(cache=True): """Build an AWS KMS Master Key Provider.""" global _KMS_MKP # pylint: disable=global-statement @@ -47,7 +57,7 @@ def setup_kms_master_key_provider(cache=True): cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider() - kms_master_key_provider.add_master_key(cmk_arn) + kms_master_key_provider.add_master_key(cmk_arn.encode("utf-8")) if cache: _KMS_MKP = kms_master_key_provider @@ -63,7 +73,7 @@ def setup_kms_master_key_provider_with_botocore_session(cache=True): cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore.session.Session()) - kms_master_key_provider.add_master_key(cmk_arn) + kms_master_key_provider.add_master_key(cmk_arn.encode("utf-8")) if cache: _KMS_MKP_BOTO = kms_master_key_provider diff --git a/tox.ini b/tox.ini index d9d60d756..7895b3a0d 100644 --- a/tox.ini +++ b/tox.ini @@ -40,8 +40,9 @@ commands = pytest --basetemp={envtmpdir} -l --cov aws_encryption_sdk {posargs} [testenv] passenv = - # Identifies AWS KMS key id to use in integration tests + # Identifies AWS KMS key ids to use in integration tests AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \ # Pass through AWS credentials AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ # Pass through AWS profile name (useful for local testing) @@ -140,6 +141,10 @@ commands = [testenv:flake8-examples] basepython = {[testenv:flake8]basepython} +# This does not actually ignore errors, +# it just runs all commands regardless of whether any fail. +# If any fail, the final result will still fail. +ignore_errors = true deps = {[testenv:flake8]deps} commands = flake8 examples/src/ @@ -164,6 +169,10 @@ commands = [testenv:pylint-examples] basepython = {[testenv:pylint]basepython} +# This does not actually ignore errors, +# it just runs all commands regardless of whether any fail. +# If any fail, the final result will still fail. +ignore_errors = true deps = {[testenv:pylint]deps} commands = pylint --rcfile=examples/src/pylintrc examples/src/