Back to blog
AWSBest PracticesCloud SecurityIdentity & AccessServerless

SNS Topic Not Using CMK: Why Customer-Managed Keys Matter

Learn why SNS topics should use a customer-managed KMS key instead of the AWS-managed default, the risks involved, and step-by-step remediation with CLI and Terraform.

TL;DR

This check flags SNS topics encrypted with an AWS-managed KMS key (aws/sns) instead of a customer-managed key (CMK). Without a CMK you lose control over key rotation, granular access policies, and audit visibility. Fix it by creating a CMK and updating the topic's KmsMasterKeyId attribute.

Server-side encryption for Amazon SNS protects the messages sitting in a topic before they fan out to subscribers. When you enable it, every published message is encrypted at rest using AWS KMS. The catch is that there are two ways to do this, and they are not equal from a security or compliance standpoint.

You can encrypt with the default AWS-managed key (aws/sns), or you can supply your own customer-managed key (CMK). This check, sns_nocmk, fires when a topic is using the AWS-managed key. It is technically encrypted, but you have given up the controls that make encryption meaningful in a security audit.


What this check detects

Lensix inspects every SNS topic in your account and reads its KmsMasterKeyId attribute. The check evaluates three possible states:

  • No encryption — the attribute is absent. Messages are not encrypted at rest at all.
  • AWS-managed key — the attribute is set to alias/aws/sns. This is what sns_nocmk flags.
  • Customer-managed key — the attribute points to a CMK you created. This passes.

Note: The AWS-managed key (aws/sns) is created automatically the first time you enable encryption with the default option. You cannot edit its key policy, you cannot disable it, and AWS controls its rotation schedule. It is shared across all SNS topics in your account that use the default.

A common point of confusion: a topic using aws/sns is encrypted. This check is not about a lack of encryption. It is about who controls the key and what you can prove about it.


Why it matters

The difference between an AWS-managed key and a CMK comes down to control, and control is exactly what auditors and incident responders care about.

You cannot restrict who decrypts your messages

With a CMK, the key policy is yours. You can write conditions that limit decryption to specific roles, specific source services, or specific VPC endpoints. With the AWS-managed key, there is no key policy you can touch. Any principal in your account with the right SNS and KMS permissions can interact with topics encrypted by it, and you cannot add a second layer of key-level restriction.

Key rotation is on AWS's schedule, not yours

AWS rotates the aws/sns key automatically, but you do not control the cadence and you cannot force an emergency rotation. If your compliance framework requires annual rotation with evidence, or requires the ability to rotate after a suspected compromise, the managed key cannot satisfy that on demand. A CMK lets you enable automatic rotation and trigger manual rotation when you need it.

Audit and revocation become hard

CloudTrail logs KMS decrypt calls against a CMK with the key ARN you recognize, which makes tracing "who decrypted what and when" far easier during an investigation. More importantly, if a key is involved in an incident, you can disable or schedule deletion of a CMK to instantly cut off access. You cannot disable the AWS-managed key.

Warning: Many compliance baselines, including parts of PCI DSS, HIPAA, and FedRAMP, expect customer control over encryption keys for sensitive data flows. SNS topics carrying PII, order events, or security alerts often fall into this category. The AWS-managed key will not pass these reviews even though the data is encrypted.

Cross-account fan-out gets safer

If your SNS topic delivers to subscribers in other accounts, a CMK lets you grant those accounts decrypt permission explicitly through the key policy. With the managed key, cross-account encrypted delivery does not work cleanly because you cannot share the key. Teams often disable encryption entirely to work around this, which is the worst outcome.


How to fix it

The fix is two steps: create a CMK with an appropriate key policy, then point the SNS topic at it.

Step 1: Create a customer-managed key

aws kms create-key \
  --description "CMK for SNS topic encryption" \
  --key-usage ENCRYPT_DECRYPT \
  --tags TagKey=Purpose,TagValue=sns-encryption

Note the KeyId in the output. Then create a friendly alias so you do not have to track raw key IDs:

aws kms create-alias \
  --alias-name alias/sns-cmk \
  --target-key-id <key-id-from-previous-step>

Step 2: Attach a key policy that lets SNS use the key

SNS needs permission to generate data keys and decrypt with your CMK. Save this as key-policy.json and adjust the account ID and service principals to match your delivery targets (for example, add the principals of services that publish to or subscribe to the topic).

