Initializing Enclave...

Fixing AWS KMS AccessDenied on CreateGrant: Resolving Grantee Principal Permission Errors

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10–20 mins

TL;DR

  • What broke: The IAM principal calling CreateGrant is denied because either the KMS key policy does not grant kms:CreateGrant to that caller, the required kms:GrantIsForAWSResource condition is absent, or the grantee principal ARN is malformed/cross-account without explicit trust.
  • How to fix it: Add kms:CreateGrant to the key policy for the calling principal; attach kms:GrantIsForAWSResource condition when the grant is for an AWS service; validate the grantee principal ARN resolves to an existing IAM entity.
  • Fast path: Use our Client-Side Sandbox above to paste your key policy and get the refactored statement auto-generated without sending your ARNs to a third-party server.

The Incident (What Does the Error Mean?)

Raw error output:

An error occurred (AccessDenied) when calling the CreateGrant operation:
  User: arn:aws:iam::123456789012:role/MyServiceRole is not authorized
  to perform: kms:CreateGrant on resource:
  arn:aws:kms:us-east-1:123456789012:key/mrk-abc123def456
  with an explicit deny

Immediate consequence: The service role — typically an ECS task role, Lambda execution role, or EC2 instance profile — cannot delegate KMS key usage to downstream AWS services (e.g., RDS, S3 SSE-KMS, Secrets Manager). Encryption operations downstream fail silently or hard-crash depending on the service. In RDS or EBS contexts, this blocks volume attachment or database startup entirely. Production is down.


The Attack Vector / Blast Radius

kms:CreateGrant is a privileged escalation vector. A grant allows a grantee principal to use a KMS key for specific operations (Decrypt, GenerateDataKey, Encrypt) without being named in the key policy. This is why AWS enforces it tightly.

Why misconfiguration here is dangerous in both directions:

  • Too permissive: If you add kms:CreateGrant without the kms:GrantIsForAWSResource condition, any principal with that permission can issue grants to arbitrary external principals, including attacker-controlled IAM roles in other accounts. This is a privilege escalation path — an attacker who compromises your service role can grant your KMS key to their own infrastructure.
  • Too restrictive (current failure): The legitimate AWS service (e.g., rds.amazonaws.com) cannot receive a grant, so it cannot perform envelope encryption. Cascading failures: encrypted RDS instances won't start, EBS volumes won't attach, Secrets Manager rotations fail.

Blast radius: Every resource encrypted under this KMS key that relies on service-linked grant delegation is offline until resolved.


How to Fix It (The Solution)

Root Cause Checklist — Check in Order

  1. Key policy missing kms:CreateGrant for the calling role — most common.
  2. kms:GrantIsForAWSResource condition missing — AWS services require this condition to be present before they accept grants.
  3. Grantee principal ARN does not exist or is malformed — KMS validates the principal at grant creation time.
  4. Cross-account grant without resource-based trust — the key policy in the owning account must explicitly allow the external account's principal.

Basic Fix — Key Policy Statement

{
  "Version": "2012-10-17",
  "Statement": [
-   {
-     "Sid": "AllowServiceRoleKeyUsage",
-     "Effect": "Allow",
-     "Principal": {
-       "AWS": "arn:aws:iam::123456789012:role/MyServiceRole"
-     },
-     "Action": [
-       "kms:Encrypt",
-       "kms:Decrypt",
-       "kms:GenerateDataKey"
-     ],
-     "Resource": "*"
-   }
+   {
+     "Sid": "AllowServiceRoleKeyUsageAndGrant",
+     "Effect": "Allow",
+     "Principal": {
+       "AWS": "arn:aws:iam::123456789012:role/MyServiceRole"
+     },
+     "Action": [
+       "kms:Encrypt",
+       "kms:Decrypt",
+       "kms:GenerateDataKey",
+       "kms:CreateGrant",
+       "kms:ListGrants",
+       "kms:RevokeGrant"
+     ],
+     "Resource": "*",
+     "Condition": {
+       "Bool": {
+         "kms:GrantIsForAWSResource": "true"
+       }
+     }
+   }
  ]
}

kms:GrantIsForAWSResource: true is non-negotiable. Without it, you are allowing unrestricted grant issuance.


Enterprise Best Practice — Least Privilege with Principal and Operation Constraints

{
  "Sid": "AllowServiceRoleGrant",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/MyServiceRole"
  },
- "Action": "kms:CreateGrant",
- "Resource": "*"
+ "Action": [
+   "kms:CreateGrant",
+   "kms:ListGrants",
+   "kms:RevokeGrant"
+ ],
+ "Resource": "*",
+ "Condition": {
+   "Bool": {
+     "kms:GrantIsForAWSResource": "true"
+   },
+   "StringEquals": {
+     "kms:ViaService": "rds.us-east-1.amazonaws.com"
+   },
+   "ArnLike": {
+     "aws:PrincipalArn": "arn:aws:iam::123456789012:role/MyServiceRole"
+   }
+ }
}

kms:ViaService locks grant creation to a specific AWS service. If your key is shared across services, use a StringLike with a wildcard: "rds.*.amazonaws.com". This prevents the service role from being used to issue grants outside its intended service boundary.


💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Checkov — Block Key Policies Missing kms:GrantIsForAWSResource

Checkov rule CKV_AWS_7 checks KMS key rotation but does not natively check grant conditions. Write a custom check or use OPA:

# OPA/Conftest policy — deny CreateGrant without GrantIsForAWSResource condition
deny[msg] {
  stmt := input.Statement[_]
  stmt.Action[_] == "kms:CreateGrant"
  not stmt.Condition.Bool["kms:GrantIsForAWSResource"]
  msg := sprintf(
    "KMS key policy statement '%v' allows kms:CreateGrant without kms:GrantIsForAWSResource condition.",
    [stmt.Sid]
  )
}

2. Terraform — Enforce Condition in aws_kms_key Policy

# In your aws_kms_key policy document, always include:
condition {
  test     = "Bool"
  variable = "kms:GrantIsForAWSResource"
  values   = ["true"]
}

Run terraform validate + checkov -d . in your PR pipeline. Block merge on any KMS policy statement containing kms:CreateGrant without the condition.

3. AWS Config Rule

Enable kms-cmk-not-scheduled-for-deletion and write a custom AWS Config rule using boto3 to evaluate key policies on change events (PutKeyPolicy) and alert if CreateGrant is present without kms:GrantIsForAWSResource.

4. IAM Access Analyzer

Enable IAM Access Analyzer with KMS key findings. It will flag any key policy that allows kms:CreateGrant to external principals or without restrictive conditions as a HIGH finding automatically.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →