From d0c7828f156915a75c743b3917e16da12fd636f4 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 27 Jun 2025 11:16:39 -0700 Subject: [PATCH 1/8] auto commit --- .../clientsupplier/clientsupplierexample.go | 254 ++++++++++++++++++ .../regionalRoleClientSupplierConfig.go | 27 ++ .../regionalroleclientsupplier.go | 53 ++++ Examples/runtimes/go/main.go | 2 + Examples/runtimes/go/utils/exampleUtils.go | 26 +- 5 files changed, 354 insertions(+), 8 deletions(-) create mode 100644 Examples/runtimes/go/clientsupplier/clientsupplierexample.go create mode 100644 Examples/runtimes/go/clientsupplier/regionalRoleClientSupplierConfig.go create mode 100644 Examples/runtimes/go/clientsupplier/regionalroleclientsupplier.go diff --git a/Examples/runtimes/go/clientsupplier/clientsupplierexample.go b/Examples/runtimes/go/clientsupplier/clientsupplierexample.go new file mode 100644 index 0000000000..e27561dbe5 --- /dev/null +++ b/Examples/runtimes/go/clientsupplier/clientsupplierexample.go @@ -0,0 +1,254 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package clientsupplier + +import ( + "context" + "fmt" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes" + dbesdkstructuredencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkstructuredencryptionsmithygeneratedtypes" + "github.com/aws/aws-database-encryption-sdk-dynamodb/dbesdkmiddleware" + "github.com/aws/aws-database-encryption-sdk-dynamodb/examples/utils" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" +) + +/* +This example sets up an MRK multi-keyring and an MRK discovery +multi-keyring using a custom client supplier. +A custom client supplier grants users access to more granular +configuration aspects of their authentication details and KMS +client. In this example, we create a simple custom client supplier +that authenticates with a different IAM role based on the +region of the KMS key. + +This example creates a MRK multi-keyring configured with a custom +client supplier using a single MRK and puts an encrypted item to the +table. Then, it creates a MRK discovery multi-keyring to decrypt the item +and retrieves the item from the table. + +Running this example requires access to the DDB Table whose name +is provided in CLI arguments. +This table must be configured with the following +primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ +func ClientSupplierExample(ddbTableName, keyArn string, accountIds, regions []string) { + // 1. Create a single MRK multi-keyring. + // This can be either a single-region KMS key or an MRK. + // For this example to succeed, the key's region must either + // 1) be in the regions list, or + // 2) the key must be an MRK with a replica defined + // in a region in the regions list, and the client + // must have the correct permissions to access the replica. + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + utils.HandleError(err) + + // Create the multi-keyring using our custom client supplier + // defined in the RegionalRoleClientSupplier class in this directory. + createAwsKmsMrkMultiKeyringInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{ + // Note: RegionalRoleClientSupplier will internally use the keyArn's region + // to retrieve the correct IAM role. + ClientSupplier: &RegionalRoleClientSupplier{}, + Generator: &keyArn, + } + mrkKeyringWithClientSupplier, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), createAwsKmsMrkMultiKeyringInput) + utils.HandleError(err) + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute is not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + attributeActionsOnEncrypt := map[string]dbesdkstructuredencryptiontypes.CryptoAction{ + "partition_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our partition attribute must be SIGN_ONLY + "sort_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Our sort attribute must be SIGN_ONLY + "sensitive_data": dbesdkstructuredencryptiontypes.CryptoActionEncryptAndSign, + } + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + unsignAttrPrefix := ":" + partitionKey := "partition_key" + sortKey := "sort_key" + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{ + LogicalTableName: ddbTableName, + PartitionKeyName: partitionKey, + SortKeyName: &sortKey, + AttributeActionsOnEncrypt: attributeActionsOnEncrypt, + Keyring: mrkKeyringWithClientSupplier, + AllowedUnsignedAttributePrefix: &unsignAttrPrefix, + } + + tableConfigs := map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{ + ddbTableName: tableConfig, + } + + // 5. Create the DynamoDb Encryption Interceptor + encryptionConfig := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{ + TableEncryptionConfigs: tableConfigs, + } + + // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + cfg, err := config.LoadDefaultConfig(context.TODO()) + utils.HandleError(err) + + dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(encryptionConfig) + utils.HandleError(err) + ddbClient := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware()) + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side using the MRK multi-keyring. + // The data key protecting this item will be encrypted + // with all the KMS Keys in this keyring, so that it can be + // decrypted with any one of those KMS Keys. + item := map[string]types.AttributeValue{ + "partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"}, + "sort_key": &types.AttributeValueMemberN{Value: "0"}, + "sensitive_data": &types.AttributeValueMemberS{Value: "encrypt and sign me!"}, + } + + putRequest := &dynamodb.PutItemInput{ + TableName: &ddbTableName, + Item: item, + } + + _, err = ddbClient.PutItem(context.Background(), putRequest) + utils.HandleError(err) + + // 8. Get the item back from our table using the same keyring. + // The client will decrypt the item client-side using the MRK + // and return the original item. + keyToGet := map[string]types.AttributeValue{ + "partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"}, + "sort_key": &types.AttributeValueMemberN{Value: "0"}, + } + + getRequest := &dynamodb.GetItemInput{ + Key: keyToGet, + TableName: aws.String(ddbTableName), + } + + getResponse, err := ddbClient.GetItem(context.Background(), getRequest) + utils.HandleError(err) + + // Demonstrate that GetItem succeeded and returned the decrypted item + returnedItem := getResponse.Item + sensitiveData := returnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value + if sensitiveData != "encrypt and sign me!" { + panic("Decrypted data does not match expected value") + } + + // 9. Create a MRK discovery multi-keyring with a custom client supplier. + // A discovery MRK multi-keyring will be composed of + // multiple discovery MRK keyrings, one for each region. + // Each component keyring has its own KMS client in a particular region. + // When we provide a client supplier to the multi-keyring, all component + // keyrings will use that client supplier configuration. + // In our tests, we make `keyArn` an MRK with a replica, and + // provide only the replica region in our discovery filter. + discoveryFilter := mpltypes.DiscoveryFilter{ + Partition: "aws", + AccountIds: accountIds, + } + + mrkDiscoveryClientSupplierInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{ + ClientSupplier: &RegionalRoleClientSupplier{}, + DiscoveryFilter: &discoveryFilter, + Regions: regions, + } + mrkDiscoveryClientSupplierKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), mrkDiscoveryClientSupplierInput) + utils.HandleError(err) + + // 10. Create a new config and client using the discovery keyring. + // This is the same setup as above, except we provide the discovery keyring to the config. + onlyReplicaKeyTableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{ + LogicalTableName: ddbTableName, + PartitionKeyName: partitionKey, + SortKeyName: &sortKey, + AttributeActionsOnEncrypt: attributeActionsOnEncrypt, + // Provide discovery keyring here + Keyring: mrkDiscoveryClientSupplierKeyring, + AllowedUnsignedAttributePrefix: &unsignAttrPrefix, + } + + onlyReplicaKeyTableConfigs := map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{ + ddbTableName: onlyReplicaKeyTableConfig, + } + + onlyReplicaKeyEncryptionConfig := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{ + TableEncryptionConfigs: onlyReplicaKeyTableConfigs, + } + + onlyReplicaKeyDbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(onlyReplicaKeyEncryptionConfig) + utils.HandleError(err) + onlyReplicaKeyDdbClient := dynamodb.NewFromConfig(cfg, onlyReplicaKeyDbEsdkMiddleware.CreateMiddleware()) + + // 11. Get the item back from our table using the discovery keyring client. + // The client will decrypt the item client-side using the keyring, + // and return the original item. + // The discovery keyring will only use KMS keys in the provided regions and + // AWS accounts. Since we have provided it with a custom client supplier + // which uses different IAM roles based on the key region, + // the discovery keyring will use a particular IAM role to decrypt + // based on the region of the KMS key it uses to decrypt. + onlyReplicaKeyKeyToGet := map[string]types.AttributeValue{ + "partition_key": &types.AttributeValueMemberS{Value: "clientSupplierItem"}, + "sort_key": &types.AttributeValueMemberN{Value: "0"}, + } + + onlyReplicaKeyGetRequest := &dynamodb.GetItemInput{ + Key: onlyReplicaKeyKeyToGet, + TableName: &ddbTableName, + } + + onlyReplicaKeyGetResponse, err := onlyReplicaKeyDdbClient.GetItem(context.Background(), onlyReplicaKeyGetRequest) + utils.HandleError(err) + + // Demonstrate that GetItem succeeded and returned the decrypted item + fmt.Println("GetItem with discovery keyring completed successfully") + onlyReplicaKeyReturnedItem := onlyReplicaKeyGetResponse.Item + onlyReplicaKeySensitiveData := onlyReplicaKeyReturnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value + if onlyReplicaKeySensitiveData != "encrypt and sign me!" { + panic("Decrypted data from discovery keyring does not match expected value") + } + + fmt.Println("Client Supplier Example completed successfully") +} diff --git a/Examples/runtimes/go/clientsupplier/regionalRoleClientSupplierConfig.go b/Examples/runtimes/go/clientsupplier/regionalRoleClientSupplierConfig.go new file mode 100644 index 0000000000..42bd52c38c --- /dev/null +++ b/Examples/runtimes/go/clientsupplier/regionalRoleClientSupplierConfig.go @@ -0,0 +1,27 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package clientsupplier + +/* +Class containing config for the RegionalRoleClientSupplier. +In your own code, this might be hardcoded, or reference +an external source, e.g. environment variables or AWS AppConfig. +*/ +type RegionalRoleClientSupplierConfig struct { + RegionIamRoleMap map[string]string +} + +const ( + usEast1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-only-us-east-1-KMS-keys" + euWest1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-only-eu-west-1-KMS-keys" +) + +func NewRegionalRoleClientSupplierConfig() *RegionalRoleClientSupplierConfig { + return &RegionalRoleClientSupplierConfig{ + RegionIamRoleMap: map[string]string{ + "us-east-1": usEast1IamRole, + "eu-west-1": euWest1IamRole, + }, + } +} diff --git a/Examples/runtimes/go/clientsupplier/regionalroleclientsupplier.go b/Examples/runtimes/go/clientsupplier/regionalroleclientsupplier.go new file mode 100644 index 0000000000..ce69c02c77 --- /dev/null +++ b/Examples/runtimes/go/clientsupplier/regionalroleclientsupplier.go @@ -0,0 +1,53 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package clientsupplier + +import ( + "context" + + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/sts" +) + +/* +Example class demonstrating an implementation of a custom client supplier. +This particular implementation will create KMS clients with different IAM roles, +depending on the region passed. +*/ +type RegionalRoleClientSupplier struct{} + +func (r *RegionalRoleClientSupplier) GetClient(input mpltypes.GetClientInput) (kms.Client, error) { + supplierConfig := NewRegionalRoleClientSupplierConfig() + + roleArn, exists := supplierConfig.RegionIamRoleMap[input.Region] + if !exists { + return kms.Client{}, mpltypes.AwsCryptographicMaterialProvidersException{ + Message: "Missing region: " + input.Region, + } + } + + // Load default AWS config + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + return kms.Client{}, err + } + + // Create STS client for assuming role + stsClient := sts.NewFromConfig(cfg) + + // Create credentials provider that assumes the role + roleProvider := stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(o *stscreds.AssumeRoleOptions) { + o.RoleSessionName = "Go-Client-Supplier-Example-Session" + }) + + // Create KMS client with the assumed role credentials + sdkConfig, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(input.Region), config.WithCredentialsProvider(roleProvider)) + kmsClient := kms.NewFromConfig(sdkConfig) + + return *kmsClient, nil +} diff --git a/Examples/runtimes/go/main.go b/Examples/runtimes/go/main.go index b9e6b4fc94..9e93452090 100644 --- a/Examples/runtimes/go/main.go +++ b/Examples/runtimes/go/main.go @@ -4,6 +4,7 @@ package main import ( + "github.com/aws/aws-database-encryption-sdk-dynamodb/examples/clientsupplier" "github.com/aws/aws-database-encryption-sdk-dynamodb/examples/itemencryptor" "github.com/aws/aws-database-encryption-sdk-dynamodb/examples/keyring" "github.com/aws/aws-database-encryption-sdk-dynamodb/examples/misc" @@ -11,6 +12,7 @@ import ( ) func main() { + clientsupplier.ClientSupplierExample(utils.DdbTableName(), utils.TestMrkReplicaKeyIdUsEast1(), utils.DefaultKMSKeyAccountID(), []string{"eu-west-1"}) keyring.AwsKmsKeyringExample(utils.KmsKeyID(), utils.DdbTableName()) keyring.RawAesExample(utils.DdbTableName(), utils.KeyNamespace(), utils.KeyName(), utils.GenerateAes256KeyBytes()) itemencryptor.ItemEncryptDecryptExample(utils.KmsKeyID(), utils.DdbTableName()) diff --git a/Examples/runtimes/go/utils/exampleUtils.go b/Examples/runtimes/go/utils/exampleUtils.go index dbef8b63f5..cacf8345e7 100644 --- a/Examples/runtimes/go/utils/exampleUtils.go +++ b/Examples/runtimes/go/utils/exampleUtils.go @@ -6,16 +6,26 @@ package utils import "crypto/rand" const ( - kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" - ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS" - keyNamespace = "my-key-namespace" - keyName = "my-key-name" - aesKeyBytes = 32 // 256 bits = 32 bytes - testKeystoreName = "KeyStoreDdbTable" - testLogicalKeystoreName = "KeyStoreDdbTable" - testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS" + keyNamespace = "my-key-namespace" + keyName = "my-key-name" + aesKeyBytes = 32 // 256 bits = 32 bytes + testKeystoreName = "KeyStoreDdbTable" + testLogicalKeystoreName = "KeyStoreDdbTable" + testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + defaultKMSKeyAccountID = "658956600833" + testMrkReplicaKeyIdUsEast1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" ) +func TestMrkReplicaKeyIdUsEast1() string { + return testMrkReplicaKeyIdUsEast1 +} + +func DefaultKMSKeyAccountID() []string { + return []string{defaultKMSKeyAccountID} +} + func TestKeystoreName() string { return testKeystoreName } From 1b126b1291795c6c90fde3b5af75c186f7f27524 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 27 Jun 2025 13:17:52 -0700 Subject: [PATCH 2/8] auto commit --- Examples/runtimes/go/utils/exampleUtils.go | 51 ++++++++++++++++++---- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/Examples/runtimes/go/utils/exampleUtils.go b/Examples/runtimes/go/utils/exampleUtils.go index a3adae6bb6..93c6982e77 100644 --- a/Examples/runtimes/go/utils/exampleUtils.go +++ b/Examples/runtimes/go/utils/exampleUtils.go @@ -9,16 +9,51 @@ import ( ) const ( - kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" - ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS" - keyNamespace = "my-key-namespace" - keyName = "my-key-name" - aesKeyBytes = 32 // 256 bits = 32 bytes - testKeystoreName = "KeyStoreDdbTable" - testLogicalKeystoreName = "KeyStoreDdbTable" - testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS" + keyNamespace = "my-key-namespace" + keyName = "my-key-name" + aesKeyBytes = 32 // 256 bits = 32 bytes + testKeystoreName = "KeyStoreDdbTable" + testLogicalKeystoreName = "KeyStoreDdbTable" + testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + defaultRsaPublicKeyFilename = "KmsRsaKeyringPublicKey.pem" + testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6" + defaultKMSKeyAccountID = "658956600833" + defaultKmsKeyRegion = "us-west-2" + exampleRsaPrivateKeyFilename = "RawRsaKeyringExamplePrivateKey.pem" + exampleRsaPublicKeyFilename = "RawRsaKeyringExamplePublicKey.pem" + testMrkReplicaKeyIdUsEast1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" ) +func TestMrkReplicaKeyIdUsEast1() string { + return testMrkReplicaKeyIdUsEast1 +} + +func ExampleRsaPublicKeyFilename() string { + return exampleRsaPublicKeyFilename +} + +func ExampleRsaPrivateKeyFilename() string { + return exampleRsaPrivateKeyFilename +} + +func DefaultKMSKeyAccountID() []string { + return []string{defaultKMSKeyAccountID} +} + +func DefaultKmsKeyRegion() []string { + return []string{defaultKmsKeyRegion} +} + +func TestKmsRsaKeyID() string { + return testKmsRsaKeyID +} + +func DefaultRsaPublicKeyFilename() string { + return defaultRsaPublicKeyFilename +} + func TestKeystoreName() string { return testKeystoreName } From 7ebe5e99ef8fd1b39c3c95b0dc71dd1bb2fa6704 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 27 Jun 2025 13:17:55 -0700 Subject: [PATCH 3/8] auto commit --- Examples/runtimes/go/clientsupplier/clientsupplierexample.go | 1 - 1 file changed, 1 deletion(-) diff --git a/Examples/runtimes/go/clientsupplier/clientsupplierexample.go b/Examples/runtimes/go/clientsupplier/clientsupplierexample.go index e27561dbe5..5f7a1c74ce 100644 --- a/Examples/runtimes/go/clientsupplier/clientsupplierexample.go +++ b/Examples/runtimes/go/clientsupplier/clientsupplierexample.go @@ -243,7 +243,6 @@ func ClientSupplierExample(ddbTableName, keyArn string, accountIds, regions []st utils.HandleError(err) // Demonstrate that GetItem succeeded and returned the decrypted item - fmt.Println("GetItem with discovery keyring completed successfully") onlyReplicaKeyReturnedItem := onlyReplicaKeyGetResponse.Item onlyReplicaKeySensitiveData := onlyReplicaKeyReturnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value if onlyReplicaKeySensitiveData != "encrypt and sign me!" { From 5f1e35f281176d05b46a8746fe566fb226798f87 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 27 Jun 2025 13:23:18 -0700 Subject: [PATCH 4/8] auto commit --- Examples/runtimes/go/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Examples/runtimes/go/main.go b/Examples/runtimes/go/main.go index 282b414dad..17f9aacd52 100644 --- a/Examples/runtimes/go/main.go +++ b/Examples/runtimes/go/main.go @@ -13,7 +13,11 @@ import ( func main() { // clientsupplier example - clientsupplier.ClientSupplierExample(utils.DdbTableName(), utils.TestMrkReplicaKeyIdUsEast1(), utils.DefaultKMSKeyAccountID(), []string{"eu-west-1"}) + clientsupplier.ClientSupplierExample( + utils.DdbTableName(), + utils.TestMrkReplicaKeyIdUsEast1(), + utils.DefaultKMSKeyAccountID(), + utils.AlternateRegionKmsKeyRegionAsAList()) // misc examples misc.GetEncryptedDataKeyDescriptionExample( utils.KmsKeyID(), From d2d6119377c22730687791381c395d06a436626d Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 27 Jun 2025 13:23:26 -0700 Subject: [PATCH 5/8] auto commit --- Examples/runtimes/go/utils/exampleUtils.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Examples/runtimes/go/utils/exampleUtils.go b/Examples/runtimes/go/utils/exampleUtils.go index 93c6982e77..0612d9e3fd 100644 --- a/Examples/runtimes/go/utils/exampleUtils.go +++ b/Examples/runtimes/go/utils/exampleUtils.go @@ -21,15 +21,19 @@ const ( testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6" defaultKMSKeyAccountID = "658956600833" defaultKmsKeyRegion = "us-west-2" + alternateRegionKmsKeyRegion = "eu-west-1" exampleRsaPrivateKeyFilename = "RawRsaKeyringExamplePrivateKey.pem" exampleRsaPublicKeyFilename = "RawRsaKeyringExamplePublicKey.pem" testMrkReplicaKeyIdUsEast1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" ) +func AlternateRegionKmsKeyRegionAsAList() []string { + return []string{alternateRegionKmsKeyRegion} +} + func TestMrkReplicaKeyIdUsEast1() string { return testMrkReplicaKeyIdUsEast1 } - func ExampleRsaPublicKeyFilename() string { return exampleRsaPublicKeyFilename } From 5107ffc1c2e5b6bd592e1a65cf6663ffbeef83d6 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 27 Jun 2025 14:03:36 -0700 Subject: [PATCH 6/8] auto commit --- .../go/clientsupplier/clientsupplierexample.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Examples/runtimes/go/clientsupplier/clientsupplierexample.go b/Examples/runtimes/go/clientsupplier/clientsupplierexample.go index 5f7a1c74ce..a01ca580ff 100644 --- a/Examples/runtimes/go/clientsupplier/clientsupplierexample.go +++ b/Examples/runtimes/go/clientsupplier/clientsupplierexample.go @@ -6,6 +6,7 @@ package clientsupplier import ( "context" "fmt" + "reflect" mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated" mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes" @@ -169,11 +170,9 @@ func ClientSupplierExample(ddbTableName, keyArn string, accountIds, regions []st getResponse, err := ddbClient.GetItem(context.Background(), getRequest) utils.HandleError(err) - // Demonstrate that GetItem succeeded and returned the decrypted item - returnedItem := getResponse.Item - sensitiveData := returnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value - if sensitiveData != "encrypt and sign me!" { - panic("Decrypted data does not match expected value") + // Verify the decrypted item + if !reflect.DeepEqual(item, getResponse.Item) { + panic("Decrypted item does not match original item") } // 9. Create a MRK discovery multi-keyring with a custom client supplier. @@ -242,11 +241,9 @@ func ClientSupplierExample(ddbTableName, keyArn string, accountIds, regions []st onlyReplicaKeyGetResponse, err := onlyReplicaKeyDdbClient.GetItem(context.Background(), onlyReplicaKeyGetRequest) utils.HandleError(err) - // Demonstrate that GetItem succeeded and returned the decrypted item - onlyReplicaKeyReturnedItem := onlyReplicaKeyGetResponse.Item - onlyReplicaKeySensitiveData := onlyReplicaKeyReturnedItem["sensitive_data"].(*types.AttributeValueMemberS).Value - if onlyReplicaKeySensitiveData != "encrypt and sign me!" { - panic("Decrypted data from discovery keyring does not match expected value") + // Verify the decrypted item + if !reflect.DeepEqual(item, onlyReplicaKeyGetResponse.Item) { + panic("Decrypted item does not match original item") } fmt.Println("Client Supplier Example completed successfully") From 95971427430d12e8d89289c222dc302b53e7e063 Mon Sep 17 00:00:00 2001 From: Rishav karanjit Date: Wed, 2 Jul 2025 10:32:09 -0700 Subject: [PATCH 7/8] chore(go): add basic put get example (#1954) --- Examples/runtimes/go/main.go | 3 + .../runtimes/go/misc/basicputgetexample.go | 145 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 Examples/runtimes/go/misc/basicputgetexample.go diff --git a/Examples/runtimes/go/main.go b/Examples/runtimes/go/main.go index 41cb4b75a1..ed6f52ff21 100644 --- a/Examples/runtimes/go/main.go +++ b/Examples/runtimes/go/main.go @@ -20,6 +20,9 @@ func main() { utils.DefaultKMSKeyAccountID(), utils.AlternateRegionKmsKeyRegionAsAList()) // misc examples + misc.BasicPutGetExample( + utils.KmsKeyID(), + utils.DdbTableName()) misc.GetEncryptedDataKeyDescriptionExample( utils.KmsKeyID(), utils.DdbTableName()) diff --git a/Examples/runtimes/go/misc/basicputgetexample.go b/Examples/runtimes/go/misc/basicputgetexample.go new file mode 100644 index 0000000000..25c4619488 --- /dev/null +++ b/Examples/runtimes/go/misc/basicputgetexample.go @@ -0,0 +1,145 @@ +package misc + +import ( + "context" + "fmt" + "reflect" + + mpl "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygenerated" + mpltypes "github.com/aws/aws-cryptographic-material-providers-library/releases/go/mpl/awscryptographymaterialproviderssmithygeneratedtypes" + dbesdkdynamodbencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkdynamodbsmithygeneratedtypes" + dbesdkstructuredencryptiontypes "github.com/aws/aws-database-encryption-sdk-dynamodb/awscryptographydbencryptionsdkstructuredencryptionsmithygeneratedtypes" + "github.com/aws/aws-database-encryption-sdk-dynamodb/dbesdkmiddleware" + "github.com/aws/aws-database-encryption-sdk-dynamodb/examples/utils" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" +) + +func BasicPutGetExample(kmsKeyID, ddbTableName string) { + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + + matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{}) + utils.HandleError(err) + awsKmsMrkKeyringMultiInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{ + Generator: &kmsKeyID, + } + keyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), awsKmsMrkKeyringMultiInput) + if err != nil { + panic(err) + } + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + attributeActions := map[string]dbesdkstructuredencryptiontypes.CryptoAction{ + "partition_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Partition key must be SIGN_ONLY + "sort_key": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, // Sort key must be SIGN_ONLY + "attribute1": dbesdkstructuredencryptiontypes.CryptoActionEncryptAndSign, + "attribute2": dbesdkstructuredencryptiontypes.CryptoActionSignOnly, + ":attribute3": dbesdkstructuredencryptiontypes.CryptoActionDoNothing, + } + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we have designed our DynamoDb table such that any attribute name with + // the ":" prefix should be considered unauthenticated. + allowedUnsignedAttributePrefix := ":" + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + partitionKey := "partition_key" + sortKeyName := "sort_key" + algorithmSuiteID := mpltypes.DBEAlgorithmSuiteIdAlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384 + tableConfig := dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig{ + LogicalTableName: ddbTableName, + PartitionKeyName: partitionKey, + SortKeyName: &sortKeyName, + AttributeActionsOnEncrypt: attributeActions, + Keyring: keyring, + AllowedUnsignedAttributePrefix: &allowedUnsignedAttributePrefix, + AlgorithmSuiteId: &algorithmSuiteID, + } + tableConfigsMap := make(map[string]dbesdkdynamodbencryptiontypes.DynamoDbTableEncryptionConfig) + tableConfigsMap[ddbTableName] = tableConfig + listOfTableConfigs := dbesdkdynamodbencryptiontypes.DynamoDbTablesEncryptionConfig{ + TableEncryptionConfigs: tableConfigsMap, + } + // 5. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor + dbEsdkMiddleware, err := dbesdkmiddleware.NewDBEsdkMiddleware(listOfTableConfigs) + utils.HandleError(err) + cfg, err := config.LoadDefaultConfig(context.TODO()) + utils.HandleError(err) + ddb := dynamodb.NewFromConfig(cfg, dbEsdkMiddleware.CreateMiddleware()) + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + item := map[string]types.AttributeValue{ + "partition_key": &types.AttributeValueMemberS{Value: "BasicPutGetExample"}, + "sort_key": &types.AttributeValueMemberN{Value: "0"}, + "attribute1": &types.AttributeValueMemberS{Value: "encrypt and sign me!"}, + "attribute2": &types.AttributeValueMemberS{Value: "sign me!"}, + ":attribute3": &types.AttributeValueMemberS{Value: "ignore me!"}, + } + putInput := &dynamodb.PutItemInput{ + TableName: aws.String(ddbTableName), + Item: item, + } + _, err = ddb.PutItem(context.TODO(), putInput) + utils.HandleError(err) + + // 7. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + key := map[string]types.AttributeValue{ + "partition_key": &types.AttributeValueMemberS{Value: "BasicPutGetExample"}, + "sort_key": &types.AttributeValueMemberN{Value: "0"}, + } + getInput := &dynamodb.GetItemInput{ + TableName: aws.String(ddbTableName), + Key: key, + // In this example we configure a strongly consistent read + // because we perform a read immediately after a write (for demonstrative purposes). + // By default, reads are only eventually consistent. + // Read our docs to determine which read consistency to use for your application: + // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html + ConsistentRead: aws.Bool(true), + } + result, err := ddb.GetItem(context.TODO(), getInput) + utils.HandleError(err) + // Verify the decrypted item + if !reflect.DeepEqual(item, result.Item) { + panic("Decrypted item does not match original item") + } + fmt.Println("BasicPutGetExample successful.") +} From 2090bd72aefab98bc094226ad6b44bc6f1a0f622 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 2 Jul 2025 10:36:10 -0700 Subject: [PATCH 8/8] auto commit --- Examples/runtimes/go/utils/exampleUtils.go | 48 ++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/Examples/runtimes/go/utils/exampleUtils.go b/Examples/runtimes/go/utils/exampleUtils.go index 0612d9e3fd..34b5aa47a4 100644 --- a/Examples/runtimes/go/utils/exampleUtils.go +++ b/Examples/runtimes/go/utils/exampleUtils.go @@ -9,22 +9,25 @@ import ( ) const ( - kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" - ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS" - keyNamespace = "my-key-namespace" - keyName = "my-key-name" - aesKeyBytes = 32 // 256 bits = 32 bytes - testKeystoreName = "KeyStoreDdbTable" - testLogicalKeystoreName = "KeyStoreDdbTable" - testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" - defaultRsaPublicKeyFilename = "KmsRsaKeyringPublicKey.pem" - testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6" - defaultKMSKeyAccountID = "658956600833" - defaultKmsKeyRegion = "us-west-2" - alternateRegionKmsKeyRegion = "eu-west-1" - exampleRsaPrivateKeyFilename = "RawRsaKeyringExamplePrivateKey.pem" - exampleRsaPublicKeyFilename = "RawRsaKeyringExamplePublicKey.pem" - testMrkReplicaKeyIdUsEast1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + kmsKeyID = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + ddbTableName = "DynamoDbEncryptionInterceptorTestTableCS" + keyNamespace = "my-key-namespace" + keyName = "my-key-name" + aesKeyBytes = 32 // 256 bits = 32 bytes + testKeystoreName = "KeyStoreDdbTable" + testLogicalKeystoreName = "KeyStoreDdbTable" + testKeystoreKmsKeyId = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + defaultRsaPublicKeyFilename = "KmsRsaKeyringPublicKey.pem" + testKmsRsaKeyID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6" + defaultKMSKeyAccountID = "658956600833" + defaultKmsKeyRegion = "us-west-2" + alternateRegionKmsKeyRegion = "eu-west-1" + exampleRsaPrivateKeyFilename = "RawRsaKeyringExamplePrivateKey.pem" + exampleRsaPublicKeyFilename = "RawRsaKeyringExamplePublicKey.pem" + unitInspectionTestDdbTableName = "UnitInspectionTestTableCS" + simpleBeaconTestDdbTableName = "SimpleBeaconTestTable" + testComplexDdbTableName = "ComplexBeaconTestTable" + testMrkReplicaKeyIdUsEast1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" ) func AlternateRegionKmsKeyRegionAsAList() []string { @@ -34,6 +37,19 @@ func AlternateRegionKmsKeyRegionAsAList() []string { func TestMrkReplicaKeyIdUsEast1() string { return testMrkReplicaKeyIdUsEast1 } + +func UnitInspectionTestDdbTableName() string { + return unitInspectionTestDdbTableName +} + +func SimpleBeaconTestDdbTableName() string { + return simpleBeaconTestDdbTableName +} + +func TestComplexDdbTableName() string { + return testComplexDdbTableName +} + func ExampleRsaPublicKeyFilename() string { return exampleRsaPublicKeyFilename }