Identity & SecurityIntermediate18 min read

    AWS KMS Security Best Practices

    Tarek Cheikh

    Founder & AWS Security Expert

    View Security Card

    AWS Key Management Service (KMS) is the cryptographic backbone of your entire AWS environment. Every encrypted S3 object, every encrypted EBS volume, every encrypted RDS database, every secret in Secrets Manager -- they all depend on KMS keys. If your KMS keys are misconfigured, an attacker can decrypt your most sensitive data, or worse, render it permanently inaccessible by scheduling key deletion.

    In January 2025, the Codefinger ransomware campaign demonstrated a devastating new attack vector: threat actors used compromised AWS credentials to encrypt S3 objects with SSE-C (Server-Side Encryption with Customer-Provided Keys), then demanded ransom for the AES-256 keys required to decrypt the data. Because AWS does not store SSE-C key material, recovery without the attacker's key was impossible. Victims were threatened with permanent data deletion unless ransom was paid within seven days. This was the first known instance of attackers weaponizing AWS's native encryption infrastructure against its own customers -- and it underscored that encryption key management is not just a compliance checkbox, it is a critical defense against data loss, extortion, and regulatory penalties.

    This guide covers 12 battle-tested KMS best practices, each with real AWS CLI commands, audit procedures, and the latest 2024-2026 updates from AWS.

    1. Enforce Least-Privilege Key Policies

    KMS key policies are the primary access control mechanism for KMS keys. Unlike most AWS resources, KMS keys require explicit key policy permissions -- IAM policies alone are insufficient unless the key policy grants access to the account root principal. Overly permissive key policies are the most common KMS misconfiguration and are flagged by multiple Security Hub controls.

    Implementation

    • Use explicit Deny statements to block dangerous actions like kms:ScheduleKeyDeletion and kms:DisableKey for all principals except a dedicated security break-glass role.
    • Grant only the minimum required actions. Separate kms:Encrypt from kms:Decrypt -- an application that writes encrypted data may not need to decrypt it.
    • Never use "Principal": "*" in key policies without tight conditions. A wildcard principal makes the key accessible to any AWS account.
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowKeyAdministration",
          "Effect": "Allow",
          "Principal": {"AWS": "arn:aws:iam::123456789012:role/KMSAdminRole"},
          "Action": [
            "kms:Create*",
            "kms:Describe*",
            "kms:Enable*",
            "kms:List*",
            "kms:Put*",
            "kms:Update*",
            "kms:Revoke*",
            "kms:Get*",
            "kms:TagResource",
            "kms:UntagResource"
          ],
          "Resource": "*"
        },
        {
          "Sid": "AllowKeyUsage",
          "Effect": "Allow",
          "Principal": {"AWS": "arn:aws:iam::123456789012:role/AppEncryptionRole"},
          "Action": [
            "kms:Encrypt",
            "kms:Decrypt",
            "kms:ReEncrypt*",
            "kms:GenerateDataKey*",
            "kms:DescribeKey"
          ],
          "Resource": "*"
        },
        {
          "Sid": "DenyDeletionExceptSecurityTeam",
          "Effect": "Deny",
          "Principal": "*",
          "Action": [
            "kms:ScheduleKeyDeletion",
            "kms:DisableKey"
          ],
          "Resource": "*",
          "Condition": {
            "ArnNotEquals": {
              "aws:PrincipalArn": "arn:aws:iam::123456789012:role/SecurityBreakGlass"
            }
          }
        }
      ]
    }
    # Audit: Get the key policy and review principal assignments
    aws kms get-key-policy --key-id KEY_ID --policy-name default --output text
    
    # Check Security Hub for KMS.1 and KMS.2 findings
    aws securityhub get-findings   --filters '{"GeneratorId":[{"Value":"aws-foundational-security-best-practices/v/1.0.0/KMS.1","Comparison":"PREFIX"}]}'   --query "Findings[].{Title:Title,Severity:Severity.Label,Resource:Resources[0].Id}"

    Security Hub: Controls [KMS.1] and [KMS.2] flag IAM policies (managed and inline) that allow decryption actions on all KMS keys using "Resource": "*". Scope decrypt permissions to specific key ARNs whenever possible.

    2. Enforce Separation of Duties

    The principal who administers a KMS key should never be the same principal who uses it for cryptographic operations. This separation prevents a single compromised role from both managing and exploiting encryption keys -- a compromised application role should not be able to delete the key that protects its own data.

    Implementation

    • Key administrators can create, disable, rotate, and manage key policies, but cannot encrypt or decrypt data.
    • Key users can encrypt and decrypt data, but cannot modify key policies, disable keys, or schedule deletion.
    • Key deletion should require a separate break-glass role with MFA and approval workflows.
    # Create separate IAM roles for administration and usage
    # KMS Admin Role - management plane only
    aws iam create-role   --role-name KMSAdminRole   --assume-role-policy-document file://kms-admin-trust.json
    
    aws iam put-role-policy   --role-name KMSAdminRole   --policy-name KMSAdminPolicy   --policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
          "Effect": "Allow",
          "Action": [
            "kms:CreateKey", "kms:DescribeKey", "kms:EnableKeyRotation",
            "kms:GetKeyPolicy", "kms:GetKeyRotationStatus", "kms:ListKeys",
            "kms:PutKeyPolicy", "kms:TagResource", "kms:UpdateKeyDescription"
          ],
          "Resource": "*"
        }]
      }'
    
    # KMS User Role - data plane only
    aws iam create-role   --role-name KMSUserRole   --assume-role-policy-document file://kms-user-trust.json
    
    aws iam put-role-policy   --role-name KMSUserRole   --policy-name KMSUserPolicy   --policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
          "Effect": "Allow",
          "Action": [
            "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey",
            "kms:GenerateDataKeyWithoutPlaintext", "kms:ReEncrypt*", "kms:DescribeKey"
          ],
          "Resource": "arn:aws:kms:us-east-1:123456789012:key/*"
        }]
      }'
    
    # Audit: List all grants on a KMS key to verify separation
    aws kms list-grants --key-id KEY_ID

    Best practice: Use IAM roles rather than IAM users as key policy principals. Require MFA to assume the KMS admin role. Document the separation of duties matrix and review it quarterly.

    3. Enable Automatic Key Rotation

    Key rotation limits the blast radius of a key compromise. If key material is exposed, only data encrypted with the compromised material is at risk. AWS KMS automatic rotation creates new cryptographic material while retaining old material for decryption of previously encrypted data -- zero downtime, zero re-encryption required.

    2024 Update: Custom Rotation Periods

    Since April 2024, AWS KMS supports custom rotation periods between 90 and 2,560 days (approximately 7 years). The default remains 365 days. You can also trigger on-demand rotation at any time and view the complete rotation history of any key.

    # Enable automatic rotation with a 180-day period
    aws kms enable-key-rotation   --key-id arn:aws:kms:us-east-1:123456789012:key/KEY_ID   --rotation-period-in-days 180
    
    # Verify rotation status
    aws kms get-key-rotation-status --key-id KEY_ID
    
    # Trigger on-demand rotation (April 2024+)
    aws kms rotate-key-on-demand --key-id KEY_ID
    
    # View rotation history
    aws kms list-key-rotations --key-id KEY_ID
    
    # Audit: Find all customer-managed keys without rotation enabled
    for key in $(aws kms list-keys --query "Keys[].KeyId" --output text); do
      manager=$(aws kms describe-key --key-id "$key" --query "KeyMetadata.KeyManager" --output text 2>/dev/null)
      if [ "$manager" = "CUSTOMER" ]; then
        status=$(aws kms get-key-rotation-status --key-id "$key" --query "KeyRotationEnabled" --output text 2>/dev/null)
        if [ "$status" = "False" ]; then
          echo "ROTATION DISABLED: $key"
        fi
      fi
    done

    CIS Benchmark: Control 3.8 (ensure rotation for customer-managed KMS keys is enabled). Security Hub: Control [KMS.4] flags customer-managed keys without automatic rotation enabled.

    Limitation: Automatic rotation is supported only for symmetric encryption keys with KMS-generated key material. Asymmetric keys, HMAC keys, and keys with imported key material must be rotated manually by creating a new key and updating aliases.

    Pricing: The first and second rotations add $1/month (prorated hourly) to key cost. All rotations after the second are free, capping the per-key cost at $3/month regardless of rotation frequency.

    4. Protect Against Accidental Key Deletion

    Deleting a KMS key is irreversible. Once the waiting period expires and the key is deleted, all data encrypted with that key becomes permanently unrecoverable. This is the encryption equivalent of dropping a production database with no backup.

    Implementation

    • Set the maximum waiting period (30 days) for all key deletion requests. The minimum is 7 days -- never use this in production.
    • Create CloudWatch alarms for ScheduleKeyDeletion and DisableKey API calls via CloudTrail.
    • Use SCPs to deny kms:ScheduleKeyDeletion across your organization, except for a dedicated security OU.
    # Create a CloudWatch metric filter for key deletion events
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name KMSKeyDeletionAttempts   --filter-pattern '{ ($.eventName = DisableKey) || ($.eventName = ScheduleKeyDeletion) }'   --metric-transformations     metricName=KMSKeyDeletionCount,metricNamespace=CloudTrailMetrics,metricValue=1
    
    # Create alarm for deletion attempts
    aws cloudwatch put-metric-alarm   --alarm-name KMSKeyDeletionAlarm   --metric-name KMSKeyDeletionCount   --namespace CloudTrailMetrics   --statistic Sum   --period 300   --threshold 1   --comparison-operator GreaterThanOrEqualToThreshold   --evaluation-periods 1   --alarm-actions arn:aws:sns:us-east-1:123456789012:SecurityAlerts
    
    # Cancel a pending key deletion
    aws kms cancel-key-deletion --key-id KEY_ID
    aws kms enable-key --key-id KEY_ID

    CIS Benchmark: Control 3.7 (ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer-managed KMS keys). Security Hub: Control [KMS.3] verifies that KMS keys are not scheduled for unintentional deletion.

    5. Use Encryption Context for Additional Authentication

    Encryption context is a set of key-value pairs included in cryptographic operations. It serves as Additional Authenticated Data (AAD) -- it is not encrypted, but it is cryptographically bound to the ciphertext. You must provide the same encryption context to decrypt that was used to encrypt. This provides both an integrity check and a powerful audit trail.

    Implementation

    # Encrypt with encryption context
    aws kms encrypt   --key-id KEY_ID   --plaintext fileb://secret.txt   --encryption-context "Department=Finance,Project=Payroll"   --output text --query CiphertextBlob > encrypted.b64
    
    # Decrypt -- must provide the SAME encryption context
    aws kms decrypt   --ciphertext-blob fileb://encrypted.b64   --encryption-context "Department=Finance,Project=Payroll"
    
    # Decryption FAILS if context does not match -- provides tamper detection

    Policy Enforcement

    Require encryption context in key policies using the kms:EncryptionContext condition key:

    {
      "Sid": "RequireEncryptionContext",
      "Effect": "Deny",
      "Principal": "*",
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:GenerateDataKey*"
      ],
      "Resource": "*",
      "Condition": {
        "Null": {
          "kms:EncryptionContextKeys": "true"
        }
      }
    }

    Audit benefit: Encryption context values are logged in CloudTrail in plaintext. You can search CloudTrail for all decryption operations for a specific department, customer, or project without accessing the encrypted data itself. This is invaluable for forensic analysis and compliance audits.

    6. Implement Envelope Encryption with Data Key Caching

    Envelope encryption is the standard pattern for encrypting data larger than 4 KB (the KMS API limit). KMS generates a data key, you use the plaintext data key to encrypt your data locally, then store the encrypted data key alongside the ciphertext. Only the encrypted data key needs to be sent to KMS for decryption.

    Why Envelope Encryption Matters

    • Performance: Local AES encryption is orders of magnitude faster than API calls to KMS. Avoids KMS API throttling limits (shared quota of thousands of requests per second per account per Region).
    • Cost: Each KMS API call costs $0.03 per 10,000 requests. Caching data keys dramatically reduces costs at scale.
    • Security: The plaintext data key exists only in memory, never persisted. The master key never leaves KMS hardware security modules.
    # Generate a data key
    aws kms generate-data-key   --key-id KEY_ID   --key-spec AES_256   --encryption-context "TableName=Users"
    
    # Returns:
    # - Plaintext (base64): Use to encrypt data locally, then discard from memory
    # - CiphertextBlob (base64): Store alongside the encrypted data
    
    # To decrypt later:
    # 1. Send CiphertextBlob to kms:Decrypt to get the plaintext data key
    # 2. Use the plaintext data key to decrypt the data locally
    
    # For the AWS Encryption SDK (recommended for production):
    pip install aws-encryption-sdk
    
    # Encrypt using the SDK CLI
    aws-encryption-cli   --encrypt   --input secret-file.txt   --wrapping-keys key=arn:aws:kms:us-east-1:123456789012:key/KEY-ID   --encryption-context Environment=production   --output secret-file.txt.encrypted   --metadata-output metadata.json   --commitment-policy require-encrypt-require-decrypt

    Data key caching: The AWS Encryption SDK supports caching data keys for a configurable maximum number of messages, bytes, or time. Set conservative limits: maximum age of 5 minutes, maximum messages of 100, and maximum bytes of 10 GB. This balances performance with security by limiting the exposure window of any single data key.

    7. Restrict Key Usage with kms:ViaService

    The kms:ViaService condition key restricts a KMS key so it can only be used when the request originates from a specific AWS service. This prevents a compromised principal from using the key directly via the KMS API, even if they have kms:Decrypt permission.

    Implementation

    {
      "Sid": "RestrictKeyToS3Only",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::123456789012:role/AppRole"},
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "s3.us-east-1.amazonaws.com"
        }
      }
    }
    # Verify kms:ViaService conditions in existing key policies
    aws kms get-key-policy --key-id KEY_ID --policy-name default --output text |   python3 -c "import sys,json; p=json.load(sys.stdin); [print(s.get('Sid',''), s.get('Condition',{})) for s in p['Statement']]"
    
    # Test: direct KMS API call should be DENIED when kms:ViaService is enforced
    aws kms decrypt --ciphertext-blob fileb://encrypted.b64 --key-id KEY_ID
    # Expected: AccessDeniedException

    Codefinger defense: The January 2025 Codefinger ransomware attack used compromised credentials to call S3 PutObject with SSE-C keys directly. While kms:ViaService does not protect against SSE-C (which bypasses KMS entirely), restricting KMS key usage to specific services limits an attacker's ability to use your KMS keys for unauthorized encryption or decryption via direct API calls. Combine this with S3 bucket policies that deny SSE-C uploads to neutralize the Codefinger attack vector entirely.

    Best practice: Create separate KMS keys for each service -- your S3 key, RDS key, Secrets Manager key, and EBS key should all be distinct with service-specific policies.

    8. Secure Cross-Account Access Controls

    Cross-account KMS access is required in many architectures -- centralized encryption, shared data lakes, cross-account backups. There are two mechanisms: key policies and grants. Each has distinct security implications.

    Key Policies vs Grants

    • Key policies: Persistent, immediately effective, visible in the key policy document. Best for stable, long-term cross-account relationships.
    • Grants: Temporary, scoped, and revocable. Best for delegated access patterns where an AWS service needs to use a key on your behalf (e.g., EBS creating an encrypted snapshot for another account). Grants do not require modifying the key policy.
    {
      "Sid": "AllowCrossAccountUsage",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::987654321098:root"},
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalOrgID": "o-EXAMPLEORG",
          "kms:ViaService": "s3.us-east-1.amazonaws.com"
        }
      }
    }
    # Create a grant for cross-account access (scoped and temporary)
    aws kms create-grant   --key-id KEY_ID   --grantee-principal arn:aws:iam::987654321098:role/DataAnalystRole   --operations Decrypt DescribeKey   --constraints '{"EncryptionContextSubset":{"Department":"Analytics"}}'
    
    # List all grants on a key to audit cross-account access
    aws kms list-grants --key-id KEY_ID
    
    # Retire a grant when no longer needed
    aws kms retire-grant --grant-id GRANT_ID --key-id KEY_ID

    Best practice: Always restrict cross-account access to principals within your AWS Organization using aws:PrincipalOrgID. The external account must also grant its principals permission to use the KMS key via an IAM policy -- both key policy and IAM policy authorization are required. Audit grants quarterly; unlike key policies, grants have no native expiration.

    9. Secure Multi-Region Keys

    Multi-Region keys share the same key material and key ID across AWS Regions, allowing you to encrypt in one Region and decrypt in another without cross-Region API calls. They are essential for disaster recovery and globally distributed applications, but they expand the attack surface because a key compromise or policy misconfiguration affects all replica Regions simultaneously.

    Security Considerations

    • Replication control: Only the primary key owner can create replicas. Use SCPs to restrict kms:ReplicateKey to authorized roles and approved Regions only.
    • Independent access controls: Each replica has its own key policy, grants, and aliases. You must configure access controls separately in each Region -- they are not replicated automatically.
    • Audit across Regions: CloudTrail events for multi-Region key usage occur in the Region where the key is used. Enable CloudTrail in all Regions to maintain complete visibility.
    • Data sovereignty: Creating a replica moves key material across Region boundaries. Verify compatibility with your regulatory requirements (GDPR, data residency laws).
    # Create a multi-Region primary key
    aws kms create-key   --multi-region   --description "Multi-Region key for global application"   --tags TagKey=Environment,TagValue=Production
    
    # Create a replica in another Region
    aws kms replicate-key   --key-id mrk-EXAMPLE   --replica-region eu-west-1   --description "Replica for EU disaster recovery"
    
    # SCP to restrict replication to approved Regions
    # Deny kms:ReplicateKey where kms:ReplicaRegion is NOT in the approved list

    SCP for Multi-Region Key Governance

    {
      "Version": "2012-10-17",
      "Statement": [{
        "Sid": "RestrictMRKReplication",
        "Effect": "Deny",
        "Action": "kms:ReplicateKey",
        "Resource": "*",
        "Condition": {
          "StringNotEquals": {
            "kms:ReplicaRegion": ["us-east-1", "eu-west-1"]
          }
        }
      }]
    }

    Best practice: Prefer single-Region keys unless you have a concrete requirement for cross-Region decryption. Multi-Region keys add operational complexity and expand the blast radius of a key compromise to all replica Regions. Ensure key policies are consistent across all replicas using AWS Config multi-Region aggregator.

    10. Monitor Key Usage with CloudTrail

    Every KMS API call is logged in CloudTrail. This is your primary mechanism for detecting unauthorized key usage, key policy changes, and potential data exfiltration. CloudTrail logging for KMS management events is enabled by default, but you must verify that data events are also captured for sensitive keys.

    Critical Events to Monitor

    • Decrypt -- Unexpected decrypt calls may indicate data exfiltration
    • DisableKey, ScheduleKeyDeletion -- Potential destructive actions
    • PutKeyPolicy -- Key policy modifications may grant unauthorized access
    • CreateGrant -- New grants may provide backdoor access
    • GenerateDataKey -- High volume may indicate bulk encryption for ransomware
    # Search CloudTrail for key policy changes in the last 24 hours
    aws cloudtrail lookup-events   --lookup-attributes AttributeKey=EventName,AttributeValue=PutKeyPolicy   --start-time $(date -u -v-1d +"%Y-%m-%dT%H:%M:%SZ")   --query "Events[].{Time:EventTime,User:Username,Key:Resources[0].ResourceName}"
    
    # Search for key deletion attempts in the last 7 days
    aws cloudtrail lookup-events   --lookup-attributes AttributeKey=EventName,AttributeValue=ScheduleKeyDeletion   --start-time $(date -u -v-7d +"%Y-%m-%dT%H:%M:%SZ")
    
    # CloudWatch Logs Insights query for anomalous decrypt volume:
    # filter eventSource = "kms.amazonaws.com" and eventName = "Decrypt"
    # | stats count(*) as decryptCount by bin(1h), userIdentity.arn
    # | sort decryptCount desc
    
    # Create metric filter for KMS access denied events (reconnaissance detection)
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name KMSAccessDenied   --filter-pattern '{ ($.eventSource = kms.amazonaws.com) && ($.errorCode = AccessDenied*) }'   --metric-transformations     metricName=KMSAccessDeniedCount,metricNamespace=CloudTrailMetrics,metricValue=1

    Incident context: During the January 2025 Codefinger ransomware campaign, defenders who had CloudTrail logging of S3 and KMS operations were able to reconstruct the attack timeline precisely, identifying the exact objects encrypted by attacker-controlled SSE-C keys. Organizations without CloudTrail logging had no visibility into which objects were affected.

    CIS Benchmark: Control 3.7 (log metric filter and alarm for KMS key deletion/disabling). Control 3.8 (CloudTrail logs encrypted with KMS CMKs).

    11. Prevent Overly Permissive Key Policies

    A KMS key with "Principal": "*" and no conditions is effectively public -- any AWS principal, including those in other accounts, can use it. This is analogous to a public S3 bucket, but for your encryption keys. It is one of the most severe KMS misconfigurations possible.

    Detection

    # Audit all key policies for wildcard principals
    for key in $(aws kms list-keys --query "Keys[].KeyId" --output text); do
      policy=$(aws kms get-key-policy --key-id "$key" --policy-name default --output text 2>/dev/null)
      if echo "$policy" | grep -q '"Principal"[[:space:]]*:[[:space:]]*"*"'; then
        echo "WARNING - Public principal on key: $key"
      fi
      if echo "$policy" | grep -q '"Principal"[[:space:]]*:[[:space:]]*{"AWS"[[:space:]]*:[[:space:]]*"*"}'; then
        echo "WARNING - Public principal on key: $key"
      fi
    done
    
    # Use IAM Access Analyzer to find externally shared KMS keys
    aws accessanalyzer list-findings   --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-analyzer   --filter '{"resourceType":{"eq":["AWS::KMS::Key"]}}'

    Remediation

    • Replace "Principal": "*" with specific account ARNs or role ARNs.
    • If wildcard is required (rare), add strict conditions such as aws:PrincipalOrgID, kms:CallerAccount, or kms:ViaService.
    • Enable IAM Access Analyzer to continuously detect externally shared KMS keys.

    Security Hub: Control [KMS.5] (where available) flags KMS keys with overly permissive policies. Access Analyzer findings for KMS keys are delivered within minutes of a policy change. Use Security Hub for centralized visibility.

    12. Implement Continuous Compliance

    Point-in-time audits are not enough. KMS configurations drift as teams create new keys, modify policies, and add grants. Continuous compliance monitoring catches misconfigurations within minutes, not months.

    Security Hub Controls

    Enable the AWS Foundational Security Best Practices standard in Security Hub for automated KMS compliance checks:

    • [KMS.1]: IAM customer managed policies should not allow decryption actions on all KMS keys
    • [KMS.2]: IAM principals should not have inline policies that allow decryption actions on all KMS keys
    • [KMS.3]: AWS KMS keys should not be scheduled for deletion unintentionally
    • [KMS.4]: AWS KMS key rotation should be enabled

    AWS Config Rules

    # Enable the managed Config rule for KMS key rotation
    aws configservice put-config-rule --config-rule '{
      "ConfigRuleName": "cmk-backing-key-rotation-enabled",
      "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "CMK_BACKING_KEY_ROTATION_ENABLED"
      }
    }'
    
    # Enable the managed Config rule for KMS key deletion
    aws configservice put-config-rule --config-rule '{
      "ConfigRuleName": "kms-cmk-not-scheduled-for-deletion",
      "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "KMS_CMK_NOT_SCHEDULED_FOR_DELETION"
      }
    }'
    
    # Enable CIS Benchmark v3.0 in Security Hub
    aws securityhub batch-enable-standards   --standards-subscription-requests '[{
        "StandardsArn": "arn:aws:securityhub:::standards/cis-aws-foundations-benchmark/v/3.0.0"
      }]'
    
    # Check compliance status
    aws configservice describe-compliance-by-config-rule   --config-rule-names cmk-backing-key-rotation-enabled kms-cmk-not-scheduled-for-deletion   --query "ComplianceByConfigRules[*].{Rule:ConfigRuleName,Compliance:Compliance.ComplianceType}"

    Automated Remediation

    Use EventBridge rules to trigger Lambda functions that automatically remediate non-compliant KMS configurations:

    # EventBridge rule to auto-enable rotation on newly created keys
    aws events put-rule   --name AutoEnableKMSRotation   --event-pattern '{
        "source": ["aws.kms"],
        "detail-type": ["AWS API Call via CloudTrail"],
        "detail": {
          "eventName": ["CreateKey"]
        }
      }'

    Best practice: Integrate Security Hub findings into your ticketing system (Jira, ServiceNow) via EventBridge. Set SLA targets: Critical findings (public key policies) resolved within 4 hours, High findings (missing rotation) resolved within 24 hours.


    Common Misconfigurations

    Misconfiguration Risk Detection
    Wildcard principal in key policy Any AWS account can use the key IAM Access Analyzer, [KMS.5]
    Key rotation disabled Extended exposure if key material is compromised AWS Config: CMK_BACKING_KEY_ROTATION_ENABLED, [KMS.4]
    Decryption on all keys (Resource: *) Lateral movement across all encrypted resources [KMS.1], [KMS.2]
    No encryption context required No additional authentication, weak audit trail Key policy review, CloudTrail analysis
    SSE-C allowed on S3 buckets Codefinger-style ransomware encryption attack S3 bucket policy audit, S3 Security Card
    Key deletion without CloudWatch alarm Irreversible data loss goes undetected CIS Control 3.7, CloudTrail metric filters

    Quick Reference Checklist

    # Practice Priority
    1Enforce least-privilege key policiesCritical
    2Enforce separation of dutiesCritical
    3Enable automatic key rotationHigh
    4Protect against accidental key deletionCritical
    5Use encryption contextHigh
    6Implement envelope encryptionHigh
    7Restrict keys with kms:ViaServiceHigh
    8Secure cross-account accessHigh
    9Secure multi-Region keysMedium
    10Monitor key usage with CloudTrailCritical
    11Prevent overly permissive key policiesCritical
    12Implement continuous complianceHigh

    Related Resources

    Go Deeper: The State of AWS Security 2026

    This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.

    KMSEncryptionKey ManagementKey RotationKey PolicyEnvelope EncryptionSSE-CData ProtectionCryptography