Skip to main content

Fix Terraform Error: KMS Key AccessDeniedException

Key Takeaway

Fix terraform KMS AccessDeniedException errors. Update KMS key policies, add IAM permissions for kms:CreateGrant and kms:Decrypt, and handle cross-account KMS access.

Table of Contents

Quick Answer

{
  "Sid": "Allow Terraform to use the key",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::123456789012:role/terraform" },
  "Action": ["kms:CreateGrant", "kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*"],
  "Resource": "*"
}

The Error

Error: creating EC2 Instance: AccessDeniedException:
User: arn:aws:iam::123456789012:role/terraform is not authorized to
perform: kms:CreateGrant on resource: arn:aws:kms:us-east-1:123456789012:key/mrk-abc123

Or:

Error: creating RDS Cluster: AccessDenied:
The cross-account KMS key access is not allowed

What Causes This

KMS access requires both an IAM policy AND a key policy to allow access. Missing either one blocks the operation.

  1. KMS key policy doesn’t allow the IAM role — the key needs explicit permission
  2. IAM role lacks kms: permissions — even with key policy, IAM must also allow
  3. Cross-account KMS — extra grants needed for keys in different accounts
  4. Service-linked role needs CreateGrant — EBS, RDS, and EFS use kms:CreateGrant internally

Solution 1: Update KMS Key Policy

resource "aws_kms_key" "main" {
  description = "Encryption key for application"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "Root account full access"
        Effect    = "Allow"
        Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
        Action    = "kms:*"
        Resource  = "*"
      },
      {
        Sid       = "Terraform role access"
        Effect    = "Allow"
        Principal = { AWS = var.terraform_role_arn }
        Action = [
          "kms:CreateGrant",
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:Encrypt",
          "kms:GenerateDataKey*",
          "kms:ReEncrypt*"
        ]
        Resource = "*"
      }
    ]
  })
}

Solution 2: Add IAM Permissions

resource "aws_iam_role_policy" "kms_access" {
  name = "kms-access"
  role = aws_iam_role.terraform.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "kms:CreateGrant",
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:Encrypt",
          "kms:GenerateDataKey*",
          "kms:ReEncrypt*",
          "kms:ListGrants",
          "kms:RevokeGrant"
        ]
        Resource = [
          aws_kms_key.main.arn,
          # Or specific key ARN:
          # "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"
        ]
      }
    ]
  })
}

Which Services Need Which KMS Permissions

ServiceRequired KMS Actions
EBS volumesCreateGrant, Decrypt, DescribeKey, GenerateDataKey*
RDS / AuroraCreateGrant, Decrypt, DescribeKey, GenerateDataKey*
S3 SSE-KMSDecrypt, GenerateDataKey*, DescribeKey
Secrets ManagerDecrypt, DescribeKey, GenerateDataKey*
EFSCreateGrant, Decrypt, DescribeKey, GenerateDataKey*
Lambda env varsDecrypt, DescribeKey

Solution 3: Cross-Account KMS Access

# Account A (key owner) — key policy
{
  Sid       = "Allow Account B"
  Effect    = "Allow"
  Principal = { AWS = "arn:aws:iam::222222222222:root" }
  Action = [
    "kms:CreateGrant",
    "kms:Decrypt",
    "kms:DescribeKey",
    "kms:GenerateDataKey*"
  ]
  Resource  = "*"
}

# Account B (consumer) — IAM policy must ALSO allow
resource "aws_iam_role_policy" "cross_account_kms" {
  policy = jsonencode({
    Statement = [{
      Effect   = "Allow"
      Action   = ["kms:CreateGrant", "kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*"]
      Resource = "arn:aws:kms:us-east-1:111111111111:key/mrk-abc123"
    }]
  })
}

Debugging

# Check who you're running as
aws sts get-caller-identity

# Simulate the KMS permission
aws kms describe-key --key-id arn:aws:kms:us-east-1:123456789012:key/mrk-abc123

# Check key policy
aws kms get-key-policy --key-id mrk-abc123 --policy-name default

# List grants
aws kms list-grants --key-id mrk-abc123

Hands-On Courses

Conclusion

KMS access requires both a KMS key policy AND IAM policy. Add kms:CreateGrant, kms:Decrypt, kms:GenerateDataKey*, and kms:DescribeKey to both. For cross-account access, both the key policy and the consuming account’s IAM policy must explicitly grant access.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.