Skip to content

Commit f451e17

Browse files
committed
hierarchical_keyring_example
1 parent 44c45da commit f451e17

File tree

8 files changed

+300
-121
lines changed

8 files changed

+300
-121
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""

Examples/runtimes/python/DynamoDBEncryption/src/keyring/hierarchical_keyring_example.py renamed to Examples/runtimes/python/DynamoDBEncryption/src/keyring/hierarchical_keyring_example/encryption_config.py

Lines changed: 9 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,10 @@
11
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
"""
4-
Example demonstrating DynamoDb Encryption using a Hierarchical Keyring.
4+
Configuration module for hierarchical keyring encryption setup.
55
6-
This example sets up DynamoDb Encryption for the AWS SDK client
7-
using the Hierarchical Keyring, which establishes a key hierarchy
8-
where "branch" keys are persisted in DynamoDb.
9-
These branch keys are used to protect your data keys,
10-
and these branch keys are themselves protected by a root KMS Key.
11-
12-
Establishing a key hierarchy like this has two benefits:
13-
14-
First, by caching the branch key material, and only calling back
15-
to KMS to re-establish authentication regularly according to your configured TTL,
16-
you limit how often you need to call back to KMS to protect your data.
17-
This is a performance/security tradeoff, where your authentication, audit, and
18-
logging from KMS is no longer one-to-one with every encrypt or decrypt call.
19-
However, the benefit is that you no longer have to make a
20-
network call to KMS for every encrypt or decrypt.
21-
22-
Second, this key hierarchy makes it easy to hold multi-tenant data
23-
that is isolated per branch key in a single DynamoDb table.
24-
You can create a branch key for each tenant in your table,
25-
and encrypt all that tenant's data under that distinct branch key.
26-
On decrypt, you can either statically configure a single branch key
27-
to ensure you are restricting decryption to a single tenant,
28-
or you can implement an interface that lets you map the primary key on your items
29-
to the branch key that should be responsible for decrypting that data.
30-
31-
This example then demonstrates configuring a Hierarchical Keyring
32-
with a Branch Key ID Supplier to encrypt and decrypt data for
33-
two separate tenants.
34-
35-
Running this example requires access to the DDB Table whose name
36-
is provided in CLI arguments.
37-
This table must be configured with the following
38-
primary key configuration:
39-
- Partition key is named "partition_key" with type (S)
40-
- Sort key is named "sort_key" with type (S)
41-
42-
This example also requires using a KMS Key whose ARN
43-
is provided in CLI arguments. You need the following access
44-
on this key:
45-
- GenerateDataKeyWithoutPlaintext
46-
- Decrypt
6+
This module provides the common encryption configuration used by both
7+
EncryptedClient and EncryptedTable examples.
478
"""
489

4910
import boto3
@@ -57,7 +18,6 @@
5718
CreateAwsKmsHierarchicalKeyringInput,
5819
DefaultCache,
5920
)
60-
from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
6121
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.client import DynamoDbEncryption
6222
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.config import (
6323
DynamoDbEncryptionConfig,
@@ -71,26 +31,27 @@
7131
CryptoAction,
7232
)
7333

74-
from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier
34+
from ..example_branch_key_id_supplier import ExampleBranchKeyIdSupplier
7535

7636

77-
def hierarchical_keyring_get_item_put_item(
37+
def create_encryption_config(
7838
ddb_table_name: str,
7939
tenant1_branch_key_id: str,
8040
tenant2_branch_key_id: str,
8141
keystore_table_name: str,
8242
logical_keystore_name: str,
8343
kms_key_id: str,
84-
):
44+
) -> DynamoDbTablesEncryptionConfig:
8545
"""
86-
Demonstrate using a hierarchical keyring with multiple tenants.
46+
Create the encryption configuration for DynamoDB encryption.
8747
8848
:param ddb_table_name: The name of the DynamoDB table
8949
:param tenant1_branch_key_id: Branch key ID for tenant 1
9050
:param tenant2_branch_key_id: Branch key ID for tenant 2
9151
:param keystore_table_name: The name of the KeyStore DynamoDB table
9252
:param logical_keystore_name: The logical name for this keystore
9353
:param kms_key_id: The ARN of the KMS key to use
54+
:return: The DynamoDB tables encryption configuration
9455
"""
9556
# Initial KeyStore Setup: This example requires that you have already
9657
# created your KeyStore, and have populated it with two new branch keys.
@@ -190,40 +151,4 @@ def hierarchical_keyring_get_item_put_item(
190151
)
191152

