Skip to content

Commit 4b42e45

Browse files
author
Lucas McDonald
committed
m
1 parent fbcee55 commit 4b42e45

File tree

6 files changed

+475
-0
lines changed

6 files changed

+475
-0
lines changed
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# This workflow performs tests in Python.
2+
name: Python Examples
3+
4+
on:
5+
workflow_call:
6+
inputs:
7+
dafny:
8+
description: "The Dafny version to run"
9+
required: true
10+
type: string
11+
regenerate-code:
12+
description: "Regenerate code using smithy-dafny"
13+
required: false
14+
default: false
15+
type: boolean
16+
mpl-version:
17+
description: "MPL version to use"
18+
required: false
19+
type: string
20+
mpl-head:
21+
description: "Running on MPL HEAD"
22+
required: false
23+
default: false
24+
type: boolean
25+
26+
jobs:
27+
testPython:
28+
strategy:
29+
matrix:
30+
python-version: [3.11]
31+
os: [macos-13]
32+
runs-on: ${{ matrix.os }}
33+
permissions:
34+
id-token: write
35+
contents: read
36+
steps:
37+
- name: Configure AWS Credentials
38+
uses: aws-actions/configure-aws-credentials@v4
39+
with:
40+
aws-region: us-west-2
41+
role-to-assume: arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-us-west-2
42+
role-session-name: DDBEC-Dafny-Python-Tests
43+
44+
- uses: actions/checkout@v3
45+
with:
46+
submodules: recursive
47+
48+
- name: Setup Python ${{ matrix.python-version }}
49+
uses: actions/setup-python@v4
50+
with:
51+
python-version: ${{ matrix.python-version }}
52+
53+
- name: Setup Python ${{ matrix.python-version }} for running tests
54+
run: |
55+
python -m pip install --upgrade pip
56+
pip install --upgrade tox
57+
pip install poetry
58+
59+
- name: Setup Dafny
60+
uses: ./submodules/MaterialProviders/.github/actions/setup_dafny/
61+
with:
62+
dafny-version: ${{ inputs.dafny }}
63+
64+
- name: Update MPL submodule if using MPL HEAD
65+
if: ${{ inputs.mpl-head == true }}
66+
working-directory: submodules/MaterialProviders
67+
run: |
68+
git checkout main
69+
git pull
70+
git submodule update --init --recursive
71+
git rev-parse HEAD
72+
73+
- name: Regenerate code using smithy-dafny if necessary
74+
if: ${{ inputs.regenerate-code }}
75+
uses: ./.github/actions/polymorph_codegen
76+
with:
77+
dafny: ${{ env.DAFNY_VERSION }}
78+
library: DynamoDbEncryption
79+
diff-generated-code: false
80+
update-and-regenerate-mpl: true
81+
82+
- name: Build and locally deploy dependencies for examples
83+
shell: bash
84+
working-directory: ./DynamoDbEncryption
85+
run: |
86+
make transpile_python
87+
88+
- name: Test Examples
89+
working-directory: ./Examples
90+
run: |
91+
# Run simple examples
92+
python -m pytest -s -v DynamoDBEncryption/test/
93+
# Run migration examples
94+
# python -m pytest -s -v Migration/PlaintextToAWSDBE/test/
95+
# python -m pytest -s -v Migration/DDBECToAWSDBE/test/
96+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
This example sets up an EncryptedClient wrapper for the boto3 client
5+
and uses the PutItem and GetItem DDB APIs to demonstrate
6+
putting a client-side encrypted item into DynamoDb
7+
and then retrieving and decrypting that item from DynamoDb.
8+
9+
Running this example requires access to the DDB Table whose name
10+
is provided in the function arguments.
11+
This table must be configured with the following
12+
primary key configuration:
13+
- Partition key is named "partition_key" with type (S)
14+
- Sort key is named "sort_key" with type (N)
15+
"""
16+
import boto3
17+
from boto3.dynamodb.types import Binary
18+
from decimal import Decimal
19+
20+
from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders
21+
from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig
22+
from aws_cryptographic_material_providers.mpl.models import (
23+
CreateAwsKmsMrkKeyringInput,
24+
CreateAwsKmsMrkMultiKeyringInput,
25+
DBEAlgorithmSuiteId,
26+
)
27+
from aws_cryptographic_material_providers.mpl.references import IKeyring
28+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_structuredencryption.models import (
29+
CryptoAction,
30+
)
31+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.models import (
32+
DynamoDbTableEncryptionConfig,
33+
DynamoDbTablesEncryptionConfig,
34+
)
35+
from aws_dbesdk_dynamodb.encrypted.client import (
36+
EncryptedClient
37+
)
38+
39+
def encrypted_client_put_get_example(
40+
kms_key_id: str,
41+
dynamodb_table_name: str,
42+
):
43+
# 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
44+
# For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
45+
# We will use the `CreateMrkMultiKeyring` method to create this keyring,
46+
# as it will correctly handle both single region and Multi-Region KMS Keys.
47+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
48+
config=MaterialProvidersConfig()
49+
)
50+
kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput =\
51+
CreateAwsKmsMrkMultiKeyringInput(
52+
generator=kms_key_id,
53+
)
54+
kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(
55+
input=kms_mrk_multi_keyring_input
56+
)
57+
58+
# 2. Configure which attributes are encrypted and/or signed when writing new items.
59+
# For each attribute that may exist on the items we plan to write to our DynamoDbTable,
60+
# we must explicitly configure how they should be treated during item encryption:
61+
# - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
62+
# - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
63+
# - DO_NOTHING: The attribute is not encrypted and not included in the signature
64+
attribute_actions_on_encrypt = {
65+
"partition_key": CryptoAction.SIGN_ONLY,
66+
"sort_key": CryptoAction.SIGN_ONLY,
67+
"attribute1": CryptoAction.ENCRYPT_AND_SIGN,
68+
"attribute2": CryptoAction.SIGN_ONLY,
69+
":attribute3": CryptoAction.DO_NOTHING,
70+
}
71+
72+
# 3. Configure which attributes we expect to be included in the signature
73+
# when reading items. There are two options for configuring this:
74+
#
75+
# - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
76+
# When defining your DynamoDb schema and deciding on attribute names,
77+
# choose a distinguishing prefix (such as ":") for all attributes that
78+
# you do not want to include in the signature.
79+
# This has two main benefits:
80+
# - It is easier to reason about the security and authenticity of data within your item
81+
# when all unauthenticated data is easily distinguishable by their attribute name.
82+
# - If you need to add new unauthenticated attributes in the future,
83+
# you can easily make the corresponding update to your `attributeActionsOnEncrypt`
84+
# and immediately start writing to that new attribute, without
85+
# any other configuration update needed.
86+
# Once you configure this field, it is not safe to update it.
87+
#
88+
# - Configure `allowedUnsignedAttributes`: You may also explicitly list
89+
# a set of attributes that should be considered unauthenticated when encountered
90+
# on read. Be careful if you use this configuration. Do not remove an attribute
91+
# name from this configuration, even if you are no longer writing with that attribute,
92+
# as old items may still include this attribute, and our configuration needs to know
93+
# to continue to exclude this attribute from the signature scope.
94+
# If you add new attribute names to this field, you must first deploy the update to this
95+
# field to all readers in your host fleet before deploying the update to start writing
96+
# with that new attribute.
97+
#
98+
# For this example, we have designed our DynamoDb table such that any attribute name with
99+
# the ":" prefix should be considered unauthenticated.
100+
unsignAttrPrefix: str = ":"
101+
102+
# 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
103+
table_configs = {}
104+
table_config = DynamoDbTableEncryptionConfig(
105+
logical_table_name = dynamodb_table_name,
106+
partition_key_name = "partition_key",
107+
sort_key_name = "sort_key",
108+
attribute_actions_on_encrypt = attribute_actions_on_encrypt,
109+
keyring = kms_mrk_multi_keyring,
110+
allowed_unsigned_attribute_prefix = unsignAttrPrefix,
111+
# Specifying an algorithm suite is not required,
112+
# but is done here to demonstrate how to do so.
113+
# We suggest using the
114+
# `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
115+
# which includes AES-GCM with key derivation, signing, and key commitment.
116+
# This is also the default algorithm suite if one is not specified in this config.
117+
# For more information on supported algorithm suites, see:
118+
# https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
119+
algorithm_suite_id = DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384,
120+
)
121+
table_configs[dynamodb_table_name] = table_config
122+
tables_config = DynamoDbTablesEncryptionConfig(
123+
table_encryption_configs = table_configs
124+
)
125+
126+
# 5. Create the EncryptedClient
127+
encrypted_client = EncryptedClient(
128+
client = boto3.client("dynamodb"),
129+
encryption_config = tables_config,
130+
)
131+
132+
# 6. Put an item into our table using the above client.
133+
# Before the item gets sent to DynamoDb, it will be encrypted
134+
# client-side, according to our configuration.
135+
item_to_encrypt = {
136+
"partition_key": {"S": "BasicPutGetExample"},
137+
"sort_key": {"N": "0"},
138+
"attribute1": {"S": "encrypt and sign me!"},
139+
"attribute2": {"S": "sign me!"},
140+
":attribute3": {"S": "ignore me!"},
141+
}
142+
143+
put_item_request = {
144+
"TableName": dynamodb_table_name,
145+
"Item": item_to_encrypt,
146+
}
147+
148+
put_item_response = encrypted_client.put_item(**put_item_request)
149+
150+
# Demonstrate that PutItem succeeded
151+
assert put_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200
152+
153+
# 7. Get the item back from our table using the same client.
154+
# The client will decrypt the item client-side, and return
155+
# back the original item.
156+
key_to_get = {
157+
"partition_key": {"S": "BasicPutGetExample"},
158+
"sort_key": {"N": "0"}
159+
}
160+
161+
get_item_request = {
162+
"TableName": dynamodb_table_name,
163+
"Key": key_to_get
164+
}
165+
166+
get_item_response = encrypted_client.get_item(**get_item_request)
167+
168+
# Demonstrate that GetItem succeeded and returned the decrypted item
169+
assert get_item_response["ResponseMetadata"]["HTTPStatusCode"] == 200
170+
assert get_item_response["Item"] == item_to_encrypt

0 commit comments

Comments
 (0)