Skip to content

Commit

Permalink
Merge pull request #20533 from bshelton/f-aws_multi_region_kms_key
Browse files Browse the repository at this point in the history
Adding support for multi-region for aws_kms_key resource
  • Loading branch information
ewbankkit authored Oct 25, 2021
2 parents 6f82a3a + 775463f commit 8c5649e
Show file tree
Hide file tree
Showing 22 changed files with 2,155 additions and 165 deletions.
19 changes: 19 additions & 0 deletions .changelog/20533.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
```release-note:enhancement
resource/aws_kms_key: Add `multi_region` argument
```

```release-note:enhancement
data-source/aws_kms_key: Add `multi_region` and `multi_region_configuration` attributes
```

```release-note:new-resource
aws_kms_replica_key
```

```release-note:enhancement
resource/aws_kms_external_key: Add `multi_region` argument
```

```release-note:new-resource
aws_kms_replica_external_key
```
34 changes: 28 additions & 6 deletions internal/conns/conns.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/accessanalyzer"
"github.com/aws/aws-sdk-go/service/acm"
"github.com/aws/aws-sdk-go/service/acmpca"
Expand Down Expand Up @@ -448,12 +449,7 @@ func (c *Config) Client() (interface{}, error) {
SkipRequestingAccountId: c.SkipRequestingAccountId,
StsEndpoint: c.Endpoints["sts"],
Token: c.Token,
UserAgentProducts: []*awsbase.UserAgentProduct{
{Name: "APN", Version: "1.0"},
{Name: "HashiCorp", Version: "1.0"},
{Name: "Terraform", Version: c.TerraformVersion, Extra: []string{"+https://www.terraform.io"}},
{Name: "terraform-provider-aws", Version: version.ProviderVersion, Extra: []string{"+https://registry.terraform.io/providers/hashicorp/aws"}},
},
UserAgentProducts: StdUserAgentProducts(c.TerraformVersion),
}

sess, accountID, Partition, err := awsbase.GetSessionWithAccountIDAndPartition(awsbaseConfig)
Expand Down Expand Up @@ -941,6 +937,32 @@ func (c *Config) Client() (interface{}, error) {
return client, nil
}

func StdUserAgentProducts(terraformVersion string) []*awsbase.UserAgentProduct {
return []*awsbase.UserAgentProduct{
{Name: "APN", Version: "1.0"},
{Name: "HashiCorp", Version: "1.0"},
{Name: "Terraform", Version: terraformVersion, Extra: []string{"+https://www.terraform.io"}},
{Name: "terraform-provider-aws", Version: version.ProviderVersion, Extra: []string{"+https://registry.terraform.io/providers/hashicorp/aws"}},
}
}

func NewSessionForRegion(cfg *aws.Config, region, terraformVersion string) (*session.Session, error) {
session, err := session.NewSession(cfg)

if err != nil {
return nil, err
}

userAgentProducts := StdUserAgentProducts(terraformVersion)
// Copied from github.com/hashicorp/[email protected]/session.go:
for i := len(userAgentProducts) - 1; i >= 0; i-- {
product := userAgentProducts[i]
session.Handlers.Build.PushFront(request.MakeAddToUserAgentHandler(product.Name, product.Version, product.Extra...))
}

return session.Copy(&aws.Config{Region: aws.String(region)}), nil
}