192153
table_configs = {ddb_table_name: table_config}
193-
tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
194-
195-
# 7. Create the EncryptedClient
196-
ddb_client = boto3.client("dynamodb")
197-
encrypted_ddb_client = EncryptedClient(client=ddb_client, encryption_config=tables_config)
198-
199-
# 8. Put an item into our table using the above client.
200-
# Before the item gets sent to DynamoDb, it will be encrypted
201-
# client-side, according to our configuration.
202-
# Because the item we are writing uses "tenantId1" as our partition value,
203-
# based on the code we wrote in the ExampleBranchKeySupplier,
204-
# `tenant1_branch_key_id` will be used to encrypt this item.
205-
item = {
206-
"partition_key": {"S": "tenant1Id"},
207-
"sort_key": {"N": "0"},
208-
"tenant_sensitive_data": {"S": "encrypt and sign me!"},
209-
}
210-
211-
put_response = encrypted_ddb_client.put_item(TableName=ddb_table_name, Item=item)
212-
213-
# Demonstrate that PutItem succeeded
214-
assert put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
215-
216-
# 9. Get the item back from our table using the same client.
217-
# The client will decrypt the item client-side, and return
218-
# back the original item.
219-
# Because the returned item's partition value is "tenantId1",
220-
# based on the code we wrote in the ExampleBranchKeySupplier,
221-
# `tenant1_branch_key_id` will be used to decrypt this item.
222-
key_to_get = {"partition_key": {"S": "tenant1Id"}, "sort_key": {"N": "0"}}
223-
224-
get_response = encrypted_ddb_client.get_item(TableName=ddb_table_name, Key=key_to_get)
225-
226-
# Demonstrate that GetItem succeeded and returned the decrypted item
227-
assert get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
228-
returned_item = get_response["Item"]
229-
assert returned_item["tenant_sensitive_data"]["S"] == "encrypt and sign me!"
154+
return DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Example demonstrating DynamoDb Encryption using a Hierarchical Keyring with EncryptedClient.
5+
6+
This example sets up DynamoDb Encryption for the AWS SDK client
7+
using the Hierarchical Keyring, which establishes a key hierarchy
8+
where "branch" keys are persisted in DynamoDb.
9+
These branch keys are used to protect your data keys,
10+
and these branch keys are themselves protected by a root KMS Key.
11+
12+
Running this example requires access to the DDB Table whose name
13+
is provided in CLI arguments.
14+
This table must be configured with the following
15+
primary key configuration:
16+
- Partition key is named "partition_key" with type (S)
17+
- Sort key is named "sort_key" with type (S)
18+
19+
This example also requires using a KMS Key whose ARN
20+
is provided in CLI arguments. You need the following access
21+
on this key:
22+
- GenerateDataKeyWithoutPlaintext
23+
- Decrypt
24+
"""
25+
26+
import boto3
27+
from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
28+
29+
from .encryption_config import create_encryption_config
30+
31+
32+
def hierarchical_keyring_client_example(
33+
ddb_table_name: str,
34+
tenant1_branch_key_id: str,
35+
tenant2_branch_key_id: str,
36+
keystore_table_name: str,
37+
logical_keystore_name: str,
38+
kms_key_id: str,
39+
):
40+
"""
41+
Demonstrate using a hierarchical keyring with multiple tenants using EncryptedClient.
42+
43+
:param ddb_table_name: The name of the DynamoDB table
44+
:param tenant1_branch_key_id: Branch key ID for tenant 1
45+
:param tenant2_branch_key_id: Branch key ID for tenant 2
46+
:param keystore_table_name: The name of the KeyStore DynamoDB table
47+
:param logical_keystore_name: The logical name for this keystore
48+
:param kms_key_id: The ARN of the KMS key to use
49+
"""
50+
# 1. Create the DynamoDb Encryption configuration for the table we will be writing to.
51+
# See encryption_config.py in this directory for detailed steps on the encryption configuration.
52+
tables_config = create_encryption_config(
53+
ddb_table_name=ddb_table_name,
54+
tenant1_branch_key_id=tenant1_branch_key_id,
55+
tenant2_branch_key_id=tenant2_branch_key_id,
56+
keystore_table_name=keystore_table_name,
57+
logical_keystore_name=logical_keystore_name,
58+
kms_key_id=kms_key_id,
59+
)
60+
61+
# 2. Create the EncryptedClient
62+
ddb_client = boto3.client("dynamodb")
63+
encrypted_ddb_client = EncryptedClient(client=ddb_client, encryption_config=tables_config)
64+
65+
# 3. Put an item into our table using the above client.
66+
# Before the item gets sent to DynamoDb, it will be encrypted
67+
# client-side, according to our configuration.
68+
# Because the item we are writing uses "tenantId1" as our partition value,
69+
# based on the code we wrote in the ExampleBranchKeySupplier,
70+
# `tenant1_branch_key_id` will be used to encrypt this item.
71+
item = {
72+
"partition_key": {"S": "tenant1Id"},
73+
"sort_key": {"N": "0"},
74+
"tenant_sensitive_data": {"S": "encrypt and sign me!"},
75+
}
76+
77+
put_response = encrypted_ddb_client.put_item(TableName=ddb_table_name, Item=item)
78+
79+
# Demonstrate that PutItem succeeded
80+
assert put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
81+
82+
# 4. Get the item back from our table using the same client.
83+
# The client will decrypt the item client-side, and return
84+
# back the original item.
85+
# Because the returned item's partition value is "tenantId1",
86+
# based on the code we wrote in the ExampleBranchKeySupplier,
87+
# `tenant1_branch_key_id` will be used to decrypt this item.
88+
key_to_get = {"partition_key": {"S": "tenant1Id"}, "sort_key": {"N": "0"}}
89+
90+
get_response = encrypted_ddb_client.get_item(TableName=ddb_table_name, Key=key_to_get)
91+
92+
# Demonstrate that GetItem succeeded and returned the decrypted item
93+
assert get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
94+
returned_item = get_response["Item"]
95+
assert returned_item["tenant_sensitive_data"]["S"] == "encrypt and sign me!"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Example demonstrating DynamoDb Encryption using a Hierarchical Keyring with EncryptedTable.
5+
6+
This example sets up DynamoDb Encryption for the AWS SDK table
7+
using the Hierarchical Keyring, which establishes a key hierarchy
8+
where "branch" keys are persisted in DynamoDb.
9+
These branch keys are used to protect your data keys,
10+
and these branch keys are themselves protected by a root KMS Key.
11+
12+
Running this example requires access to the DDB Table whose name
13+
is provided in CLI arguments.
14+
This table must be configured with the following
15+
primary key configuration:
16+
- Partition key is named "partition_key" with type (S)
17+
- Sort key is named "sort_key" with type (S)
18+
19+
This example also requires using a KMS Key whose ARN
20+
is provided in CLI arguments. You need the following access
21+
on this key:
22+
- GenerateDataKeyWithoutPlaintext
23+
- Decrypt
24+
"""
25+
26+
import boto3
27+
from aws_dbesdk_dynamodb.encrypted.table import EncryptedTable
28+
29+
from .encryption_config import create_encryption_config
30+
31+
32+
def hierarchical_keyring_table_example(
33+
ddb_table_name: str,
34+
tenant1_branch_key_id: str,
35+
tenant2_branch_key_id: str,
36+
keystore_table_name: str,
37+
logical_keystore_name: str,
38+
kms_key_id: str,
39+
):
40+
"""
41+
Demonstrate using a hierarchical keyring with multiple tenants using EncryptedTable.
42+
43+
:param ddb_table_name: The name of the DynamoDB table
44+
:param tenant1_branch_key_id: Branch key ID for tenant 1
45+
:param tenant2_branch_key_id: Branch key ID for tenant 2
46+
:param keystore_table_name: The name of the KeyStore DynamoDB table
47+
:param logical_keystore_name: The logical name for this keystore
48+
:param kms_key_id: The ARN of the KMS key to use
49+
"""
50+
# 1. Create the DynamoDb Encryption configuration for the table we will be writing to.
51+
# See encryption_config.py in this directory for detailed steps on how this is done.
52+
tables_config = create_encryption_config(
53+
ddb_table_name=ddb_table_name,
54+
tenant1_branch_key_id=tenant1_branch_key_id,
55+
tenant2_branch_key_id=tenant2_branch_key_id,
56+
keystore_table_name=keystore_table_name,
57+
logical_keystore_name=logical_keystore_name,
58+
kms_key_id=kms_key_id,
59+
)
60+
61+
# 2. Create the EncryptedTable
62+
ddb_table = boto3.resource("dynamodb").Table(ddb_table_name)
63+
encrypted_table = EncryptedTable(
64+
table=ddb_table,
65+
encryption_config=tables_config,
66+
)
67+
68+
# 3. Put an item into our table using the above table.
69+
# Before the item gets sent to DynamoDb, it will be encrypted
70+
# client-side, according to our configuration.
71+
# Because the item we are writing uses "tenantId1" as our partition value,
72+
# based on the code we wrote in the ExampleBranchKeySupplier,
73+
# `tenant1_branch_key_id` will be used to encrypt this item.
74+
item = {
75+
"partition_key": "tenant1Id",
76+
"sort_key": 0,
77+
"tenant_sensitive_data": "encrypt and sign me!",
78+
}
79+
80+
put_response = encrypted_table.put_item(Item=item)
81+
82+
# Demonstrate that PutItem succeeded
83+
assert put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
84+
85+
# 4. Get the item back from our table using the same table.
86+
# The table will decrypt the item client-side, and return
87+
# back the original item.
88+
# Because the returned item's partition value is "tenantId1",
89+
# based on the code we wrote in the ExampleBranchKeySupplier,
90+
# `tenant1_branch_key_id` will be used to decrypt this item.
91+
key_to_get = {"partition_key": "tenant1Id", "sort_key": 0}
92+
93+
get_response = encrypted_table.get_item(Key=key_to_get)
94+
95+
# Demonstrate that GetItem succeeded and returned the decrypted item
96+
assert get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
97+
returned_item = get_response["Item"]
98+
assert returned_item["tenant_sensitive_data"] == "encrypt and sign me!"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Test hierarchical keyring with abstraction EncryptedClient example."""
4+
import time
5+
6+
import pytest
7+
8+
from ....src.create_keystore_key_example import keystore_create_key
9+
from ....src.keyring.hierarchical_keyring_example.with_encrypted_client import (
10+
hierarchical_keyring_client_example,
11+
)
12+
from ...cleanup import delete_branch_key
13+
from ...test_utils import (
14+
TEST_DDB_TABLE_NAME,
15+
TEST_KEYSTORE_KMS_KEY_ID,
16+
TEST_KEYSTORE_NAME,
17+
TEST_LOGICAL_KEYSTORE_NAME,
18+
)
19+
20+
pytestmark = [pytest.mark.examples]
21+
22+
23+
def test_hierarchical_keyring_client_example():
24+
"""Test hierarchical keyring abstracted client example."""
25+
# Create new branch keys for test
26+
key_id1 = keystore_create_key(TEST_KEYSTORE_NAME, TEST_LOGICAL_KEYSTORE_NAME, TEST_KEYSTORE_KMS_KEY_ID)
27+
key_id2 = keystore_create_key(TEST_KEYSTORE_NAME, TEST_LOGICAL_KEYSTORE_NAME, TEST_KEYSTORE_KMS_KEY_ID)
28+
29+
try:
30+
# Key creation is eventually consistent, so wait 5 seconds to decrease the likelihood
31+
# our test fails due to eventual consistency issues.
32+
time.sleep(5)
33+
34+
# Run the client example
35+
hierarchical_keyring_client_example(
36+
TEST_DDB_TABLE_NAME,
37+
key_id1,
38+
key_id2,
39+
TEST_KEYSTORE_NAME,
40+
TEST_LOGICAL_KEYSTORE_NAME,
41+
TEST_KEYSTORE_KMS_KEY_ID,
42+
)
43+
finally:
44+
# Cleanup Branch Keys
45+
delete_branch_key(key_id1, TEST_KEYSTORE_NAME, None)
46+
delete_branch_key(key_id2, TEST_KEYSTORE_NAME, None)

0 commit comments

Comments
 (0)