Initializing Enclave...

How to Fix Missing kms:GenerateDataKey Permission for Writing KMS-Encrypted Objects in AWS

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

TL;DR

  • What broke: The IAM principal (role/user) writing to a KMS-encrypted resource lacks kms:GenerateDataKey, so AWS cannot generate the envelope encryption key — every PutObject, CopyObject, or volume write fails with AccessDeniedException.
  • How to fix it: Add kms:GenerateDataKey (and kms:Decrypt for read-back) to either the IAM identity policy or the KMS key policy for the specific key ARN.
  • Use our Client-Side Sandbox above to drop your failing IAM or KMS policy and auto-generate the corrected, least-privilege policy.

The Incident (What Does the Error Mean?)

You will see one of these in CloudTrail or your application logs:

An error occurred (AccessDeniedException) when calling the PutObject operation:
User: arn:aws:sts::123456789012:assumed-role/my-app-role/session
is not authorized to perform: kms:GenerateDataKey
on resource: arn:aws:kms:us-east-1:123456789012:key/mrk-abc123

or from the AWS SDK:

AccessDeniedException: User is not authorized to perform kms:GenerateDataKey
Status Code: 400
Request ID: a1b2c3d4-...

Immediate consequence: Every write to any SSE-KMS encrypted S3 bucket, EBS volume, RDS instance, or Kinesis stream tied to this key is dead. Uploads return 403. ETL pipelines stall. Backup jobs silently fail if error handling is weak.


The Attack Vector / Blast Radius

This is not a hacker exploit — this is a misconfigured least-privilege rollout that kills availability. The blast radius depends on what the principal does:

Principal Type Blast Radius
Application service role All writes to encrypted S3 buckets — API returns 403, users see failures
CI/CD pipeline role Artifact uploads fail, deployments halt
Lambda execution role Function errors on every invocation that writes encrypted data
Cross-account role Entire cross-account data ingestion pipeline down

Why it's subtle and dangerous: kms:GenerateDataKey is required for the write path only. A principal with only kms:Decrypt can read existing objects but cannot write new ones. Teams often discover this in production after rotating to a new CMK or tightening an existing policy — reads keep working, masking the breakage until the first write attempt.

If you over-correct by granting kms:* to fix this fast, you've now given the principal kms:DisableKey, kms:ScheduleKeyDeletion, and kms:PutKeyPolicy — a catastrophic over-privilege that an attacker with access to that role can leverage to destroy your encryption keys.


How to Fix It (The Solution)

Basic Fix — IAM Identity Policy

Add kms:GenerateDataKey to the IAM policy attached to the role/user. Always pair it with kms:Decrypt so the same principal can read back what it writes.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3KMSAccess",
      "Effect": "Allow",
      "Action": [
-       "kms:Decrypt"
+       "kms:Decrypt",
+       "kms:GenerateDataKey"
      ],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"
    }
  ]
}

Basic Fix — KMS Key Policy (if the key policy is the blocker)

If the IAM policy looks correct but the KMS key policy doesn't explicitly allow the principal, the key policy wins.

{
  "Sid": "AllowAppRoleEncryptDecrypt",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/my-app-role"
  },
  "Action": [
-   "kms:Decrypt",
-   "kms:DescribeKey"
+   "kms:Decrypt",
+   "kms:DescribeKey",
+   "kms:GenerateDataKey"
  ],
  "Resource": "*"
}

Enterprise Best Practice — Least Privilege with Condition Keys

Scope kms:GenerateDataKey to specific encryption contexts and calling services. This prevents the key from being used outside its intended workload even if the role is compromised.

{
  "Sid": "AllowEncryptedS3WritesScoped",
  "Effect": "Allow",
  "Action": [
-   "kms:Decrypt"
+   "kms:Decrypt",
+   "kms:GenerateDataKey"
  ],
  "Resource": "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123",
+ "Condition": {
+   "StringEquals": {
+     "kms:ViaService": "s3.us-east-1.amazonaws.com",
+     "kms:CallerAccount": "123456789012"
+   },
+   "StringLike": {
+     "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::my-secure-bucket/*"
+   }
+ }
}

Why this matters: kms:ViaService ensures this key can only be used when the call originates from S3 in your region — not from an attacker using the AWS CLI directly with a stolen credential.


💡 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 — catch missing GenerateDataKey before deployment:

Checkov rule CKV_AWS_7 covers KMS key rotation but doesn't catch this specific gap. Write a custom check or use cfn-python-lint with a custom rule:

# .checkov/custom_checks/kms_generate_data_key.py
# Flag any IAM policy that grants s3:PutObject on an encrypted bucket
# without a corresponding kms:GenerateDataKey in the same statement block

2. OPA/Rego policy for Terraform plan validation:

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_iam_policy"
  actions := resource.change.after.policy.Statement[_].Action
  "s3:PutObject" == actions[_]
  not contains_generate_data_key(actions)
  msg := sprintf("IAM policy '%v' allows s3:PutObject but is missing kms:GenerateDataKey", [resource.address])
}

contains_generate_data_key(actions) {
  actions[_] == "kms:GenerateDataKey"
}

3. AWS IAM Access Analyzer: Enable it in every account. It will flag policies that allow S3 write actions without corresponding KMS permissions when the bucket has SSE-KMS enforced via bucket policy (aws:SecureTransport + s3:x-amz-server-side-encryption).

4. Enforce SSE-KMS + deny unencrypted puts at the bucket policy level — this makes the kms:GenerateDataKey gap surface immediately in dev/staging rather than silently in prod:

{
  "Sid": "DenyUnencryptedObjectUploads",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:PutObject",
  "Resource": "arn:aws:s3:::my-secure-bucket/*",
  "Condition": {
    "StringNotEquals": {
      "s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"
    }
  }
}

This forces every writer to use the specific CMK — and if they're missing kms:GenerateDataKey, they'll fail fast in your pipeline, not in production.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →