Skip to content

Commit af51040

Browse files
auto commit
1 parent 2e8dae0 commit af51040

File tree

2 files changed

+242
-8
lines changed

2 files changed

+242
-8
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package keyring
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"reflect"
10+
11+
mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated"
12+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
13+
dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes"
14+
dbesdkstructuredencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkstructuredencryptionsmithygeneratedtypes"
15+
"github.com/aws/aws-database-encryption-sdk-dynamodb/dbesdkmiddleware"
16+
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/utils"
17+
18+
"github.com/aws/aws-sdk-go-v2/aws"
19+
"github.com/aws/aws-sdk-go-v2/config"
20+
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
21+
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
22+
)
23+
24+
/*
25+
This example sets up a MRK discovery multi-keyring to decrypt data using
26+
the DynamoDB encryption client. A discovery keyring is not provided with any wrapping
27+
keys; instead, it recognizes the KMS key that was used to encrypt a data key,
28+
and asks KMS to decrypt with that KMS key. Discovery keyrings cannot be used
29+
to encrypt data.
30+
31+
For more information on discovery keyrings, see
32+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
33+
34+
This example encrypts an item using an MRK multi-keyring and puts the
35+
encrypted item to the configured DynamoDb table. Then, it gets the item
36+
from the table and decrypts it using the discovery keyring.
37+
38+
Running this example requires access to the DDB Table whose name
39+
is provided in CLI arguments.
40+
This table must be configured with the following
41+
primary key configuration:
42+
- Partition key is named "partition_key" with type (S)
43+
- Sort key is named "sort_key" with type (S)
44+
*/
45+
func MrkDiscoveryMultiKeyringExample(keyArn, ddbTableName string, accountIds, regions []string) {
46+
// 1. Create a single MRK multi-keyring using the key arn.
47+
// Although this example demonstrates use of the MRK discovery multi-keyring,
48+
// a discovery keyring cannot be used to encrypt. So we will need to construct
49+
// a non-discovery keyring for this example to encrypt. For more information on MRK
50+
// multi-keyrings, see the MultiMrkKeyringExample in this directory.
51+
// Though this is an "MRK multi-keyring", we do not need to provide multiple keys,
52+
// and can use single-region KMS keys. We will provide a single key here; this
53+
// can be either an MRK or a single-region key.
54+
cfg, err := config.LoadDefaultConfig(context.TODO())
55+
utils.HandleError(err)
56+
57+
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{})
58+
utils.HandleError(err)
59+
60+
createAwsKmsMrkMultiKeyringInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{
61+
Generator: &keyArn,
62+
}
63+
encryptKeyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), createAwsKmsMrkMultiKeyringInput)
64+
utils.HandleError(err)
65+
66+
// 2. Configure which attributes are encrypted and/or signed when writing new items.
67+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
68+
// we must explicitly configure how they should be treated during item encryption:
69+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and icncluded in the signature
70+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
71+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
72+
attributeActions := map[string]dbesdkstructuredencryptiontypes.CryptoAction{
73+
"partition_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Partition key must be SIGN_ONLY
74+
"sort_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Sort key must be SIGN_ONLY
75+
"sensitive_data": dbesdkstructuredencryptiontypes.CryptoActionEncryptAndSign,
76+
}
77+
78+
// 3. Configure which attributes we expect to be included in the signature
79+
// when reading items. There are two options for configuring this:
80+
//
81+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
82+
// When defining your DynamoDb schema and deciding on attribute names,
83+
// choose a distinguishing prefix (such as ":") for all attributes that
84+
// you do not want to include in the signature.
85+
// This has two main benefits:
86+
// - It is easier to reason about the security and authenticity of data within your item
87+
// when all unauthenticated data is easily distinguishable by their attribute name.
88+
// - If you need to add new unauthenticated attributes in the future,
89+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
90+
// and immediately start writing to that new attribute, without
91+
// any other configuration update needed.
92+
// Once you configure this field, it is not safe to update it.
93+
//
94+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
95+
// a set of attributes that should be considered unauthenticated when encountered
96+
// on read. Be careful if you use this configuration. Do not remove an attribute
97+
// name from this configuration, even if you are no longer writing with that attribute,
98+
// as old items may still include this attribute, and our configuration needs to know
99+
// to continue to exclude this attribute from the signature scope.
100+
// If you add new attribute names to this field, you must first deploy the update to this
101+
// field to all readers in your host fleet before deploying the update to start writing
102+
// with that new attribute.
103+
//
104+
// For this example, we currently authenticate all attributes. To make it easier to
105+
// add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
106+
unsignAttrPrefix := ":"
107+
108+
// 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
109+
tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
110+
LogicalTableName: ddbTableName,
111+
PartitionKeyName: "partition_key",
112+
SortKeyName: aws.String("sort_key"),
113+
AttributeActionsOnEncrypt: attributeActions,
114+
Keyring: encryptKeyring,
115+
AllowedUnsignedAttributePrefix: &unsignAttrPrefix,
116+
}
117+
tableConfigsMap := make(map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig)
118+
tableConfigsMap[ddbTableName] = tableConfig
119+
120+
// 5. Create the DynamoDb Encryption Interceptor
121+
dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
122+
TableEncryptionConfigs: tableConfigsMap,
123+
})
124+
utils.HandleError(err)
125+
126+
// 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
127+
ddbClient := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware())
128+
129+
// 7. Put an item into our table using the above client.
130+
// Before the item gets sent to DynamoDb, it will be encrypted
131+
// client-side using the MRK multi-keyring.
132+
item := map[string]types.AttributeValue{
133+
"partition_key": &types.AttributeValueMemberS{Value: "awsKmsMrkDiscoveryMultiKeyringItem"},
134+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
135+
"sensitive_data": &types.AttributeValueMemberS{Value: "encrypt and sign me!"},
136+
}
137+
138+
putRequest := &dynamodb.PutItemInput{
139+
TableName: aws.String(ddbTableName),
140+
Item: item,
141+
}
142+
143+
_, err = ddbClient.PutItem(context.TODO(), putRequest)
144+
utils.HandleError(err)
145+
146+
// 8. Construct a discovery filter.
147+
// A discovery filter limits the set of encrypted data keys
148+
// the keyring can use to decrypt data.
149+
// We will only let the keyring use keys in the selected AWS accounts
150+
// and in the `aws` partition.
151+
// This is the suggested config for most users; for more detailed config, see
152+
// https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
153+
discoveryFilter := mpltypes.DiscoveryFilter{
154+
Partition: "aws",
155+
AccountIds: accountIds,
156+
}
157+
158+
// 9. Construct a discovery keyring.
159+
// Note that we choose to use the MRK discovery multi-keyring, even though
160+
// our original keyring used a single KMS key.
161+
createAwsKmsMrkDiscoveryMultiKeyringInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{
162+
DiscoveryFilter: &discoveryFilter,
163+
Regions: regions,
164+
}
165+
decryptKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), createAwsKmsMrkDiscoveryMultiKeyringInput)
166+
utils.HandleError(err)
167+
168+
// 10. Create new DDB config and client using the decrypt discovery keyring.
169+
// This is the same as the above config, except we pass in the decrypt keyring.
170+
tableConfigForDecrypt := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
171+
LogicalTableName: ddbTableName,
172+
PartitionKeyName: "partition_key",
173+
SortKeyName: aws.String("sort_key"),
174+
AttributeActionsOnEncrypt: attributeActions,
175+
Keyring: decryptKeyring,
176+
AllowedUnsignedAttributePrefix: &unsignAttrPrefix,
177+
}
178+
tableConfigsMapForDecrypt := make(map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig)
179+
tableConfigsMapForDecrypt[ddbTableName] = tableConfigForDecrypt
180+
181+
dbEsdkMiddlewareForDecrypt, err := dbesdkmiddleware.NewDBEsdkMiddleware(dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
182+
TableEncryptionConfigs: tableConfigsMapForDecrypt,
183+
})
184+
utils.HandleError(err)
185+
186+
ddbClientForDecrypt := dynamodb.NewFromConfig(cfg, dbEsdkMiddlewareForDecrypt.CreateMiddleware())
187+
188+
// 11. Get the item back from our table using the client.
189+
// The client will retrieve encrypted items from the DDB table, then
190+
// detect the KMS key that was used to encrypt their data keys.
191+
// The client will make a request to KMS to decrypt with the encrypting KMS key.
192+
// If the client has permission to decrypt with the KMS key,
193+
// the client will decrypt the item client-side using the keyring
194+
// and return the original item.
195+
keyToGet := map[string]types.AttributeValue{
196+
"partition_key": &types.AttributeValueMemberS{Value: "awsKmsMrkDiscoveryMultiKeyringItem"},
197+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
198+
}
199+
200+
getRequest := &dynamodb.GetItemInput{
201+
Key: keyToGet,
202+
TableName: aws.String(ddbTableName),
203+
ConsistentRead: aws.Bool(true),
204+
}
205+
206+
getResponse, err := ddbClientForDecrypt.GetItem(context.TODO(), getRequest)
207+
utils.HandleError(err)
208+
209+
// Demonstrate that GetItem succeeded and returned the decrypted item
210+
if !reflect.DeepEqual(item, getResponse.Item) {
211+
panic("Decrypted item does not match original item")
212+
}
213+
fmt.Println("MRK Discovery Multi Keyring Example successful.")
214+
}

