Skip to content

Commit 29406fe

Browse files
Merge branch 'main' into rishav/go/complexExample
2 parents 5a22216 + 2e36410 commit 29406fe

File tree

6 files changed

+495
-0
lines changed

6 files changed

+495
-0
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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+
"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 an MRK multi-keyring and an MRK discovery
26+
multi-keyring using a custom client supplier.
27+
A custom client supplier grants users access to more granular
28+
configuration aspects of their authentication details and KMS
29+
client. In this example, we create a simple custom client supplier
30+
that authenticates with a different IAM role based on the
31+
region of the KMS key.
32+
33+
This example creates a MRK multi-keyring configured with a custom
34+
client supplier using a single MRK and puts an encrypted item to the
35+
table. Then, it creates a MRK discovery multi-keyring to decrypt the item
36+
and retrieves the item from the table.
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 ClientSupplierExample(ddbTableName, keyArn string, accountIds, regions []string) {
46+
// 1. Create a single MRK multi-keyring.
47+
// This can be either a single-region KMS key or an MRK.
48+
// For this example to succeed, the key's region must either
49+
// 1) be in the regions list, or
50+
// 2) the key must be an MRK with a replica defined
51+
// in a region in the regions list, and the client
52+
// must have the correct permissions to access the replica.
53+
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{})
54+
utils.HandleError(err)
55+
56+
// Create the multi-keyring using our custom client supplier
57+
// defined in the RegionalRoleClientSupplier class in this directory.
58+
createAwsKmsMrkMultiKeyringInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{
59+
// Note: RegionalRoleClientSupplier will internally use the keyArn's region
60+
// to retrieve the correct IAM role.
61+
ClientSupplier: &RegionalRoleClientSupplier{},
62+
Generator: &keyArn,
63+
}
64+
mrkKeyringWithClientSupplier, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), createAwsKmsMrkMultiKeyringInput)
65+
utils.HandleError(err)
66+
67+
// 2. Configure which attributes are encrypted and/or signed when writing new items.
68+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
69+
// we must explicitly configure how they should be treated during item encryption:
70+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
71+
// - SIGN_ONLY: The attribute is not encrypted, but is still included in the signature
72+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
73+
attributeActionsOnEncrypt := map[string]dbesdkstructuredencryptiontypes.CryptoAction{
74+
"partition_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our partition attribute must be SIGN_ONLY
75+
"sort_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our sort attribute must be SIGN_ONLY
76+
"sensitive_data": dbesdkstructuredencryptiontypes.CryptoActionEncryptAndSign,
77+
}
78+
79+
// 3. Configure which attributes we expect to be included in the signature
80+
// when reading items. There are two options for configuring this:
81+
//
82+
// - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
83+
// When defining your DynamoDb schema and deciding on attribute names,
84+
// choose a distinguishing prefix (such as ":") for all attributes that
85+
// you do not want to include in the signature.
86+
// This has two main benefits:
87+
// - It is easier to reason about the security and authenticity of data within your item
88+
// when all unauthenticated data is easily distinguishable by their attribute name.
89+
// - If you need to add new unauthenticated attributes in the future,
90+
// you can easily make the corresponding update to your `attributeActionsOnEncrypt`
91+
// and immediately start writing to that new attribute, without
92+
// any other configuration update needed.
93+
// Once you configure this field, it is not safe to update it.
94+
//
95+
// - Configure `allowedUnsignedAttributes`: You may also explicitly list
96+
// a set of attributes that should be considered unauthenticated when encountered
97+
// on read. Be careful if you use this configuration. Do not remove an attribute
98+
// name from this configuration, even if you are no longer writing with that attribute,
99+
// as old items may still include this attribute, and our configuration needs to know
100+
// to continue to exclude this attribute from the signature scope.
101+
// If you add new attribute names to this field, you must first deploy the update to this
102+
// field to all readers in your host fleet before deploying the update to start writing
103+
// with that new attribute.
104+
//
105+
// For this example, we currently authenticate all attributes. To make it easier to
106+
// add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
107+
unsignAttrPrefix := ":"
108+
partitionKey := "partition_key"
109+
sortKey := "sort_key"
110+
// 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
111+
tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
112+
LogicalTableName: ddbTableName,
113+
PartitionKeyName: partitionKey,
114+
SortKeyName: &sortKey,
115+
AttributeActionsOnEncrypt: attributeActionsOnEncrypt,
116+
Keyring: mrkKeyringWithClientSupplier,
117+
AllowedUnsignedAttributePrefix: &unsignAttrPrefix,
118+
}
119+
120+
tableConfigs := map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
121+
ddbTableName: tableConfig,
122+
}
123+
124+
// 5. Create the DynamoDb Encryption Interceptor
125+
encryptionConfig := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
126+
TableEncryptionConfigs: tableConfigs,
127+
}
128+
129+
// 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
130+
cfg, err := config.LoadDefaultConfig(context.TODO())
131+
utils.HandleError(err)
132+
133+
dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(encryptionConfig)
134+
utils.HandleError(err)
135+
ddbClient := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware())
136+
137+
// 7. Put an item into our table using the above client.
138+
// Before the item gets sent to DynamoDb, it will be encrypted
139+
// client-side using the MRK multi-keyring.
140+
// The data key protecting this item will be encrypted
141+
// with all the KMS Keys in this keyring, so that it can be
142+
// decrypted with any one of those KMS Keys.
143+
item := map[string]types.AttributeValue{
144+
"partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"},
145+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
146+
"sensitive_data": &types.AttributeValueMemberS{Value: "encrypt and sign me!"},
147+
}
148+
149+
putRequest := &dynamodb.PutItemInput{
150+
TableName: &ddbTableName,
151+
Item: item,
152+
}
153+
154+
_, err = ddbClient.PutItem(context.Background(), putRequest)
155+
utils.HandleError(err)
156+
157+
// 8. Get the item back from our table using the same keyring.
158+
// The client will decrypt the item client-side using the MRK
159+
// and return the original item.
160+
keyToGet := map[string]types.AttributeValue{
161+
"partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"},
162+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
163+
}
164+
165+
getRequest := &dynamodb.GetItemInput{
166+
Key: keyToGet,
167+
TableName: aws.String(ddbTableName),
168+
}
169+
170+
getResponse, err := ddbClient.GetItem(context.Background(), getRequest)
171+
utils.HandleError(err)
172+
173+
// Verify the decrypted item
174+
if !reflect.DeepEqual(item, getResponse.Item) {
175+
panic("Decrypted item does not match original item")
176+
}
177+
178+
// 9. Create a MRK discovery multi-keyring with a custom client supplier.
179+
// A discovery MRK multi-keyring will be composed of
180+
// multiple discovery MRK keyrings, one for each region.
181+
// Each component keyring has its own KMS client in a particular region.
182+
// When we provide a client supplier to the multi-keyring, all component
183+
// keyrings will use that client supplier configuration.
184+
// In our tests, we make `keyArn` an MRK with a replica, and
185+
// provide only the replica region in our discovery filter.
186+
discoveryFilter := mpltypes.DiscoveryFilter{
187+
Partition: "aws",
188+
AccountIds: accountIds,
189+
}
190+
191+
mrkDiscoveryClientSupplierInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{
192+
ClientSupplier: &RegionalRoleClientSupplier{},
193+
DiscoveryFilter: &discoveryFilter,
194+
Regions: regions,
195+
}
196+
mrkDiscoveryClientSupplierKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), mrkDiscoveryClientSupplierInput)
197+
utils.HandleError(err)
198+
199+
// 10. Create a new config and client using the discovery keyring.
200+
// This is the same setup as above, except we provide the discovery keyring to the config.
201+
onlyReplicaKeyTableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
202+
LogicalTableName: ddbTableName,
203+
PartitionKeyName: partitionKey,
204+
SortKeyName: &sortKey,
205+
AttributeActionsOnEncrypt: attributeActionsOnEncrypt,
206+
// Provide discovery keyring here
207+
Keyring: mrkDiscoveryClientSupplierKeyring,
208+
AllowedUnsignedAttributePrefix: &unsignAttrPrefix,
209+
}
210+
211+
onlyReplicaKeyTableConfigs := map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{
212+
ddbTableName: onlyReplicaKeyTableConfig,
213+
}
214+
215+
onlyReplicaKeyEncryptionConfig := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{
216+
TableEncryptionConfigs: onlyReplicaKeyTableConfigs,
217+
}
218+
219+
onlyReplicaKeyDbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(onlyReplicaKeyEncryptionConfig)
220+
utils.HandleError(err)
221+
onlyReplicaKeyDdbClient := dynamodb.NewFromConfig(cfg, onlyReplicaKeyDbEsdkMiddleware.CreateMiddleware())
222+
223+
// 11. Get the item back from our table using the discovery keyring client.
224+
// The client will decrypt the item client-side using the keyring,
225+
// and return the original item.
226+
// The discovery keyring will only use KMS keys in the provided regions and
227+
// AWS accounts. Since we have provided it with a custom client supplier
228+
// which uses different IAM roles based on the key region,
229+
// the discovery keyring will use a particular IAM role to decrypt
230+
// based on the region of the KMS key it uses to decrypt.
231+
onlyReplicaKeyKeyToGet := map[string]types.AttributeValue{
232+
"partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"},
233+
"sort_key": &types.AttributeValueMemberN{Value: "0"},
234+
}
235+
236+
onlyReplicaKeyGetRequest := &dynamodb.GetItemInput{
237+
Key: onlyReplicaKeyKeyToGet,
238+
TableName: &ddbTableName,
239+
}
240+
241+
onlyReplicaKeyGetResponse, err := onlyReplicaKeyDdbClient.GetItem(context.Background(), onlyReplicaKeyGetRequest)
242+
utils.HandleError(err)
243+
244+
// Verify the decrypted item
245+
if !reflect.DeepEqual(item, onlyReplicaKeyGetResponse.Item) {
246+
panic("Decrypted item does not match original item")
247+
}
248+
249+
fmt.Println("Client Supplier Example completed successfully")
250+
}
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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
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"
@@ -13,7 +14,16 @@ import (
1314
)
1415

1516
func main() {
17+
// clientsupplier example
18+
clientsupplier.ClientSupplierExample(
19+
utils.DdbTableName(),
20+
utils.TestMrkReplicaKeyIdUsEast1(),
21+
utils.DefaultKMSKeyAccountID(),
22+
utils.AlternateRegionKmsKeyRegionAsAList())
1623
// misc examples
24+
misc.BasicPutGetExample(
25+
utils.KmsKeyID(),
26+
utils.DdbTableName())
1727
misc.GetEncryptedDataKeyDescriptionExample(
1828
utils.KmsKeyID(),
1929
utils.DdbTableName())

0 commit comments

Comments
 (0)