diff --git a/.changelog/29923.txt b/.changelog/29923.txt new file mode 100644 index 000000000000..3b75e30a396a --- /dev/null +++ b/.changelog/29923.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_kms_key_policy +``` diff --git a/internal/conns/awsclient.go b/internal/conns/awsclient.go index a3bdc0d7269e..93fb2d8fb048 100644 --- a/internal/conns/awsclient.go +++ b/internal/conns/awsclient.go @@ -53,3 +53,24 @@ func (client *AWSClient) CloudFrontDistributionHostedZoneID() string { } return "Z2FDTNDATAQYW2" // See https://docs.aws.amazon.com/Route53/latest/APIReference/API_AliasTarget.html#Route53-Type-AliasTarget-HostedZoneId } + +// DefaultKMSKeyPolicy returns the default policy for KMS keys in the configured AWS partition. +func (client *AWSClient) DefaultKMSKeyPolicy() string { + return fmt.Sprintf(` +{ + "Id": "default", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Enable IAM User Permissions", + "Effect": "Allow", + "Principal": { + "AWS": "arn:%[1]s:iam::%[2]s:root" + }, + "Action": "kms:*", + "Resource": "*" + } + ] +} +`, client.Partition, client.AccountID) +} diff --git a/internal/service/kms/key_policy.go b/internal/service/kms/key_policy.go new file mode 100644 index 000000000000..407f7aff2d83 --- /dev/null +++ b/internal/service/kms/key_policy.go @@ -0,0 +1,123 @@ +package kms + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +// @SDKResource("aws_kms_key_policy") +func ResourceKeyPolicy() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceKeyPolicyCreate, + ReadWithoutTimeout: resourceKeyPolicyRead, + UpdateWithoutTimeout: resourceKeyPolicyUpdate, + DeleteWithoutTimeout: resourceKeyPolicyDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "bypass_policy_lockout_safety_check": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "key_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 2048), + }, + "policy": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + DiffSuppressOnRefresh: true, + ValidateFunc: validation.StringIsJSON, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + }, + }, + } +} + +func resourceKeyPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).KMSConn() + + keyID := d.Get("key_id").(string) + + if err := updateKeyPolicy(ctx, conn, keyID, d.Get("policy").(string), d.Get("bypass_policy_lockout_safety_check").(bool)); err != nil { + return sdkdiag.AppendErrorf(diags, "attaching KMS Key policy (%s): %s", keyID, err) + } + + d.SetId(keyID) + + return append(diags, resourceKeyPolicyRead(ctx, d, meta)...) +} + +func resourceKeyPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).KMSConn() + + key, err := findKey(ctx, conn, d.Id(), d.IsNewResource()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] KMS Key (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading KMS Key (%s): %s", d.Id(), err) + } + + d.Set("key_id", key.metadata.KeyId) + + policyToSet, err := verify.PolicyToSet(d.Get("policy").(string), key.policy) + if err != nil { + return sdkdiag.AppendErrorf(diags, "while setting policy (%s), encountered: %s", key.policy, err) + } + + d.Set("policy", policyToSet) + + return diags +} + +func resourceKeyPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).KMSConn() + + if d.HasChange("policy") { + if err := updateKeyPolicy(ctx, conn, d.Id(), d.Get("policy").(string), d.Get("bypass_policy_lockout_safety_check").(bool)); err != nil { + return sdkdiag.AppendErrorf(diags, "attaching KMS Key policy (%s): %s", d.Id(), err) + } + } + + return append(diags, resourceKeyPolicyRead(ctx, d, meta)...) +} + +func resourceKeyPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).KMSConn() + + if !d.Get("bypass_policy_lockout_safety_check").(bool) { + if err := updateKeyPolicy(ctx, conn, d.Get("key_id").(string), meta.(*conns.AWSClient).DefaultKMSKeyPolicy(), d.Get("bypass_policy_lockout_safety_check").(bool)); err != nil { + return sdkdiag.AppendErrorf(diags, "attaching KMS Key policy (%s): %s", d.Id(), err) + } else { + log.Printf("[WARN] KMS Key Policy for Key (%s) does not allow PutKeyPolicy. Default Policy cannot be restored. Removing from state", d.Id()) + } + } + + return diags +} diff --git a/internal/service/kms/key_policy_test.go b/internal/service/kms/key_policy_test.go new file mode 100644 index 000000000000..42450f294334 --- /dev/null +++ b/internal/service/kms/key_policy_test.go @@ -0,0 +1,719 @@ +package kms_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/kms" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfkms "github.com/hashicorp/terraform-provider-aws/internal/service/kms" +) + +func TestAccKMSKeyPolicy_basic(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + attachmentResourceName := "aws_kms_key_policy.test" + expectedPolicyText := fmt.Sprintf(`{"Version":"2012-10-17","Id":%[1]q,"Statement":[{"Sid":"Enable IAM User Permissions","Effect":"Allow","Principal":{"AWS":"*"},"Action":"kms:*","Resource":"*"}]}`, rName) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policy(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + testAccCheckKeyHasPolicy(ctx, keyResourceName, expectedPolicyText), + ), + }, + { + ResourceName: attachmentResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"bypass_policy_lockout_safety_check"}, + }, + { + Config: testAccKeyPolicyConfig_removedPolicy(keyResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + }, + }) +} + +func TestAccKMSKeyPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + attachmentResourceName := "aws_kms_key_policy.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policy(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, attachmentResourceName, &key), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfkms.ResourceKey(), attachmentResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccKMSKeyPolicy_bypass(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + attachmentResourceName := "aws_kms_key_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policyBypass(rName, false), + ExpectError: regexp.MustCompile(`The new key policy will not allow you to update the key policy in the future`), + }, + { + Config: testAccKeyPolicyConfig_policyBypass(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + resource.TestCheckResourceAttr(attachmentResourceName, "bypass_policy_lockout_safety_check", "true"), + ), + }, + { + ResourceName: attachmentResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"bypass_policy_lockout_safety_check"}, + }, + }, + }) +} + +func TestAccKMSKeyPolicy_bypassUpdate(t *testing.T) { + ctx := acctest.Context(t) + var before, after kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + attachmentResourceName := "aws_kms_key_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policy(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &before), + resource.TestCheckResourceAttr(attachmentResourceName, "bypass_policy_lockout_safety_check", "false"), + ), + }, + { + Config: testAccKeyPolicyConfig_policyBypass(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &after), + resource.TestCheckResourceAttr(attachmentResourceName, "bypass_policy_lockout_safety_check", "true"), + ), + }, + }, + }) +} + +func TestAccKMSKeyPolicy_keyIsEnabled(t *testing.T) { + ctx := acctest.Context(t) + var before, after kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_keyIsEnabled(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &before), + ), + }, + { + Config: testAccKeyPolicyConfig_keyIsEnabled(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &after), + ), + }, + }, + }) +} + +func TestAccKMSKeyPolicy_iamRole(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policyIAMRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + { + ResourceName: keyResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_window_in_days", "bypass_policy_lockout_safety_check"}, + }, + }, + }) +} + +func TestAccKMSKeyPolicy_iamRoleUpdate(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policy(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + { + Config: testAccKeyPolicyConfig_policyIAMRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + }, + }) +} + +// // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/11801 +func TestAccKMSKeyPolicy_iamRoleOrder(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policyIAMMultiRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + { + Config: testAccKeyPolicyConfig_policyIAMMultiRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + PlanOnly: true, + }, + { + Config: testAccKeyPolicyConfig_policyIAMMultiRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + PlanOnly: true, + }, + { + Config: testAccKeyPolicyConfig_policyIAMMultiRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + PlanOnly: true, + }, + }, + }) +} + +// // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/7646 +func TestAccKMSKeyPolicy_iamServiceLinkedRole(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policyIAMServiceLinkedRole(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + { + ResourceName: keyResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_window_in_days", "bypass_policy_lockout_safety_check"}, + }, + }, + }) +} + +func TestAccKMSKeyPolicy_booleanCondition(t *testing.T) { + ctx := acctest.Context(t) + var key kms.KeyMetadata + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + keyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccKeyPolicyConfig_policyBooleanCondition(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckKeyExists(ctx, keyResourceName, &key), + ), + }, + }, + }) +} + +func testAccKeyPolicyConfig_policy(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + policy = jsonencode({ + Id = %[1]q + Statement = [{ + Sid = "Enable IAM User Permissions" + Effect = "Allow" + Principal = { + "AWS" : "*" + } + Action = "kms:*" + Resource = "*" + }] + Version = "2012-10-17" + }) +} +`, rName) +} + +func testAccKeyPolicyConfig_policyBypass(rName string, bypassFlag bool) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} + +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + bypass_policy_lockout_safety_check = %[2]t + + policy = jsonencode({ + Id = %[1]q + Statement = [ + { + Action = [ + "kms:CreateKey", + "kms:DescribeKey", + "kms:ScheduleKeyDeletion", + "kms:Describe*", + "kms:Get*", + "kms:List*", + "kms:TagResource", + "kms:UntagResource", + ] + Effect = "Allow" + Principal = { + AWS = data.aws_caller_identity.current.arn + } + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + ] + Version = "2012-10-17" + }) +} +`, rName, bypassFlag) +} + +func testAccKeyPolicyConfig_policyIAMRole(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_caller_identity" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} + +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + policy = jsonencode({ + Id = %[1]q + Statement = [ + { + Action = "kms:*" + Effect = "Allow" + Principal = { + AWS = data.aws_caller_identity.current.arn + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + { + Action = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + Effect = "Allow" + Principal = { + AWS = aws_iam_role.test.arn + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + ] + Version = "2012-10-17" + }) +} +`, rName) +} + +func testAccKeyPolicyConfig_policyIAMMultiRole(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test1" { + name = "%[1]s-sultan" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role" "test2" { + name = "%[1]s-shepard" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role" "test3" { + name = "%[1]s-tritonal" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role" "test4" { + name = "%[1]s-artlec" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role" "test5" { + name = "%[1]s-cazzette" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + }] + Version = "2012-10-17" + }) +} + +data "aws_iam_policy_document" "test" { + policy_id = %[1]q + statement { + actions = [ + "kms:*", + ] + effect = "Allow" + principals { + identifiers = ["*"] + type = "AWS" + } + resources = ["*"] + } + + statement { + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + effect = "Allow" + principals { + identifiers = [ + aws_iam_role.test2.arn, + aws_iam_role.test1.arn, + aws_iam_role.test4.arn, + aws_iam_role.test3.arn, + aws_iam_role.test5.arn, + ] + type = "AWS" + } + resources = ["*"] + } +} + +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} + +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + policy = data.aws_iam_policy_document.test.json +} +`, rName) +} + +func testAccKeyPolicyConfig_policyIAMServiceLinkedRole(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_caller_identity" "current" {} + +resource "aws_iam_service_linked_role" "test" { + aws_service_name = "autoscaling.${data.aws_partition.current.dns_suffix}" + custom_suffix = %[1]q +} + +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} + +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + policy = jsonencode({ + Id = %[1]q + Statement = [ + { + Action = "kms:*" + Effect = "Allow" + Principal = { + AWS = data.aws_caller_identity.current.arn + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + { + Action = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + Effect = "Allow" + Principal = { + AWS = aws_iam_service_linked_role.test.arn + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + ] + Version = "2012-10-17" + }) +} +`, rName) +} + +func testAccKeyPolicyConfig_policyBooleanCondition(rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +data "aws_partition" "current" {} + +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + policy = jsonencode({ + Id = %[1]q + Statement = [ + { + Action = "kms:*" + Effect = "Allow" + Principal = { + AWS = data.aws_caller_identity.current.arn + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + { + Action = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + ] + Effect = "Allow" + Principal = { + AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root" + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + + Condition = { + Bool = { + "kms:GrantIsForAWSResource" = true + } + } + }, + ] + Version = "2012-10-17" + }) +} +`, rName) +} + +func testAccKeyPolicyConfig_removedPolicy(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} +`, rName) +} + +func testAccKeyPolicyConfig_keyIsEnabled(rName string, isEnabled bool) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 + is_enabled = %[2]t +} +resource "aws_kms_key_policy" "test" { + key_id = aws_kms_key.test.id + policy = jsonencode({ + Id = %[1]q + Statement = [{ + Sid = "Enable IAM User Permissions" + Effect = "Allow" + Principal = { + AWS = data.aws_caller_identity.current.arn + } + Action = "kms:*" + Resource = "*" + }] + Version = "2012-10-17" + }) +} +`, rName, isEnabled) +} diff --git a/internal/service/kms/key_test.go b/internal/service/kms/key_test.go index fe6fbb852652..8887a43422db 100644 --- a/internal/service/kms/key_test.go +++ b/internal/service/kms/key_test.go @@ -635,6 +635,7 @@ resource "aws_kms_key" "test" { func testAccKeyConfig_policy(rName string) string { return fmt.Sprintf(` + resource "aws_kms_key" "test" { description = %[1]q deletion_window_in_days = 7 @@ -645,7 +646,7 @@ resource "aws_kms_key" "test" { Sid = "Enable IAM User Permissions" Effect = "Allow" Principal = { - AWS = "*" + "AWS" : "*" } Action = "kms:*" Resource = "*" @@ -698,6 +699,8 @@ func testAccKeyConfig_policyIAMRole(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + resource "aws_iam_role" "test" { name = %[1]q @@ -724,7 +727,7 @@ resource "aws_kms_key" "test" { Action = "kms:*" Effect = "Allow" Principal = { - AWS = "*" + AWS = data.aws_caller_identity.current.arn } Resource = "*" @@ -882,6 +885,8 @@ func testAccKeyConfig_policyIAMServiceLinkedRole(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + resource "aws_iam_service_linked_role" "test" { aws_service_name = "autoscaling.${data.aws_partition.current.dns_suffix}" custom_suffix = %[1]q @@ -898,7 +903,7 @@ resource "aws_kms_key" "test" { Action = "kms:*" Effect = "Allow" Principal = { - AWS = "*" + AWS = data.aws_caller_identity.current.arn } Resource = "*" @@ -944,7 +949,7 @@ resource "aws_kms_key" "test" { Action = "kms:*" Effect = "Allow" Principal = { - AWS = "*" + AWS = data.aws_caller_identity.current.arn } Resource = "*" diff --git a/internal/service/kms/service_package_gen.go b/internal/service/kms/service_package_gen.go index c26457c2436f..dfa2ae575c8b 100644 --- a/internal/service/kms/service_package_gen.go +++ b/internal/service/kms/service_package_gen.go @@ -78,6 +78,10 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceKey, TypeName: "aws_kms_key", }, + { + Factory: ResourceKeyPolicy, + TypeName: "aws_kms_key_policy", + }, { Factory: ResourceReplicaExternalKey, TypeName: "aws_kms_replica_external_key", diff --git a/website/docs/r/kms_key.html.markdown b/website/docs/r/kms_key.html.markdown index 574fe74e9cc4..b3be0db6ea33 100644 --- a/website/docs/r/kms_key.html.markdown +++ b/website/docs/r/kms_key.html.markdown @@ -10,6 +10,9 @@ description: |- Manages a single-Region or multi-Region primary KMS key. +~> **NOTE on KMS Key Policy:** KMS Key Policy can be configured in either the standalone resource [`aws_kms_key_policy`](kms_key_policy.html) +or with the parameter `policy` in this resource. +Configuring with both will cause inconsistencies and may overwrite configuration. ## Example Usage ```terraform diff --git a/website/docs/r/kms_key_policy.html.markdown b/website/docs/r/kms_key_policy.html.markdown new file mode 100644 index 000000000000..823277720511 --- /dev/null +++ b/website/docs/r/kms_key_policy.html.markdown @@ -0,0 +1,64 @@ +--- +subcategory: "KMS (Key Management)" +layout: "aws" +page_title: "AWS: aws_kms_key_policy" +description: |- + Attaches a policy to a KMS Key. +--- + +# Resource: aws_kms_key_policy + +Attaches a policy to a KMS Key. + +## Example Usage + +```terraform +resource "aws_kms_key" "example" { + description = "example" +} + +resource "aws_kms_key_policy" "example" { + key_id = aws_kms_key.example.id + policy = jsonencode({ + Id = "example" + Statement = [ + { + Action = "kms:*" + Effect = "Allow" + Principal = { + AWS = "*" + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + ] + Version = "2012-10-17" + }) +} +``` + +## Argument Reference + +The following arguments are supported: + +* `key_id` - (Required) The ID of the KMS Key to attach the policy. +* `policy` - (Required) A valid policy JSON document. Although this is a key policy, not an IAM policy, an [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document), in the form that designates a principal, can be used. For more information about building policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://learn.hashicorp.com/terraform/aws/iam-policy). + +~> **NOTE:** Note: All KMS keys must have a key policy. If a key policy is not specified, or this resource is destroyed, AWS gives the KMS key a [default key policy](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default) that gives all principals in the owning account unlimited access to all KMS operations for the key. This default key policy effectively delegates all access control to IAM policies and KMS grants. + +* `bypass_policy_lockout_safety_check` - (Optional) A flag to indicate whether to bypass the key policy lockout safety check. +Setting this value to true increases the risk that the KMS key becomes unmanageable. Do not set this value to true indiscriminately. If this value is set, and the resource is destroyed, a warning will be shown, and the resource will be removed from state. +For more information, refer to the scenario in the [Default Key Policy](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-root-enable-iam) section in the _AWS Key Management Service Developer Guide_. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +KMS Key Policies can be imported using the `key_id`, e.g., + +``` +$ terraform import aws_kms_key_policy.a 1234abcd-12ab-34cd-56ef-1234567890ab +```