Examples/runtimes/go/utils/exampleUtils.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,36 @@ package utils
66
import "crypto/rand"
77

88
const (
9-
kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"
10-
ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS"
11-
keyNamespace = "my-key-namespace"
12-
keyName = "my-key-name"
13-
aesKeyBytes = 32 // 256 bits = 32 bytes
14-
testKeystoreName = "KeyStoreDdbTable"
15-
testLogicalKeystoreName = "KeyStoreDdbTable"
16-
testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"
9+
kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"
10+
ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS"
11+
keyNamespace = "my-key-namespace"
12+
keyName = "my-key-name"
13+
aesKeyBytes = 32 // 256 bits = 32 bytes
14+
testKeystoreName = "KeyStoreDdbTable"
15+
testLogicalKeystoreName = "KeyStoreDdbTable"
16+
testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"
17+
defaultRsaPublicKeyFilename = "KmsRsaKeyringPublicKey.pem"
18+
testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"
19+
defaultKMSKeyAccountID = "658956600833"
20+
defaultKmsKeyRegion = "us-west-2"
1721
)
1822

23+
func DefaultKMSKeyAccountID() []string {
24+
return []string{defaultKMSKeyAccountID}
25+
}
26+
27+
func DefaultKmsKeyRegion() []string {
28+
return []string{defaultKmsKeyRegion}
29+
}
30+
31+
func TestKmsRsaKeyID() string {
32+
return testKmsRsaKeyID
33+
}
34+
35+
func DefaultRsaPublicKeyFilename() string {
36+
return defaultRsaPublicKeyFilename
37+
}
38+
1939
func TestKeystoreName() string {
2040
return testKeystoreName
2141
}

0 commit comments

Comments
 (0)