{
  "Version": "2012-10-17",
  "Id": "sns-cmk-policy",
  "Statement": [
    {
      "Sid": "AllowAccountAdmin",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "AllowSNSService",
      "Effect": "Allow",
      "Principal": { "Service": "sns.amazonaws.com" },
      "Action": [
        "kms:Decrypt",
        "kms:GenerateDataKey*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowPublishersToUseKey",
      "Effect": "Allow",
      "Principal": { "Service": "cloudwatch.amazonaws.com" },
      "Action": [
        "kms:Decrypt",
        "kms:GenerateDataKey*"
      ],
      "Resource": "*"
    }
  ]
}
aws kms put-key-policy \
  --key-id <key-id> \
  --policy-name default \
  --policy file://key-policy.json

Warning: If a service that publishes to the topic (CloudWatch Alarms, S3 event notifications, Lambda, etc.) does not have kms:GenerateDataKey* on the CMK, publishing will silently fail with a KMS access denied error and your notifications stop arriving. Map every publisher and subscriber before you switch keys.

Step 3: Enable automatic rotation

aws kms enable-key-rotation --key-id <key-id>

Step 4: Point the SNS topic at the CMK

aws sns set-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --attribute-name KmsMasterKeyId \
  --attribute-value alias/sns-cmk

Verify the change:

aws sns get-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --query "Attributes.KmsMasterKeyId"

Note: Switching the key affects messages encrypted from that point forward. There is no re-encryption of in-flight messages, and SNS does not retain delivered messages, so the cutover is clean. Just confirm publishers and subscribers keep working immediately after.

Terraform example

If you manage infrastructure as code, define the key and topic together so the relationship is explicit and never drifts:

resource "aws_kms_key" "sns" {
  description             = "CMK for SNS topic encryption"
  enable_key_rotation     = true
  deletion_window_in_days = 30
}

resource "aws_kms_alias" "sns" {
  name          = "alias/sns-cmk"
  target_key_id = aws_kms_key.sns.key_id
}

resource "aws_sns_topic" "main" {
  name              = "my-topic"
  kms_master_key_id = aws_kms_key.sns.key_id
}

Tip: Reuse a single, well-scoped SNS CMK across topics that share the same trust boundary rather than creating one key per topic. Fewer keys means simpler policies and lower KMS cost, while still giving you the control benefits over the AWS-managed default.


How to prevent it from happening again

Fixing one topic is easy. Stopping the next engineer from spinning up a topic with the default key is the real win.

Gate it in CI/CD with policy-as-code

If you use Terraform, add a Checkov or OPA rule that fails the pipeline when an aws_sns_topic has no kms_master_key_id or uses the AWS-managed alias. A minimal OPA/Rego policy for a Terraform plan looks like this:

package sns

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_sns_topic"
  key := resource.change.after.kms_master_key_id
  not startswith(key, "arn:aws:kms")
  not startswith(key, "alias/")
  msg := sprintf("SNS topic %s must use a customer-managed KMS key", [resource.address])
}

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_sns_topic"
  resource.change.after.kms_master_key_id == "alias/aws/sns"
  msg := sprintf("SNS topic %s uses the AWS-managed key, not a CMK", [resource.address])
}

Enforce with Service Control Policies

At the organization level, you can require that any sns:CreateTopic or sns:SetTopicAttributes call specifying a key references a CMK, denying the managed alias. This is stricter and harder to bypass than a pipeline check.

Continuous detection with Lensix

Pipeline gates only catch resources created through the pipeline. Topics created by hand in the console, by other tools, or by AWS services on your behalf slip past. Lensix runs the sns_nocmk check continuously across every region and account, so a topic that drifts to the AWS-managed key shows up in your findings instead of waiting for an audit.


Best practices

  • Always encrypt SNS topics carrying sensitive data with a CMK, not the managed default, especially anything with PII, financial events, or security signals.
  • Enable automatic key rotation on every CMK and document the cadence so it maps to your compliance requirements.
  • Scope key policies tightly. Grant kms:Decrypt and kms:GenerateDataKey* only to the SNS service and the specific publishers and subscribers that need it.
  • Tag your keys with purpose and owner so an investigator can immediately tell what a CMK protects.
  • Audit decrypt activity in CloudTrail and alert on unexpected principals calling decrypt against your SNS CMK.
  • Use a deletion window of at least 30 days on the CMK so a mistaken deletion is recoverable before the key is gone.

Danger: Never schedule deletion of a CMK that is actively protecting a production SNS topic without first repointing the topic to a replacement key and confirming delivery. Once the deletion window passes, the key is gone permanently and every message encrypted under it becomes unrecoverable.

Encryption at rest is a baseline, but the value of encryption is in the controls around the key. Moving SNS topics from the AWS-managed key to a customer-managed key turns "the data is encrypted" into "we control, rotate, audit, and can revoke access to the key that encrypts the data." That distinction is what gets you through an audit and what limits the blast radius when something goes wrong.