Skip to main content

IAM Policy No Full Access to KMS

Overview

This check identifies custom IAM policies that grant full access to AWS Key Management Service (KMS) using the kms:* action. Policies should follow the principle of least privilege by granting only the specific KMS actions needed, scoped to specific keys.

Risk

Granting kms:* permissions creates serious security risks:

  • Data exposure: Anyone with the policy can decrypt sensitive data protected by any KMS key
  • Key tampering: Attackers can modify key policies or disable keys
  • Data loss: Keys can be scheduled for deletion, making encrypted data permanently unreadable
  • Privilege escalation: Broad KMS access can help attackers move laterally through your environment

Remediation Steps

Prerequisites

You need permission to view and edit IAM policies in your AWS account. This typically requires IAM administrator access or a role with iam:GetPolicy, iam:GetPolicyVersion, and iam:CreatePolicyVersion permissions.

AWS Console Method

  1. Open the IAM console in us-east-1
  2. Click Policies in the left navigation
  3. Find the policy flagged by Prowler (you can search by name)
  4. Click the policy name to open it
  5. Select the Permissions tab, then click Edit
  6. Find any statement with "Action": "kms:*" and replace it with specific actions like:
    • kms:Encrypt
    • kms:Decrypt
    • kms:GenerateDataKey
    • kms:DescribeKey
  7. Update the Resource field from "*" to specific KMS key ARNs:
    arn:aws:kms:us-east-1:123456789012:key/your-key-id
  8. Click Next, review your changes, then click Save changes
AWS CLI (optional)

Step 1: Identify the problematic policy

List your customer-managed policies:

aws iam list-policies \
--scope Local \
--region us-east-1 \
--query 'Policies[*].[PolicyName,Arn]' \
--output table

Step 2: View the current policy document

Get the policy version and document:

# Get the default version ID
aws iam get-policy \
--policy-arn arn:aws:iam::123456789012:policy/YourPolicyName \
--region us-east-1 \
--query 'Policy.DefaultVersionId' \
--output text

# Get the policy document (replace v1 with your version)
aws iam get-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/YourPolicyName \
--version-id v1 \
--region us-east-1 \
--query 'PolicyVersion.Document' \
--output json

Step 3: Create a new policy version with least privilege

Create a JSON file with scoped permissions:

cat > /tmp/kms-least-privilege-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificKMSActions",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:GenerateDataKeyWithoutPlaintext",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/your-key-id",
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.us-east-1.amazonaws.com"
}
}
}
]
}
EOF

Apply the updated policy:

aws iam create-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/YourPolicyName \
--policy-document file:///tmp/kms-least-privilege-policy.json \
--set-as-default \
--region us-east-1

Common KMS actions by use case

Use CaseRequired Actions
Encrypt datakms:Encrypt, kms:GenerateDataKey
Decrypt datakms:Decrypt
S3 server-side encryptionkms:Encrypt, kms:Decrypt, kms:GenerateDataKey
Secrets Managerkms:Decrypt, kms:GenerateDataKey
View key info onlykms:DescribeKey, kms:GetKeyPolicy
CloudFormation (optional)

Replace any existing policy with this least-privilege template:

AWSTemplateFormatVersion: '2010-09-09'
Description: IAM policy with least privilege KMS permissions

Parameters:
KMSKeyArn:
Type: String
Description: ARN of the KMS key to grant access to
AllowedPattern: "arn:aws:kms:[a-z0-9-]+:[0-9]+:key/[a-f0-9-]+"

Resources:
LeastPrivilegeKMSPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: LeastPrivilegeKMSAccess
Description: Policy with scoped KMS permissions following least privilege
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowSpecificKMSActions
Effect: Allow
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey
- kms:GenerateDataKeyWithoutPlaintext
- kms:DescribeKey
Resource: !Ref KMSKeyArn
Condition:
StringEquals:
kms:ViaService: !Sub 's3.${AWS::Region}.amazonaws.com'

Outputs:
PolicyArn:
Description: ARN of the created IAM policy
Value: !Ref LeastPrivilegeKMSPolicy

Deploy with:

aws cloudformation deploy \
--template-file template.yaml \
--stack-name least-privilege-kms-policy \
--parameter-overrides KMSKeyArn=arn:aws:kms:us-east-1:123456789012:key/your-key-id \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Terraform (optional)
variable "kms_key_arn" {
description = "ARN of the KMS key to grant access to"
type = string
}

variable "policy_name" {
description = "Name for the IAM policy"
type = string
default = "LeastPrivilegeKMSAccess"
}

variable "via_service" {
description = "AWS service that must be used to access the key (e.g., s3.us-east-1.amazonaws.com)"
type = string
default = "s3.us-east-1.amazonaws.com"
}

resource "aws_iam_policy" "least_privilege_kms" {
name = var.policy_name
description = "Policy with scoped KMS permissions following least privilege"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowSpecificKMSActions"
Effect = "Allow"
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:GenerateDataKeyWithoutPlaintext",
"kms:DescribeKey"
]
Resource = var.kms_key_arn
Condition = {
StringEquals = {
"kms:ViaService" = var.via_service
}
}
}
]
})
}

output "policy_arn" {
description = "ARN of the created IAM policy"
value = aws_iam_policy.least_privilege_kms.arn
}

Apply with:

terraform apply -var="kms_key_arn=arn:aws:kms:us-east-1:123456789012:key/your-key-id"

Verification

After making changes, verify the fix:

  1. Return to the IAM console and open your updated policy
  2. Review the JSON to confirm no kms:* actions remain
  3. Re-run the Prowler check to confirm it passes:
prowler aws --check iam_policy_no_full_access_to_kms --region us-east-1
Advanced verification with AWS CLI

Check for any remaining kms:* statements:

# Get all customer-managed policies
for arn in $(aws iam list-policies --scope Local --query 'Policies[*].Arn' --output text --region us-east-1); do
version=$(aws iam get-policy --policy-arn "$arn" --query 'Policy.DefaultVersionId' --output text --region us-east-1)
doc=$(aws iam get-policy-version --policy-arn "$arn" --version-id "$version" --query 'PolicyVersion.Document' --output json --region us-east-1)
if echo "$doc" | grep -q '"kms:\*"'; then
echo "FOUND kms:* in policy: $arn"
fi
done

Additional Resources

Notes

  • Test before applying: Changes to IAM policies take effect immediately. Test in a non-production environment first.
  • Policy version limits: IAM policies can have at most 5 versions. Delete old versions if you hit this limit.
  • Identify dependent resources: Before modifying a policy, use aws iam list-entities-for-policy to see which users, groups, and roles depend on it.
  • Consider conditions: The kms:ViaService condition shown in examples restricts key usage to specific AWS services. Adjust or remove based on your needs.
  • Audit existing access: Use AWS CloudTrail to see what KMS actions are actually being used before narrowing permissions.