diff --git a/builtin/providers/aws/resource_aws_kms_key.go b/builtin/providers/aws/resource_aws_kms_key.go index 90294dc87..7a03ccee0 100644 --- a/builtin/providers/aws/resource_aws_kms_key.go +++ b/builtin/providers/aws/resource_aws_kms_key.go @@ -3,7 +3,9 @@ package aws import ( "fmt" "log" + "time" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/aws/aws-sdk-go/aws" @@ -51,6 +53,16 @@ func resourceAwsKmsKey() *schema.Resource { Computed: true, StateFunc: normalizeJson, }, + "is_enabled": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "enable_key_rotation": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "deletion_window_in_days": &schema.Schema{ Type: schema.TypeInt, Optional: true, @@ -88,8 +100,9 @@ func resourceAwsKmsKeyCreate(d *schema.ResourceData, meta interface{}) error { } d.SetId(*resp.KeyMetadata.KeyId) + d.Set("key_id", resp.KeyMetadata.KeyId) - return resourceAwsKmsKeyRead(d, meta) + return _resourceAwsKmsKeyUpdate(d, meta, true) } func resourceAwsKmsKeyRead(d *schema.ResourceData, meta interface{}) error { @@ -110,6 +123,7 @@ func resourceAwsKmsKeyRead(d *schema.ResourceData, meta interface{}) error { d.Set("key_id", metadata.KeyId) d.Set("description", metadata.Description) d.Set("key_usage", metadata.KeyUsage) + d.Set("is_enabled", metadata.Enabled) p, err := conn.GetKeyPolicy(&kms.GetKeyPolicyInput{ KeyId: metadata.KeyId, @@ -121,12 +135,40 @@ func resourceAwsKmsKeyRead(d *schema.ResourceData, meta interface{}) error { d.Set("policy", normalizeJson(*p.Policy)) + krs, err := conn.GetKeyRotationStatus(&kms.GetKeyRotationStatusInput{ + KeyId: metadata.KeyId, + }) + if err != nil { + return err + } + d.Set("enable_key_rotation", krs.KeyRotationEnabled) + return nil } func resourceAwsKmsKeyUpdate(d *schema.ResourceData, meta interface{}) error { + return _resourceAwsKmsKeyUpdate(d, meta, false) +} + +// We expect new keys to be enabled already +// but there is no easy way to differentiate between Update() +// called from Create() and regular update, so we have this wrapper +func _resourceAwsKmsKeyUpdate(d *schema.ResourceData, meta interface{}, isFresh bool) error { conn := meta.(*AWSClient).kmsconn + if d.HasChange("is_enabled") && d.Get("is_enabled").(bool) && !isFresh { + // Enable before any attributes will be modified + if err := updateKmsKeyStatus(conn, d.Id(), d.Get("is_enabled").(bool)); err != nil { + return err + } + } + + if d.HasChange("enable_key_rotation") { + if err := updateKmsKeyRotationStatus(conn, d); err != nil { + return err + } + } + if d.HasChange("description") { if err := resourceAwsKmsKeyDescriptionUpdate(conn, d); err != nil { return err @@ -137,6 +179,15 @@ func resourceAwsKmsKeyUpdate(d *schema.ResourceData, meta interface{}) error { return err } } + + if d.HasChange("is_enabled") && !d.Get("is_enabled").(bool) { + // Only disable when all attributes are modified + // because we cannot modify disabled keys + if err := updateKmsKeyStatus(conn, d.Id(), d.Get("is_enabled").(bool)); err != nil { + return err + } + } + return resourceAwsKmsKeyRead(d, meta) } @@ -169,6 +220,108 @@ func resourceAwsKmsKeyPolicyUpdate(conn *kms.KMS, d *schema.ResourceData) error return err } +func updateKmsKeyStatus(conn *kms.KMS, id string, shouldBeEnabled bool) error { + var err error + + if shouldBeEnabled { + log.Printf("[DEBUG] Enabling KMS key %q", id) + _, err = conn.EnableKey(&kms.EnableKeyInput{ + KeyId: aws.String(id), + }) + } else { + log.Printf("[DEBUG] Disabling KMS key %q", id) + _, err = conn.DisableKey(&kms.DisableKeyInput{ + KeyId: aws.String(id), + }) + } + + if err != nil { + return fmt.Errorf("Failed to set KMS key %q status to %t: %q", + id, shouldBeEnabled, err.Error()) + } + + // Wait for propagation since KMS is eventually consistent + wait := resource.StateChangeConf{ + Pending: []string{fmt.Sprintf("%t", !shouldBeEnabled)}, + Target: []string{fmt.Sprintf("%t", shouldBeEnabled)}, + Timeout: 20 * time.Minute, + MinTimeout: 2 * time.Second, + ContinuousTargetOccurence: 10, + Refresh: func() (interface{}, string, error) { + log.Printf("[DEBUG] Checking if KMS key %s enabled status is %t", + id, shouldBeEnabled) + resp, err := conn.DescribeKey(&kms.DescribeKeyInput{ + KeyId: aws.String(id), + }) + if err != nil { + return resp, "FAILED", err + } + status := fmt.Sprintf("%t", *resp.KeyMetadata.Enabled) + log.Printf("[DEBUG] KMS key %s status received: %s, retrying", id, status) + + return resp, status, nil + }, + } + + _, err = wait.WaitForState() + if err != nil { + return fmt.Errorf("Failed setting KMS key status to %t: %s", shouldBeEnabled, err) + } + + return nil +} + +func updateKmsKeyRotationStatus(conn *kms.KMS, d *schema.ResourceData) error { + var err error + shouldEnableRotation := d.Get("enable_key_rotation").(bool) + if shouldEnableRotation { + log.Printf("[DEBUG] Enabling key rotation for KMS key %q", d.Id()) + _, err = conn.EnableKeyRotation(&kms.EnableKeyRotationInput{ + KeyId: aws.String(d.Id()), + }) + } else { + log.Printf("[DEBUG] Disabling key rotation for KMS key %q", d.Id()) + _, err = conn.DisableKeyRotation(&kms.DisableKeyRotationInput{ + KeyId: aws.String(d.Id()), + }) + } + + if err != nil { + return fmt.Errorf("Failed to set key rotation for %q to %t: %q", + d.Id(), shouldEnableRotation, err.Error()) + } + + // Wait for propagation since KMS is eventually consistent + wait := resource.StateChangeConf{ + Pending: []string{fmt.Sprintf("%t", !shouldEnableRotation)}, + Target: []string{fmt.Sprintf("%t", shouldEnableRotation)}, + Timeout: 5 * time.Minute, + MinTimeout: 1 * time.Second, + ContinuousTargetOccurence: 5, + Refresh: func() (interface{}, string, error) { + log.Printf("[DEBUG] Checking if KMS key %s rotation status is %t", + d.Id(), shouldEnableRotation) + resp, err := conn.GetKeyRotationStatus(&kms.GetKeyRotationStatusInput{ + KeyId: aws.String(d.Id()), + }) + if err != nil { + return resp, "FAILED", err + } + status := fmt.Sprintf("%t", *resp.KeyRotationEnabled) + log.Printf("[DEBUG] KMS key %s rotation status received: %s, retrying", d.Id(), status) + + return resp, status, nil + }, + } + + _, err = wait.WaitForState() + if err != nil { + return fmt.Errorf("Failed setting KMS key rotation status to %t: %s", shouldEnableRotation, err) + } + + return nil +} + func resourceAwsKmsKeyDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).kmsconn keyId := d.Get("key_id").(string) diff --git a/builtin/providers/aws/resource_aws_kms_key_test.go b/builtin/providers/aws/resource_aws_kms_key_test.go index 47cc2374c..e85f84e0c 100644 --- a/builtin/providers/aws/resource_aws_kms_key_test.go +++ b/builtin/providers/aws/resource_aws_kms_key_test.go @@ -12,6 +12,8 @@ import ( ) func TestAccAWSKmsKey_basic(t *testing.T) { + var keyBefore, keyAfter kms.KeyMetadata + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -20,13 +22,52 @@ func TestAccAWSKmsKey_basic(t *testing.T) { resource.TestStep{ Config: testAccAWSKmsKey, Check: resource.ComposeTestCheckFunc( - testAccCheckAWSKmsKeyExists("aws_kms_key.foo"), + testAccCheckAWSKmsKeyExists("aws_kms_key.foo", &keyBefore), ), }, resource.TestStep{ Config: testAccAWSKmsKey_removedPolicy, Check: resource.ComposeTestCheckFunc( - testAccCheckAWSKmsKeyExists("aws_kms_key.foo"), + testAccCheckAWSKmsKeyExists("aws_kms_key.foo", &keyAfter), + ), + }, + }, + }) +} + +func TestAccAWSKmsKey_isEnabled(t *testing.T) { + var key1, key2, key3 kms.KeyMetadata + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSKmsKeyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSKmsKey_enabledRotation, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSKmsKeyExists("aws_kms_key.bar", &key1), + resource.TestCheckResourceAttr("aws_kms_key.bar", "is_enabled", "true"), + testAccCheckAWSKmsKeyIsEnabled(&key1, true), + resource.TestCheckResourceAttr("aws_kms_key.bar", "enable_key_rotation", "true"), + ), + }, + resource.TestStep{ + Config: testAccAWSKmsKey_disabled, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSKmsKeyExists("aws_kms_key.bar", &key2), + resource.TestCheckResourceAttr("aws_kms_key.bar", "is_enabled", "false"), + testAccCheckAWSKmsKeyIsEnabled(&key2, false), + resource.TestCheckResourceAttr("aws_kms_key.bar", "enable_key_rotation", "false"), + ), + }, + resource.TestStep{ + Config: testAccAWSKmsKey_enabled, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSKmsKeyExists("aws_kms_key.bar", &key3), + resource.TestCheckResourceAttr("aws_kms_key.bar", "is_enabled", "true"), + testAccCheckAWSKmsKeyIsEnabled(&key3, true), + resource.TestCheckResourceAttr("aws_kms_key.bar", "enable_key_rotation", "true"), ), }, }, @@ -55,13 +96,39 @@ func testAccCheckAWSKmsKeyDestroy(s *terraform.State) error { return nil } -func testAccCheckAWSKmsKeyExists(name string) resource.TestCheckFunc { +func testAccCheckAWSKmsKeyExists(name string, key *kms.KeyMetadata) resource.TestCheckFunc { return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Not found: %s", name) } + if rs.Primary.ID == "" { + return fmt.Errorf("No KMS Key ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).kmsconn + + out, err := conn.DescribeKey(&kms.DescribeKeyInput{ + KeyId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *key = *out.KeyMetadata + + return nil + } +} + +func testAccCheckAWSKmsKeyIsEnabled(key *kms.KeyMetadata, isEnabled bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *key.Enabled != isEnabled { + return fmt.Errorf("Expected key %q to have is_enabled=%t, given %t", + *key.Arn, isEnabled, *key.Enabled) + } + return nil } } @@ -95,3 +162,24 @@ resource "aws_kms_key" "foo" { description = "Terraform acc test %s" deletion_window_in_days = 7 }`, kmsTimestamp) + +var testAccAWSKmsKey_enabledRotation = fmt.Sprintf(` +resource "aws_kms_key" "bar" { + description = "Terraform acc test is_enabled %s" + deletion_window_in_days = 7 + enable_key_rotation = true +}`, kmsTimestamp) +var testAccAWSKmsKey_disabled = fmt.Sprintf(` +resource "aws_kms_key" "bar" { + description = "Terraform acc test is_enabled %s" + deletion_window_in_days = 7 + enable_key_rotation = false + is_enabled = false +}`, kmsTimestamp) +var testAccAWSKmsKey_enabled = fmt.Sprintf(` +resource "aws_kms_key" "bar" { + description = "Terraform acc test is_enabled %s" + deletion_window_in_days = 7 + enable_key_rotation = true + is_enabled = true +}`, kmsTimestamp) diff --git a/website/source/docs/providers/aws/r/kms_key.html.markdown b/website/source/docs/providers/aws/r/kms_key.html.markdown index be1956246..751babb71 100644 --- a/website/source/docs/providers/aws/r/kms_key.html.markdown +++ b/website/source/docs/providers/aws/r/kms_key.html.markdown @@ -29,6 +29,9 @@ The following arguments are supported: * `policy` - (Optional) A valid policy JSON document. * `deletion_window_in_days` - (Optional) Duration in days after which the key is deleted after destruction of the resource, must be between 7 and 30 days. Defaults to 30 days. +* `is_enabled` - (Optional) Specifies whether the key is enabled. Defaults to true. +* `enable_key_rotation` - (Optional) Specifies whether [key rotation](http://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html) + is enabled. Defaults to false. ## Attributes Reference