func HasEC2Classic(platforms []string) bool {
for _, p := range platforms {
if p == "EC2" {
Expand Down
12 changes: 7 additions & 5 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1266,11 +1266,13 @@ func Provider() *schema.Provider {

"aws_kinesis_video_stream": kinesisvideo.ResourceStream(),

"aws_kms_alias": kms.ResourceAlias(),
"aws_kms_ciphertext": kms.ResourceCiphertext(),
"aws_kms_external_key": kms.ResourceExternalKey(),
"aws_kms_grant": kms.ResourceGrant(),
"aws_kms_key": kms.ResourceKey(),
"aws_kms_alias": kms.ResourceAlias(),
"aws_kms_ciphertext": kms.ResourceCiphertext(),
"aws_kms_external_key": kms.ResourceExternalKey(),
"aws_kms_grant": kms.ResourceGrant(),
"aws_kms_key": kms.ResourceKey(),
"aws_kms_replica_external_key": kms.ResourceReplicaExternalKey(),
"aws_kms_replica_key": kms.ResourceReplicaKey(),

"aws_lakeformation_data_lake_settings": lakeformation.ResourceDataLakeSettings(),
"aws_lakeformation_permissions": lakeformation.ResourcePermissions(),
Expand Down
50 changes: 38 additions & 12 deletions internal/service/kms/external_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,54 +39,51 @@ func ResourceExternalKey() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"bypass_policy_lockout_safety_check": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"deletion_window_in_days": {
Type: schema.TypeInt,
Optional: true,
Default: 30,
ValidateFunc: validation.IntBetween(7, 30),
},

"description": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 8192),
},

"enabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"expiration_model": {
Type: schema.TypeString,
Computed: true,
},

"key_material_base64": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
},

"key_state": {
Type: schema.TypeString,
Computed: true,
},

"key_usage": {
Type: schema.TypeString,
Computed: true,
},

"multi_region": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
ForceNew: true,
},
"policy": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -97,10 +94,8 @@ func ResourceExternalKey() *schema.Resource {
validation.StringIsJSON,
),
},

"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),

"valid_to": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -125,6 +120,10 @@ func resourceExternalKeyCreate(d *schema.ResourceData, meta interface{}) error {
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("multi_region"); ok {
input.MultiRegion = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("policy"); ok {
input.Policy = aws.String(v.(string))
}
Expand Down Expand Up @@ -154,7 +153,7 @@ func resourceExternalKeyCreate(d *schema.ResourceData, meta interface{}) error {
validTo := d.Get("valid_to").(string)

if err := importKmsExternalKeyMaterial(conn, d.Id(), v.(string), validTo); err != nil {
return fmt.Errorf("error importing KMS External Key (%s) material: %s", d.Id(), err)
return fmt.Errorf("error importing KMS External Key (%s) material: %w", d.Id(), err)
}

if _, err := WaitKeyMaterialImported(conn, d.Id()); err != nil {
Expand All @@ -174,6 +173,19 @@ func resourceExternalKeyCreate(d *schema.ResourceData, meta interface{}) error {
}
}

// Wait for propagation since KMS is eventually consistent.
if v, ok := d.GetOk("policy"); ok {
if err := WaitKeyPolicyPropagated(conn, d.Id(), v.(string)); err != nil {
return fmt.Errorf("error waiting for KMS External Key (%s) policy propagation: %w", d.Id(), err)
}
}

if len(tags) > 0 {
if err := WaitTagsPropagated(conn, d.Id(), tags); err != nil {
return fmt.Errorf("error waiting for KMS External Key (%s) tag propagation: %w", d.Id(), err)
}
}

return resourceExternalKeyRead(d, meta)
}

Expand All @@ -194,12 +206,26 @@ func resourceExternalKeyRead(d *schema.ResourceData, meta interface{}) error {
return err
}

if keyManager := aws.StringValue(key.metadata.KeyManager); keyManager != kms.KeyManagerTypeCustomer {
return fmt.Errorf("KMS External Key (%s) has invalid KeyManager: %s", d.Id(), keyManager)
}

if origin := aws.StringValue(key.metadata.Origin); origin != kms.OriginTypeExternal {
return fmt.Errorf("KMS External Key (%s) has invalid Origin: %s", d.Id(), origin)
}

if aws.BoolValue(key.metadata.MultiRegion) &&
aws.StringValue(key.metadata.MultiRegionConfiguration.MultiRegionKeyType) != kms.MultiRegionKeyTypePrimary {
return fmt.Errorf("KMS External Key (%s) is not a multi-Region primary key", d.Id())
}

d.Set("arn", key.metadata.Arn)
d.Set("description", key.metadata.Description)
d.Set("enabled", key.metadata.Enabled)
d.Set("expiration_model", key.metadata.ExpirationModel)
d.Set("key_state", key.metadata.KeyState)
d.Set("key_usage", key.metadata.KeyUsage)
d.Set("multi_region", key.metadata.MultiRegion)
d.Set("policy", key.policy)
if key.metadata.ValidTo != nil {
d.Set("valid_to", aws.TimeValue(key.metadata.ValidTo).Format(time.RFC3339))
Expand Down
44 changes: 44 additions & 0 deletions internal/service/kms/external_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestAccKMSExternalKey_basic(t *testing.T) {
resource.TestCheckNoResourceAttr(resourceName, "key_material_base64"),
resource.TestCheckResourceAttr(resourceName, "key_state", "PendingImport"),
resource.TestCheckResourceAttr(resourceName, "key_usage", "ENCRYPT_DECRYPT"),
resource.TestCheckResourceAttr(resourceName, "multi_region", "false"),
resource.TestMatchResourceAttr(resourceName, "policy", regexp.MustCompile(`Enable IAM User Permissions`)),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
resource.TestCheckResourceAttr(resourceName, "valid_to", ""),
Expand Down Expand Up @@ -81,6 +82,38 @@ func TestAccKMSExternalKey_disappears(t *testing.T) {
})
}

func TestAccKMSExternalKey_multiRegion(t *testing.T) {
var key kms.KeyMetadata
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_kms_external_key.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckExternalKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccExternalKeyMultiRegionConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckExternalKeyExists(resourceName, &key),
resource.TestCheckResourceAttr(resourceName, "multi_region", "true"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"bypass_policy_lockout_safety_check",
"deletion_window_in_days",
"key_material_base64",
},
},
},
})
}

