diff --git a/aws/resource_aws_iam_access_key.go b/aws/resource_aws_iam_access_key.go index caee6abfe456..af8f853f67bc 100644 --- a/aws/resource_aws_iam_access_key.go +++ b/aws/resource_aws_iam_access_key.go @@ -43,6 +43,12 @@ func resourceAwsIamAccessKey() *schema.Resource { Sensitive: true, }, "ses_smtp_password": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Deprecated: "AWS SigV2 for SES SMTP passwords isy deprecated.\nUse 'ses_smtp_password_v4' for region-specific AWS SigV4 signed SES SMTP password instead.", + }, + "ses_smtp_password_v4": { Type: schema.TypeString, Computed: true, Sensitive: true, @@ -105,12 +111,20 @@ func resourceAwsIamAccessKeyCreate(d *schema.ResourceData, meta interface{}) err } } - sesSMTPPassword, err := sesSmtpPasswordFromSecretKey(createResp.AccessKey.SecretAccessKey) + // AWS SigV2 + sesSMTPPassword, err := sesSmtpPasswordFromSecretKeySigV2(createResp.AccessKey.SecretAccessKey) if err != nil { - return fmt.Errorf("error getting SES SMTP Password from Secret Access Key: %s", err) + return fmt.Errorf("error getting SES SigV2 SMTP Password from Secret Access Key: %s", err) } d.Set("ses_smtp_password", sesSMTPPassword) + // AWS SigV4 + sesSMTPPasswordV4, err := sesSmtpPasswordFromSecretKeySigV4(createResp.AccessKey.SecretAccessKey, meta.(*AWSClient).region) + if err != nil { + return fmt.Errorf("error getting SES SigV4 SMTP Password from Secret Access Key: %s", err) + } + d.Set("ses_smtp_password_v4", sesSMTPPasswordV4) + return resourceAwsIamAccessKeyReadResult(d, &iam.AccessKeyMetadata{ AccessKeyId: createResp.AccessKey.AccessKeyId, CreateDate: createResp.AccessKey.CreateDate, @@ -197,7 +211,49 @@ func resourceAwsIamAccessKeyStatusUpdate(iamconn *iam.IAM, d *schema.ResourceDat return nil } -func sesSmtpPasswordFromSecretKey(key *string) (string, error) { +func hmacSignature(key []byte, value []byte) ([]byte, error) { + h := hmac.New(sha256.New, key) + if _, err := h.Write(value); err != nil { + return []byte(""), err + } + return h.Sum(nil), nil +} + +func sesSmtpPasswordFromSecretKeySigV4(key *string, region string) (string, error) { + if key == nil { + return "", nil + } + version := byte(0x04) + date := []byte("11111111") + service := []byte("ses") + terminal := []byte("aws4_request") + message := []byte("SendRawEmail") + + rawSig, err := hmacSignature([]byte("AWS4"+*key), date) + if err != nil { + return "", err + } + + if rawSig, err = hmacSignature(rawSig, []byte(region)); err != nil { + return "", err + } + if rawSig, err = hmacSignature(rawSig, service); err != nil { + return "", err + } + if rawSig, err = hmacSignature(rawSig, terminal); err != nil { + return "", err + } + if rawSig, err = hmacSignature(rawSig, message); err != nil { + return "", err + } + + versionedSig := make([]byte, 0, len(rawSig)+1) + versionedSig = append(versionedSig, version) + versionedSig = append(versionedSig, rawSig...) + return base64.StdEncoding.EncodeToString(versionedSig), nil +} + +func sesSmtpPasswordFromSecretKeySigV2(key *string) (string, error) { if key == nil { return "", nil } diff --git a/aws/resource_aws_iam_access_key_test.go b/aws/resource_aws_iam_access_key_test.go index 0c79e22e1587..03e400834c0d 100644 --- a/aws/resource_aws_iam_access_key_test.go +++ b/aws/resource_aws_iam_access_key_test.go @@ -234,7 +234,30 @@ resource "aws_iam_access_key" "a_key" { `, rName) } -func TestSesSmtpPasswordFromSecretKey(t *testing.T) { +func TestSesSmtpPasswordFromSecretKeySigV4(t *testing.T) { + cases := []struct { + Region string + Input string + Expected string + }{ + {"eu-central-1", "some+secret+key", "BMXhUYlu5Z3gSXVQORxlVa7XPaz91aGWdfHxvkOZdWZ2"}, + {"eu-central-1", "another+secret+key", "BBbphbrQmrKMx42d1N6+C7VINYEBGI5v9VsZeTxwskfh"}, + {"us-west-1", "some+secret+key", "BH+jbMzper5WwlwUar9E1ySBqHa9whi0GPo+sJ0mVYJj"}, + {"us-west-1", "another+secret+key", "BKVmjjMDFk/qqw8EROW99bjCS65PF8WKvK5bSr4Y6EqF"}, + } + + for _, tc := range cases { + actual, err := sesSmtpPasswordFromSecretKeySigV4(&tc.Input, tc.Region) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if actual != tc.Expected { + t.Fatalf("%q: expected %q, got %q", tc.Input, tc.Expected, actual) + } + } +} + +func TestSesSmtpPasswordFromSecretKeySigV2(t *testing.T) { cases := []struct { Input string Expected string @@ -244,7 +267,7 @@ func TestSesSmtpPasswordFromSecretKey(t *testing.T) { } for _, tc := range cases { - actual, err := sesSmtpPasswordFromSecretKey(&tc.Input) + actual, err := sesSmtpPasswordFromSecretKeySigV2(&tc.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } diff --git a/website/docs/r/iam_access_key.html.markdown b/website/docs/r/iam_access_key.html.markdown index d08ac09ef322..76ab70a6abc2 100644 --- a/website/docs/r/iam_access_key.html.markdown +++ b/website/docs/r/iam_access_key.html.markdown @@ -48,6 +48,21 @@ output "secret" { } ``` +```hcl +resource "aws_iam_user" "test" { + name = "test" + path = "/test/" +} + +resource "aws_iam_access_key" "test" { + user = aws_iam_user.test.name +} + +output "aws_iam_smtp_password_v4" { + value = aws_iam_access_key.test.ses_smtp_password_v4 +} +``` + ## Argument Reference The following arguments are supported: @@ -75,6 +90,9 @@ the use of the secret key in automation. * `encrypted_secret` - The encrypted secret, base64 encoded, if `pgp_key` was specified. ~> **NOTE:** The encrypted secret may be decrypted using the command line, for example: `terraform output encrypted_secret | base64 --decode | keybase pgp decrypt`. -* `ses_smtp_password` - The secret access key converted into an SES SMTP - password by applying [AWS's documented conversion +* `ses_smtp_password` - **DEPRECATED** The secret access key converted into an SES SMTP + password by applying AWS's SigV2 conversion algorithm +* `ses_smtp_password_v4` - The secret access key converted into an SES SMTP + password by applying [AWS's documented Sigv4 conversion algorithm](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html#smtp-credentials-convert). + As SigV4 is region specific, valid Provider regions are `ap-south-1`, `ap-southeast-2`, `eu-central-1`, `eu-west-1`, `us-east-1` and `us-west-2`. See current [AWS SES regions](https://docs.aws.amazon.com/general/latest/gr/rande.html#ses_region)