Skip to content

Commit 3499d5d

Browse files
chore: Add an example illustrating MRKs (#169)
1 parent b2b6370 commit 3499d5d

File tree

10 files changed

+167
-41
lines changed

10 files changed

+167
-41
lines changed

examples/README.md

-33
This file was deleted.

examples/README.rst

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#########################################
2+
AWS DynamoDB Encryption Client Examples
3+
#########################################
4+
5+
This section features examples that show you
6+
how to use the AWS DynamoDB Encryption Client.
7+
We demonstrate how to use the encryption and decryption APIs
8+
and how to set up some common configuration patterns.
9+
10+
APIs
11+
====
12+
13+
The AWS DynamoDB Encryption Client provides four high-level APIs: `EncryptedClient`, `EncryptedItem`,
14+
`EncryptedResource`, and `EncryptedTable`.
15+
16+
You can find examples that demonstrate these APIs
17+
in the `examples/src/dynamodb_encryption_sdk_examples <./src/dynamodb_encryption_sdk_examples>`_ directory.
18+
Each of these examples uses AWS KMS as the materials provider.
19+
20+
* `How to use the EncryptedClient API <./src/dynamodb_encryption_sdk_examples/aws_kms_encrypted_client.py>`_
21+
* `How to use the EncryptedItem API <./src/dynamodb_encryption_sdk_examples/aws_kms_encrypted_item.py>`_
22+
* `How to use the EncryptedResource API <./src/dynamodb_encryption_sdk_examples/aws_kms_encrypted_resource.py>`_
23+
* `How to use the EncryptedTable API <./src/dynamodb_encryption_sdk_examples/aws_kms_encrypted_table.py>`_
24+
25+
Material Providers
26+
==================
27+
28+
To use the encryption and decryption APIs, you need to describe how you want the library to protect your data keys.
29+
You can do this by configuring material providers. AWS KMS is the most common material provider used with the AWS DynamoDB Encryption
30+
SDK, and each of the API examples above uses AWS KMS. This section describes the other providers that come bundled
31+
with this library.
32+
33+
* `How to use the CachingMostRecentProvider <./src/dynamodb_encryption_sdk_examples/most_recent_provider_encrypted_table.py>`_
34+
* `How to use raw symmetric wrapping keys <./src/dynamodb_encryption_sdk_examples/wrapped_symmetric_encrypted_table.py>`_
35+
* `How to use raw asymmetric wrapping keys <./src/dynamodb_encryption_sdk_examples/wrapped_rsa_encrypted_table.py>`_
36+
37+
For more details on the different type of material providers, see `How to choose a cryptographic materials provider <https://docs.aws.amazon.com/dynamodb-encryption-client/latest/devguide/crypto-materials-providers.html>`_.
38+
39+
Running the examples
40+
====================
41+
42+
In order to run these examples, these things must be configured:
43+
44+
#. Ensure that AWS credentials are available in one of the `automatically discoverable credential locations`_.
45+
#. The ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID`` environment variable
46+
must be set to a valid `AWS KMS CMK ARN`_ that can be used by the available credentials.
47+
#. The ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID`` and ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2`` environment variables
48+
must be set to two related AWS KMS Multi-Region key ids in different regions.
49+
#. The ``DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME`` environment variable must be set to a valid
50+
DynamoDB table name, in the default region, to which the discoverable credentials have
51+
read, write, and describe permissions.
52+
53+
.. _automatically discoverable credential locations: http://boto3.readthedocs.io/en/latest/guide/configuration.html
54+
.. _AWS KMS CMK ARN: http://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html

examples/src/dynamodb_encryption_sdk_examples/aws_kms_encrypted_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def encrypt_item(table_name, aws_cmk_id):
5454
# Get the encrypted item using the standard client.
5555
encrypted_item = client.get_item(TableName=table_name, Key=index_key)["Item"]
5656

57-
# Get the item using the encrypted client, transparently decyrpting it.
57+
# Get the item using the encrypted client, transparently decrypting it.
5858
decrypted_item = encrypted_client.get_item(TableName=table_name, Key=index_key)["Item"]
5959

6060
# Verify that all of the attributes are different in the encrypted item
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Example showing use of AWS KMS CMP with a DynamoDB Global table and an AWS Multi-Region Key."""
5+
6+
import time
7+
8+
import boto3
9+
10+
from dynamodb_encryption_sdk.encrypted.client import EncryptedClient
11+
from dynamodb_encryption_sdk.identifiers import CryptoAction
12+
from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider
13+
from dynamodb_encryption_sdk.structures import AttributeActions
14+
15+
SECOND_REGION = "eu-west-1"
16+
17+
18+
def encrypt_item(table_name, cmk_mrk_arn_first_region, cmk_mrk_arn_second_region):
19+
"""Demonstrate use of Multi-Region Keys with DynamoDB Encryption Client.
20+
21+
This example encrypts an item with a Multi-Region Key in one region and decrypts it in another region. It
22+
assumes that you have a Dynamo DB Global table in two regions, as well as a KMS
23+
Multi-Region Key replicated to these regions.
24+
"""
25+
index_key = {"partition_attribute": {"S": "is this"}, "sort_attribute": {"N": "55"}}
26+
plaintext_item = {
27+
"example": {"S": "data"},
28+
"some numbers": {"N": "99"},
29+
"and some binary": {"B": b"\x00\x01\x02"},
30+
"leave me": {"S": "alone"}, # We want to ignore this attribute
31+
}
32+
# Collect all of the attributes that will be encrypted (used later).
33+
encrypted_attributes = set(plaintext_item.keys())
34+
encrypted_attributes.remove("leave me")
35+
# Collect all of the attributes that will not be encrypted (used later).
36+
unencrypted_attributes = set(index_key.keys())
37+
unencrypted_attributes.add("leave me")
38+
# Add the index pairs to the item.
39+
plaintext_item.update(index_key)
40+
41+
# Create attribute actions that tells the encrypted client to encrypt all attributes except one.
42+
actions = AttributeActions(
43+
default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING}
44+
)
45+
46+
# Create a DDB client and KMS crypto materials provider in the first region using the specified AWS KMS key.
47+
split_arn = cmk_mrk_arn_first_region.split(":")
48+
encryption_region = split_arn[3]
49+
ddb_client = boto3.client("dynamodb", region_name=encryption_region)
50+
encryption_cmp = AwsKmsCryptographicMaterialsProvider(key_id=cmk_mrk_arn_first_region)
51+
# Use these objects to create an encrypted client.
52+
encryption_client = EncryptedClient(client=ddb_client, materials_provider=encryption_cmp, attribute_actions=actions)
53+
54+
# Put the item to the table, using the encrypted client to transparently encrypt it.
55+
encryption_client.put_item(TableName=table_name, Item=plaintext_item)
56+
57+
# Create a DDB client and KMS crypto materials provider in the second region
58+
split_arn = cmk_mrk_arn_second_region.split(":")
59+
decryption_region = split_arn[3]
60+
decryption_cmp = AwsKmsCryptographicMaterialsProvider(key_id=cmk_mrk_arn_second_region)
61+
ddb_client = boto3.client("dynamodb", region_name=decryption_region)
62+
# Use these objects to create an encrypted client.
63+
decryption_client = EncryptedClient(client=ddb_client, materials_provider=decryption_cmp, attribute_actions=actions)
64+
65+
# DDB Global Table replication takes some time. Sleep for a moment to give the item a chance to replicate to the
66+
# second region
67+
time.sleep(1)
68+
69+
# Get the item from the second region, transparently decrypting it. This allows you to avoid a cross-region KMS
70+
# call to the first region if your application is running in the second region
71+
decrypted_item = decryption_client.get_item(TableName=table_name, Key=index_key)["Item"]
72+
73+
# Verify that the decryption successfully retrieved the original plaintext
74+
for name in encrypted_attributes:
75+
assert plaintext_item[name] == decrypted_item[name]
76+
77+
# Clean up the item
78+
encryption_client.delete_item(TableName=table_name, Key=index_key)

examples/test/examples_test_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
os.environ["AWS_ENCRYPTION_SDK_EXAMPLES_TESTING"] = "yes"
66
sys.path.extend([os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"])])
77

8-
from integration_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
8+
from integration_test_utils import cmk_arn, cmk_mrk_arn, ddb_table_name, second_cmk_mrk_arn # noqa pylint: disable=unused-import

examples/test/test_aws_kms_encrypted_examples.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
aws_kms_encrypted_item,
1818
aws_kms_encrypted_resource,
1919
aws_kms_encrypted_table,
20+
aws_kms_multi_region_key,
2021
)
2122

22-
from .examples_test_utils import cmk_arn, ddb_table_name # noqa pylint: disable=unused-import
23+
from .examples_test_utils import cmk_arn, cmk_mrk_arn, ddb_table_name, second_cmk_mrk_arn # noqa pylint: disable=unused-import
2324

2425
pytestmark = [pytest.mark.examples]
2526

@@ -42,3 +43,7 @@ def test_aws_kms_encrypted_item(ddb_table_name, cmk_arn):
4243

4344
def test_aws_kms_encrypted_resource(ddb_table_name, cmk_arn):
4445
aws_kms_encrypted_resource.encrypt_batch_items(ddb_table_name, cmk_arn)
46+
47+
48+
def test_aws_kms_mrk_client(ddb_table_name, cmk_mrk_arn, second_cmk_mrk_arn):
49+
aws_kms_multi_region_key.encrypt_item(ddb_table_name, cmk_mrk_arn, second_cmk_mrk_arn)

examples/tox.ini

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ commands = python -m pytest --basetemp={envtmpdir} -l {posargs}
1111
passenv =
1212
# Identifies AWS KMS key id to use in integration tests
1313
AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \
14+
# Identifes AWS KMS Multi-Region key ids to use in examples \
15+
AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID \
16+
AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2 \
1417
# DynamoDB Table to use in integration tests
1518
DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME \
1619
# Pass through AWS credentials

test/README.rst

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ In order to run these integration tests successfully, these things which must be
88
`automatically discoverable credential locations`_.
99
#. The ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID`` environment variable
1010
must be set to a valid `AWS KMS CMK ARN`_ that can be used by the available credentials.
11+
#. The ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID`` and ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2`` environment variables
12+
must be set to two related AWS KMS Multi-Region key ids in different regions.
1113
#. The ``DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME`` environment variable must be set to a valid
1214
DynamoDB table name, in the default region, to which the discoverable credentials have
1315
read, write, and describe permissions.

test/integration/integration_test_utils.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@
2727
raise
2828

2929
AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID"
30+
AWS_KMS_MRK_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID"
31+
AWS_KMS_MRK_KEY_ID_2 = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2"
3032
DDB_TABLE_NAME = "DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME"
3133

3234

33-
def cmk_arn_value():
35+
def cmk_arn_value(env_variable=AWS_KMS_KEY_ID):
3436
"""Retrieve the target CMK ARN from environment variable."""
35-
arn = os.environ.get(AWS_KMS_KEY_ID, None)
37+
arn = os.environ.get(env_variable, None)
3638
if arn is None:
3739
raise ValueError(
3840
'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format(
39-
AWS_KMS_KEY_ID
41+
env_variable
4042
)
4143
)
4244
if arn.startswith("arn:") and ":alias/" not in arn:
@@ -47,7 +49,19 @@ def cmk_arn_value():
4749
@pytest.fixture
4850
def cmk_arn():
4951
"""As of Pytest 4.0.0, fixtures cannot be called directly."""
50-
return cmk_arn_value()
52+
return cmk_arn_value(AWS_KMS_KEY_ID)
53+
54+
55+
@pytest.fixture
56+
def cmk_mrk_arn():
57+
"""As of Pytest 4.0.0, fixtures cannot be called directly."""
58+
return cmk_arn_value(AWS_KMS_MRK_KEY_ID)
59+
60+
61+
@pytest.fixture
62+
def second_cmk_mrk_arn():
63+
"""As of Pytest 4.0.0, fixtures cannot be called directly."""
64+
return cmk_arn_value(AWS_KMS_MRK_KEY_ID_2)
5165

5266

5367
def _build_kms_cmp(require_attributes):

tox.ini

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ commands = pytest --basetemp={envtmpdir} -l {posargs}
4646
passenv =
4747
# Identifies AWS KMS key id to use in integration tests
4848
AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \
49+
# Identifies AWS KMS MRK key ids to use in integration tests
50+
AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID \
51+
AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2 \
4952
# DynamoDB Table to use in integration tests
5053
DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME \
5154
# Pass through AWS credentials
@@ -251,7 +254,7 @@ commands =
251254
# Ignore D103 docstring requirements for tests
252255
--ignore F811,D103 \
253256
# Our path munging confuses isort, so disable flake8-isort checks on that file
254-
--per-file-ignores="examples/test/examples_test_utils.py:I003,I004" \
257+
--per-file-ignores="examples/test/examples_test_utils.py:I003,I004,I005,examples/test/test_aws_kms_encrypted_examples.py:I005" \
255258
examples/test/
256259

257260
[testenv:pylint]

0 commit comments

Comments
 (0)