Skip to content

Commit d0c7828

Browse files
auto commit
1 parent 69c37c6 commit d0c7828

File tree

5 files changed

+354
-8
lines changed

5 files changed

+354
-8
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package clientsupplier
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated"
11+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
12+
dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes"
13+
dbesdkstructuredencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkstructuredencryptionsmithygeneratedtypes"
14+
"github.com/aws/aws-database-encryption-sdk-dynamodb/dbesdkmiddleware"
15+
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/utils"
16+
17+
"github.com/aws/aws-sdk-go-v2/aws"
18+
"github.com/aws/aws-sdk-go-v2/config"
19+
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
20+
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
21+
)
22+
23+
/*
24+
This example sets up an MRK multi-keyring and an MRK discovery
25+
multi-keyring using a custom client supplier.
26+
A custom client supplier grants users access to more granular
27+
configuration aspects of their authentication details and KMS
28+
client. In this example, we create a simple custom client supplier
29+
that authenticates with a different IAM role based on the
30+
region of the KMS key.
31+
32+
This example creates a MRK multi-keyring configured with a custom
33+
client supplier using a single MRK and puts an encrypted item to the
34+
table. Then, it creates a MRK discovery multi-keyring to decrypt the item
35+
and retrieves the item from the table.
36+
37+
Running this example requires access to the DDB Table whose name
38+
is provided in CLI arguments.
39+
This table must be configured with the following
40+
primary key configuration:
41+
- Partition key is named "partition_key" with type (S)
42+
- Sort key is named "sort_key" with type (S)
43+
*/
44+
func ClientSupplierExample(ddbTableName, keyArn string, accountIds, regions []string) {
45+
// 1. Create a single MRK multi-keyring.
46+
// This can be either a single-region KMS key or an MRK.
47+
// For this example to succeed, the key's region must either
48+
// 1) be in the regions list, or
49+
// 2) the key must be an MRK with a replica defined
50+
// in a region in the regions list, and the client
51+
// must have the correct permissions to access the replica.
52+
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{})
53+
utils.HandleError(err)
54+
55+
// Create the multi-keyring using our custom client supplier
56+
// defined in the RegionalRoleClientSupplier class in this directory.
57+
createAwsKmsMrkMultiKeyringInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{
58+
// Note: RegionalRoleClientSupplier will internally use the keyArn's region
59+
// to retrieve the correct IAM role.
60+
ClientSupplier: &RegionalRoleClientSupplier{},
61+
Generator: &keyArn,
62+
}
63+
mrkKeyringWithClientSupplier, 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 included in the signature
70+
// - SIGN_ONLY: The attribute is not encrypted, but is still included in the signature
71+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
72+
attributeActionsOnEncrypt := map[string]dbesdkstructuredencryptiontypes.CryptoAction{
73+
"partition_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our partition attribute must be SIGN_ONLY
74+
"sort_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our sort attribute 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+
partitionKey := "partition_key"
108+
sortKey := "sort_key"
109+
// 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
110+
tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
111+
LogicalTableName: ddbTableName,
112+
PartitionKeyName: partitionKey,
113+
SortKeyName: &sortKey,
114+
AttributeActionsOnEncrypt: attributeActionsOnEncrypt,
115+
Keyring: mrkKeyringWithClientSupplier,
116+
AllowedUnsignedAttributePrefix: &unsignAttrPrefix,
117+
}
118+
119+
tableConfigs := map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
120+
ddbTableName: tableConfig,
121+
}
122+
123+
// 5. Create the DynamoDb Encryption Interceptor
124+
encryptionConfig := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
125+
TableEncryptionConfigs: tableConfigs,
126+
}
127+
128+
// 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
129+
cfg, err := config.LoadDefaultConfig(context.TODO())
130+
utils.HandleError(err)
131+
132+
dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(encryptionConfig)
133+
utils.HandleError(err)
134+
ddbClient := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware())
135+
136+
// 7. Put an item into our table using the above client.
137+
// Before the item gets sent to DynamoDb, it will be encrypted
138+
// client-side using the MRK multi-keyring.
139+
// The data key protecting this item will be encrypted
140+
// with all the KMS Keys in this keyring, so that it can be
141+
// decrypted with any one of those KMS Keys.
142+
item := map[string]types.AttributeValue{
143+
"partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"},
144+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
145+
"sensitive_data": &types.AttributeValueMemberS{Value: "encrypt and sign me!"},
146+
}
147+
148+
putRequest := &dynamodb.PutItemInput{
149+
TableName: &ddbTableName,
150+
Item: item,
151+
}
152+
153+
_, err = ddbClient.PutItem(context.Background(), putRequest)
154+
utils.HandleError(err)
155+
156+
// 8. Get the item back from our table using the same keyring.
157+
// The client will decrypt the item client-side using the MRK
158+
// and return the original item.
159+
keyToGet := map[string]types.AttributeValue{
160+
"partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"},
161+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
162+
}
163+
164+
getRequest := &dynamodb.GetItemInput{
165+
Key: keyToGet,
166+
TableName: aws.String(ddbTableName),
167+
}
168+
169+
getResponse, err := ddbClient.GetItem(context.Background(), getRequest)
170+
utils.HandleError(err)
171+
172+
// Demonstrate that GetItem succeeded and returned the decrypted item
173+
returnedItem := getResponse.Item
174+
sensitiveData := returnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value
175+
if sensitiveData != "encrypt and sign me!" {
176+
panic("Decrypted data does not match expected value")
177+
}
178+
179+
// 9. Create a MRK discovery multi-keyring with a custom client supplier.
180+
// A discovery MRK multi-keyring will be composed of
181+
// multiple discovery MRK keyrings, one for each region.
182+
// Each component keyring has its own KMS client in a particular region.
183+
// When we provide a client supplier to the multi-keyring, all component
184+
// keyrings will use that client supplier configuration.
185+
// In our tests, we make `keyArn` an MRK with a replica, and
186+
// provide only the replica region in our discovery filter.
187+
discoveryFilter := mpltypes.DiscoveryFilter{
188+
Partition: "aws",
189+
AccountIds: accountIds,
190+
}
191+
192+
mrkDiscoveryClientSupplierInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{
193+
ClientSupplier: &RegionalRoleClientSupplier{},
194+
DiscoveryFilter: &discoveryFilter,
195+
Regions: regions,
196+
}
197+
mrkDiscoveryClientSupplierKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), mrkDiscoveryClientSupplierInput)
198+
utils.HandleError(err)
199+
200+
// 10. Create a new config and client using the discovery keyring.
201+
// This is the same setup as above, except we provide the discovery keyring to the config.
202+
onlyReplicaKeyTableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
203+
LogicalTableName: ddbTableName,
204+
PartitionKeyName: partitionKey,
205+
SortKeyName: &sortKey,
206+
AttributeActionsOnEncrypt: attributeActionsOnEncrypt,
207+
// Provide discovery keyring here
208+
Keyring: mrkDiscoveryClientSupplierKeyring,
209+
AllowedUnsignedAttributePrefix: &unsignAttrPrefix,
210+
}
211+
212+
onlyReplicaKeyTableConfigs := map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
213+
ddbTableName: onlyReplicaKeyTableConfig,
214+
}
215+
216+
onlyReplicaKeyEncryptionConfig := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
217+
TableEncryptionConfigs: onlyReplicaKeyTableConfigs,
218+
}
219+
220+
onlyReplicaKeyDbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(onlyReplicaKeyEncryptionConfig)
221+
utils.HandleError(err)
222+
onlyReplicaKeyDdbClient := dynamodb.NewFromConfig(cfg, onlyReplicaKeyDbEsdkMiddleware.CreateMiddleware())
223+
224+
// 11. Get the item back from our table using the discovery keyring client.
225+
// The client will decrypt the item client-side using the keyring,
226+
// and return the original item.
227+
// The discovery keyring will only use KMS keys in the provided regions and
228+
// AWS accounts. Since we have provided it with a custom client supplier
229+
// which uses different IAM roles based on the key region,
230+
// the discovery keyring will use a particular IAM role to decrypt
231+
// based on the region of the KMS key it uses to decrypt.
232+
onlyReplicaKeyKeyToGet := map[string]types.AttributeValue{
233+
"partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"},
234+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
235+
}
236+
237+
onlyReplicaKeyGetRequest := &dynamodb.GetItemInput{
238+
Key: onlyReplicaKeyKeyToGet,
239+
TableName: &ddbTableName,
240+
}
241+
242+
onlyReplicaKeyGetResponse, err := onlyReplicaKeyDdbClient.GetItem(context.Background(), onlyReplicaKeyGetRequest)
243+
utils.HandleError(err)
244+
245+
// Demonstrate that GetItem succeeded and returned the decrypted item
246+
fmt.Println("GetItem with discovery keyring completed successfully")
247+
onlyReplicaKeyReturnedItem := onlyReplicaKeyGetResponse.Item
248+
onlyReplicaKeySensitiveData := onlyReplicaKeyReturnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value
249+
if onlyReplicaKeySensitiveData != "encrypt and sign me!" {
250+
panic("Decrypted data from discovery keyring does not match expected value")
251+
}
252+
253+
fmt.Println("Client Supplier Example completed successfully")
254+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package clientsupplier
5+
6+
/*
7+
Class containing config for the RegionalRoleClientSupplier.
8+
In your own code, this might be hardcoded, or reference
9+
an external source, e.g. environment variables or AWS AppConfig.
10+
*/
11+
type RegionalRoleClientSupplierConfig struct {
12+
RegionIamRoleMap map[string]string
13+
}
14+
15+
const (
16+
usEast1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-only-us-east-1-KMS-keys"
17+
euWest1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-only-eu-west-1-KMS-keys"
18+
)
19+
20+
func NewRegionalRoleClientSupplierConfig() *RegionalRoleClientSupplierConfig {
21+
return &RegionalRoleClientSupplierConfig{
22+
RegionIamRoleMap: map[string]string{
23+
"us-east-1": usEast1IamRole,
24+
"eu-west-1": euWest1IamRole,
25+
},
26+
}
27+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package clientsupplier
5+
6+
import (
7+
"context"
8+
9+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
10+
11+
"github.com/aws/aws-sdk-go-v2/config"
12+
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
13+
"github.com/aws/aws-sdk-go-v2/service/kms"
14+
"github.com/aws/aws-sdk-go-v2/service/sts"
15+
)
16+
17+
/*
18+
Example class demonstrating an implementation of a custom client supplier.
19+
This particular implementation will create KMS clients with different IAM roles,
20+
depending on the region passed.
21+
*/
22+
type RegionalRoleClientSupplier struct{}
23+
24+
func (r *RegionalRoleClientSupplier) GetClient(input mpltypes.GetClientInput) (kms.Client, error) {
25+
supplierConfig := NewRegionalRoleClientSupplierConfig()
26+
27+
roleArn, exists := supplierConfig.RegionIamRoleMap[input.Region]
28+
if !exists {
29+
return kms.Client{}, mpltypes.AwsCryptographicMaterialProvidersException{
30+
Message: "Missing region: " + input.Region,
31+
}
32+
}
33+
34+
// Load default AWS config
35+
cfg, err := config.LoadDefaultConfig(context.TODO())
36+
if err != nil {
37+
return kms.Client{}, err
38+
}
39+
40+
// Create STS client for assuming role
41+
stsClient := sts.NewFromConfig(cfg)
42+
43+
// Create credentials provider that assumes the role
44+
roleProvider := stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(o *stscreds.AssumeRoleOptions) {
45+
o.RoleSessionName = "Go-Client-Supplier-Example-Session"
46+
})
47+
48+
// Create KMS client with the assumed role credentials
49+
sdkConfig, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(input.Region), config.WithCredentialsProvider(roleProvider))
50+
kmsClient := kms.NewFromConfig(sdkConfig)
51+
52+
return *kmsClient, nil
53+
}

