Skip to content

Commit c3ba5e3

Browse files
chore(examples): Add examples for Go with CI (#733)
1 parent fa1ca01 commit c3ba5e3

38 files changed

+5435
-1
lines changed

.github/workflows/library_go_tests.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,11 @@ jobs:
9595
working-directory: ${{ matrix.library }}
9696
shell: bash
9797
run: |
98-
make test_go
98+
make test_go
99+
100+
- name: Test Examples for Go
101+
if: matrix.library == 'AwsEncryptionSDK'
102+
working-directory: ${{ matrix.library }}/runtimes/go/examples
103+
shell: bash
104+
run: |
105+
go run main.go

AwsEncryptionSDK/Makefile

+15
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,18 @@ TYPES_FILE_WITHOUT_EXTERN_STRING="module AwsCryptographyEncryptionSdkTypes"
9292
INDEX_FILE_PATH=dafny/AwsEncryptionSdk/src/Index.dfy
9393
INDEX_FILE_WITH_EXTERN_STRING="module {:extern \"software.amazon.cryptography.encryptionsdk.internaldafny\" } ESDK refines AbstractAwsCryptographyEncryptionSdkService {"
9494
INDEX_FILE_WITHOUT_EXTERN_STRING="module ESDK refines AbstractAwsCryptographyEncryptionSdkService {"
95+
96+
# Target to restore all directories in a list
97+
# TODO: remove this once we don't copy all of the directories into implementation and test. This is done by make file target _mv_polymorph_go in smithy-dafny.
98+
RESTORE_DIRS := examples
99+
_polymorph_go: restore_directories
100+
restore_directories:
101+
@for dir in $(RESTORE_DIRS); do \
102+
if [ -d "runtimes/go/ImplementationFromDafny-go/$$dir" ]; then \
103+
cp -Rf runtimes/go/ImplementationFromDafny-go/$$dir runtimes/go/; \
104+
rm -rf runtimes/go/ImplementationFromDafny-go/$$dir; \
105+
rm -rf runtimes/go/TestsFromDafny-go/$$dir; \
106+
else \
107+
echo "Directory $$dir not found"; \
108+
fi \
109+
done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# AWS Encryption SDK for Go Examples
2+
3+
This section features examples that show you
4+
how to use the AWS Encryption SDK.
5+
We demonstrate how to use the encryption and decryption APIs
6+
and how to set up some common configuration patterns.
7+
8+
## APIs
9+
10+
The AWS Encryption SDK provides two high-level APIs:
11+
one-step APIs that process the entire operation in memory
12+
and streaming APIs.
13+
14+
You can find examples that demonstrate these APIs
15+
in the [`examples/`](./) directory.
16+
17+
* [How to encrypt and decrypt](./keyring/awskmskeyring/awskmskeyring.go)
18+
* [How to change the algorithm suite](./misc/setencryptionalgorithmsuite.go)
19+
* [How to set the commitment policy](./misc/commitmentpolicy.go)
20+
* [How to limit the number of encrypted data keys (EDKs)](./misc/limitencrypteddatakeysexample.go)
21+
22+
## Configuration
23+
24+
To use the encryption and decryption APIs,
25+
you need to describe how you want the library to protect your data keys.
26+
You can do this by configuring
27+
[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers).
28+
These examples will show you how to use the configuration tools that we include for you
29+
and how to create some of your own.
30+
We start with AWS KMS examples, then show how to use other wrapping keys.
31+
32+
* Using AWS Key Management Service (AWS KMS)
33+
* [How to use one AWS KMS key](./keyring/awskmskeyring/awskmskeyring.go)
34+
* [How to use multiple AWS KMS keys in different regions](./keyring/awskmsmrkmultikeyring/awskmsmrkmultikeyring.go)
35+
* [How to decrypt when you don't know the AWS KMS key](./keyring/awskmsdiscoverykeyring/awskmsdiscoverykeyring.go)
36+
* [How to limit decryption to a single region](./keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go)
37+
* [How to decrypt with a preferred region but failover to others](./keyring/awskmsmrkdiscoverykeyring/awskmsmrkdiscoverykeyring.go)
38+
* [How to reproduce the behavior of an AWS KMS master key provider](./keyring/awskmsmultikeyring/awskmsmultikeyring.go)
39+
* Using raw wrapping keys
40+
* [How to use a raw AES wrapping key](./keyring/rawaeskeyring/rawaeskeyring.go)
41+
* [How to use a raw RSA wrapping key](./keyring/rawrsakeyring/rawrasakeyring.go)
42+
* Combining wrapping keys
43+
* [How to combine AWS KMS with an offline escrow key](./keyring/multikeyring/multikeyring.go)
44+
* How to restrict algorithm suites
45+
* [with a custom cryptographic materials manager](./cryptographicmaterialsmanager/restrictalgorithmsuite/signingsuiteonlycmm.go)
46+
47+
### Keyrings
48+
49+
Keyrings are the most common way for you to configure the AWS Encryption SDK.
50+
They determine how the AWS Encryption SDK protects your data.
51+
You can find these examples in [`examples/keyring`](./keyring).
52+
53+
### Cryptographic Materials Managers
54+
55+
Keyrings define how your data keys are protected,
56+
but there is more going on here than just protecting data keys.
57+
58+
Cryptographic materials managers give you higher-level controls
59+
over how the AWS Encryption SDK protects your data.
60+
This can include things like
61+
enforcing the use of certain algorithm suites or encryption context settings,
62+
reusing data keys across messages,
63+
or changing how you interact with keyrings.
64+
You can find these examples in
65+
[`examples/cryptographic_materials_manager`](./cryptographicmaterialsmanager).
66+
67+
### Client Supplier
68+
69+
The AWS Encryption SDK creates AWS KMS clients when interacting with AWS KMS.
70+
In case the default AWS KMS client configuration doesn't suit your needs,
71+
you can configure clients by defining a custom Client Supplier.
72+
For example, your Client Supplier could tune
73+
the retry and timeout settings on the client, or use different credentials
74+
based on which region is being called. In our
75+
[regional_role_client_supplier](./clientsupplier/regionalroleclientsupplier.go)
76+
example, we show how you can build a custom Client Supplier which
77+
creates clients by assuming different IAM roles for different regions.
78+
79+
# Writing Examples
80+
81+
If you want to contribute a new example, that's awesome!
82+
To make sure that your example runs in our CI,
83+
please make sure that it meets the following requirements:
84+
85+
1. The example MUST be a distinct subdirectory or file in the [`examples/`](./) directory.
86+
1. The example MAY be nested arbitrarily deeply.
87+
1. Each example file MUST contain exactly one example.
88+
1. Each example filename MUST be descriptive.
89+
1. Each example file MUST contain validation checks to check for expected returned values and MUST panic is the returned value is no expected.
90+
1. Each example MUST also be called inside the `main` function of [main.go](./main.go).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/*
5+
This example sets up an MRK multi-keyring and an MRK discovery
6+
multi-keyring using a custom client supplier.
7+
A custom client supplier grants users access to more granular
8+
configuration aspects of their authentication details and KMS
9+
client. In this example, we create a simple custom client supplier
10+
that authenticates with a different IAM role based on the
11+
region of the KMS key.
12+
13+
This example creates a MRK multi-keyring configured with a custom
14+
client supplier using a single MRK and encrypts the example_data with it.
15+
Then, it creates a MRK discovery multi-keyring to decrypt the ciphertext.
16+
*/
17+
18+
package clientsupplier
19+
20+
import (
21+
"context"
22+
"fmt"
23+
24+
mpl "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygenerated"
25+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
26+
client "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygenerated"
27+
esdktypes "github.com/aws/aws-encryption-sdk/awscryptographyencryptionsdksmithygeneratedtypes"
28+
)
29+
30+
func ClientSupplierExample(exampleText, mrkKeyIdEncrypt, awsAccountId string, awsRegions []string) {
31+
// Step 1: Instantiate the encryption SDK client.
32+
// This builds the default client with the RequireEncryptRequireDecrypt commitment policy,
33+
// which enforces that this client only encrypts using committing algorithm suites and enforces
34+
// that this client will only decrypt encrypted messages that were created with a committing
35+
// algorithm suite.
36+
encryptionClient, err := client.NewClient(esdktypes.AwsEncryptionSdkConfig{})
37+
if err != nil {
38+
panic(err)
39+
}
40+
// Step 2: Create your encryption context.
41+
// Remember that your encryption context is NOT SECRET.
42+
// For more information, see
43+
// https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
44+
encryptionContext := map[string]string{
45+
"encryption": "context",
46+
"is not": "secret",
47+
"but adds": "useful metadata",
48+
"that can help you": "be confident that",
49+
"the data you are handling": "is what you think it is",
50+
}
51+
// Step 3: Initialize the mpl client
52+
matProv, err := mpl.NewClient(mpltypes.MaterialProvidersConfig{})
53+
if err != nil {
54+
panic(err)
55+
}
56+
// Step 4: Create keyrings
57+
// First Keyring: Create the multi-keyring using our custom client supplier
58+
// defined in the RegionalRoleClientSupplier class in this directory.
59+
// Note: RegionalRoleClientSupplier will internally use the key_arn's region
60+
// to retrieve the correct IAM role.
61+
awsKmsMrkKeyringMultiInput := mpltypes.CreateAwsKmsMrkMultiKeyringInput{
62+
ClientSupplier: &RegionalRoleClientSupplier{},
63+
Generator: &mrkKeyIdEncrypt,
64+
}
65+
awsKmsMrkMultiKeyring, err := matProv.CreateAwsKmsMrkMultiKeyring(context.Background(), awsKmsMrkKeyringMultiInput)
66+
if err != nil {
67+
panic(err)
68+
}
69+
// Second Keyring: Create a MRK discovery multi-keyring with a custom client supplier.
70+
// A discovery MRK multi-keyring will be composed of
71+
// multiple discovery MRK keyrings, one for each region.
72+
// Each component keyring has its own KMS client in a particular region.
73+
// When we provide a client supplier to the multi-keyring, all component
74+
// keyrings will use that client supplier configuration.
75+
// In our tests, we make `mrk_key_id_encrypt` an MRK with a replica, and
76+
// provide only the replica region in our discovery filter.
77+
discoveryFilter := mpltypes.DiscoveryFilter{
78+
AccountIds: []string{awsAccountId},
79+
Partition: "aws",
80+
}
81+
awsKmsMrkDiscoveryMultiKeyringInput := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{
82+
ClientSupplier: &RegionalRoleClientSupplier{},
83+
Regions: awsRegions,
84+
DiscoveryFilter: &discoveryFilter,
85+
}
86+
awsKmsMrkDiscoveryMultiKeyring, err := matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInput)
87+
// Step 5a: Encrypt
88+
res, err := encryptionClient.Encrypt(context.Background(), esdktypes.EncryptInput{
89+
Plaintext: []byte(exampleText),
90+
EncryptionContext: encryptionContext,
91+
Keyring: awsKmsMrkMultiKeyring,
92+
})
93+
if err != nil {
94+
panic(err)
95+
}
96+
// Validate Ciphertext and Plaintext before encryption are NOT the same
97+
if string(res.Ciphertext) == exampleText {
98+
panic("Ciphertext and Plaintext before encryption are the same")
99+
}
100+
// Step 5b: Decrypt
101+
// Decrypt your encrypted data using the discovery multi keyring.
102+
// On Decrypt, the header of the encrypted message (ciphertext) will be parsed.
103+
// The header contains the Encrypted Data Keys (EDKs), which, if the EDK
104+
// was encrypted by a KMS Keyring, includes the KMS Key ARN.
105+
// For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption
106+
// is successful.
107+
// Since every member of the Multi Keyring is a Discovery Keyring:
108+
// Each Keyring will filter the EDKs by the Discovery Filter and the Keyring's region.
109+
// For each filtered EDK, the keyring will attempt decryption with the keyring's client.
110+
// All of this is done serially, until a success occurs or all keyrings have failed
111+
// all (filtered) EDKs. KMS MRK Discovery Keyrings will attempt to decrypt
112+
// Multi Region Keys (MRKs) and regular KMS Keys.
113+
decryptOutput, err := encryptionClient.Decrypt(context.Background(), esdktypes.DecryptInput{
114+
EncryptionContext: encryptionContext,
115+
Keyring: awsKmsMrkDiscoveryMultiKeyring,
116+
Ciphertext: res.Ciphertext,
117+
})
118+
if err != nil {
119+
panic(err)
120+
}
121+
// Validate Plaintext after decryption and Plaintext before encryption ARE the same
122+
if string(decryptOutput.Plaintext) != exampleText {
123+
panic("Plaintext after decryption and Plaintext before encryption are NOT the same")
124+
}
125+
// If you do not specify the encryption context on Decrypt, it's recommended to check if the resulting encryption context matches.
126+
// The encryption context was specified on decrypt; we are validating the encryption context for demonstration only.
127+
// Before your application uses plaintext data, verify that the encryption context that
128+
// you used to encrypt the message is included in the encryption context that was used to
129+
// decrypt the message. The AWS Encryption SDK can add pairs, so don't require an exact match.
130+
if err = validateEncryptionContext(encryptionContext, decryptOutput.EncryptionContext); err != nil {
131+
panic(err)
132+
}
133+
// Test the Missing Region Exception
134+
// (This is for demonstration; you do not need to do this in your code.)
135+
136+
// Create a MRK discovery multi-keyring with a custom client supplier and a fake region.
137+
awsKmsMrkDiscoveryMultiKeyringInputMissingRegion := mpltypes.CreateAwsKmsMrkDiscoveryMultiKeyringInput{
138+
ClientSupplier: &RegionalRoleClientSupplier{},
139+
Regions: []string{"fake-region"},
140+
DiscoveryFilter: &discoveryFilter,
141+
}
142+
_, err = matProv.CreateAwsKmsMrkDiscoveryMultiKeyring(context.Background(), awsKmsMrkDiscoveryMultiKeyringInputMissingRegion)
143+
// Swallow the AwsCryptographicMaterialProvidersException but you may choose how to handle the exception
144+
switch err.(type) {
145+
case mpltypes.AwsCryptographicMaterialProvidersException:
146+
// You may choose how to handle the exception in this switch case.
147+
default:
148+
panic("Decryption using discovery keyring with missing region MUST raise AwsCryptographicMaterialProvidersException")
149+
}
150+
fmt.Println("Client Supplier Example completed successfully")
151+
}
152+
153+
// This function only does subset matching because AWS Encryption SDK can add pairs, so don't require an exact match.
154+
func validateEncryptionContext(expected, actual map[string]string) error {
155+
for expectedKey, expectedValue := range expected {
156+
actualValue, exists := actual[expectedKey]
157+
if !exists || actualValue != expectedValue {
158+
return fmt.Errorf("encryption context mismatch: expected key '%s' with value '%s'",
159+
expectedKey, expectedValue)
160+
}
161+
}
162+
return nil
163+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package clientsupplier
2+
3+
import (
4+
"context"
5+
6+
mpltypes "github.com/aws/aws-cryptographic-material-providers-library/mpl/awscryptographymaterialproviderssmithygeneratedtypes"
7+
"github.com/aws/aws-sdk-go-v2/config"
8+
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
9+
"github.com/aws/aws-sdk-go-v2/service/kms"
10+
"github.com/aws/aws-sdk-go-v2/service/sts"
11+
)
12+
13+
/*
14+
Example class demonstrating an implementation of a custom client supplier.
15+
This particular implementation will create KMS clients with different IAM roles,
16+
depending on the region passed.
17+
*/
18+
19+
// RegionalRoleClientSupplier provides implementation for mpltypes.IClientSupplier
20+
type RegionalRoleClientSupplier struct {
21+
}
22+
23+
func (this *RegionalRoleClientSupplier) GetClient(input mpltypes.GetClientInput) (kms.Client, error) {
24+
region := input.Region
25+
// Check if the region is supported
26+
regionIamRoleMap := RegionIamRoleMap()
27+
var defaultVal kms.Client
28+
// Check if region is supported
29+
if _, exists := regionIamRoleMap[region]; !exists {
30+
return defaultVal, mpltypes.AwsCryptographicMaterialProvidersException{
31+
Message: "Region is not supported by this client supplier",
32+
}
33+
}
34+
// Get the IAM role ARN associated with the region
35+
arn := regionIamRoleMap[region]
36+
ctx := context.TODO()
37+
cfg, err := config.LoadDefaultConfig(ctx,
38+
config.WithRegion(region),
39+
)
40+
if err != nil {
41+
return defaultVal, err
42+
}
43+
stsClient := sts.NewFromConfig(cfg)
44+
// Create the AssumeRoleProvider
45+
provider := stscreds.NewAssumeRoleProvider(stsClient, arn, func(o *stscreds.AssumeRoleOptions) {
46+
o.RoleSessionName = "Go-ESDK-Client-Supplier-Example-Session"
47+
})
48+
// Load AWS SDK configuration with the AssumeRoleProvider
49+
sdkConfig, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region), config.WithCredentialsProvider(provider))
50+
if err != nil {
51+
return defaultVal, mpltypes.AwsCryptographicMaterialProvidersException{Message: "failed to load AWS SDK config"}
52+
}
53+
// Create the KMS client
54+
kmsClient := kms.NewFromConfig(sdkConfig)
55+
// Return the KMS client wrapped in a custom type
56+
return *kmsClient, nil
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
File 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+
12+
const (
13+
usEast1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Dafny-Role-only-us-east-1-KMS-keys"
14+
euWest1IamRole = "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Dafny-Role-only-eu-west-1-KMS-keys"
15+
)
16+
17+
func RegionIamRoleMap() map[string]string {
18+
return map[string]string{
19+
"us-east-1": usEast1IamRole,
20+
"eu-west-1": euWest1IamRole,
21+
}
22+
}

0 commit comments

Comments
 (0)