func TestAccKMSExternalKey_deletionWindowInDays(t *testing.T) {
var key1, key2 kms.KeyMetadata
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -552,6 +585,17 @@ resource "aws_kms_external_key" "test" {}
`
}

func testAccExternalKeyMultiRegionConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_kms_external_key" "test" {
description = %[1]q
multi_region = true
deletion_window_in_days = 7
}
`, rName)
}

func testAccExternalKeyDeletionWindowInDaysConfig(rName string, deletionWindowInDays int) string {
return fmt.Sprintf(`
resource "aws_kms_external_key" "test" {
Expand Down
20 changes: 6 additions & 14 deletions internal/service/kms/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/aws/aws-sdk-go/service/kms"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

func FindAliasByName(conn *kms.KMS, name string) (*kms.AliasListEntry, error) {
Expand Down Expand Up @@ -57,16 +58,13 @@ func FindKeyByID(conn *kms.KMS, id string) (*kms.KeyMetadata, error) {
}

if output == nil || output.KeyMetadata == nil {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
return nil, tfresource.NewEmptyResultError(input)
}

keyMetadata := output.KeyMetadata

// Once the CMK is in the pending deletion state Terraform considers it logically deleted.
if state := aws.StringValue(keyMetadata.KeyState); state == kms.KeyStatePendingDeletion {
// Once the CMK is in the pending (replica) deletion state Terraform considers it logically deleted.
if state := aws.StringValue(keyMetadata.KeyState); state == kms.KeyStatePendingDeletion || state == kms.KeyStatePendingReplicaDeletion {
return nil, &resource.NotFoundError{
Message: state,
LastRequest: input,
Expand Down Expand Up @@ -96,10 +94,7 @@ func FindKeyPolicyByKeyIDAndPolicyName(conn *kms.KMS, keyID, policyName string)
}

if output == nil {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
return nil, tfresource.NewEmptyResultError(input)
}

return output.Policy, nil
Expand All @@ -124,10 +119,7 @@ func FindKeyRotationEnabledByKeyID(conn *kms.KMS, keyID string) (*bool, error) {
}

if output == nil {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
return nil, tfresource.NewEmptyResultError(input)
}

return output.KeyRotationEnabled, nil
Expand Down
Loading

0 comments on commit 8c5649e

Please sign in to comment.