Examples/runtimes/go/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
package main
55

66
import (
7+
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/clientsupplier"
78
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/itemencryptor"
89
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/keyring"
910
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/misc"
1011
"github.com/aws/aws-database-encryption-sdk-dynamodb/examples/utils"
1112
)
1213

1314
func main() {
15+
clientsupplier.ClientSupplierExample(utils.DdbTableName(), utils.TestMrkReplicaKeyIdUsEast1(), utils.DefaultKMSKeyAccountID(), []string{"eu-west-1"})
1416
keyring.AwsKmsKeyringExample(utils.KmsKeyID(), utils.DdbTableName())
1517
keyring.RawAesExample(utils.DdbTableName(), utils.KeyNamespace(), utils.KeyName(), utils.GenerateAes256KeyBytes())
1618
itemencryptor.ItemEncryptDecryptExample(utils.KmsKeyID(), utils.DdbTableName())

Examples/runtimes/go/utils/exampleUtils.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,26 @@ 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+
defaultKMSKeyAccountID = "658956600833"
18+
testMrkReplicaKeyIdUsEast1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"
1719
)
1820

21+
func TestMrkReplicaKeyIdUsEast1() string {
22+
return testMrkReplicaKeyIdUsEast1
23+
}
24+
25+
func DefaultKMSKeyAccountID() []string {
26+
return []string{defaultKMSKeyAccountID}
27+
}
28+
1929
func TestKeystoreName() string {
2030
return testKeystoreName
2131
}

0 commit comments

Comments
 (0)