← Back to DevBytes

SNS Security: IAM Policies and Network Security

Introduction to SNS Security

Amazon Simple Notification Service (SNS) is a fully managed messaging service that enables application-to-application and application-to-person communication. Because SNS topics often carry sensitive data and fan out messages to multiple subscribers, securing them is critical. A misconfigured SNS topic can expose internal events to unauthorized users, allow malicious actors to publish spam or malicious payloads, or leak confidential information to unintended recipients.

SNS security rests on two foundational pillars: IAM policies, which control who can perform actions on topics, and network security, which controls where requests can originate from. This tutorial walks through both, with practical examples you can apply immediately.

Why SNS Security Matters

SNS topics are frequently used as the backbone of event-driven architectures. A single topic might trigger Lambda functions, push messages to SQS queues, send SMS to customers, or fan out to HTTP endpoints. If an attacker gains the ability to publish to such a topic, they can:

By combining precise IAM policies with network-level restrictions, you create defense in depth that significantly reduces the attack surface.

IAM Policies for SNS

IAM policies for SNS come in two flavors: identity-based policies attached to users, groups, or roles, and resource-based policies attached directly to SNS topics. Understanding when to use each is essential.

Identity-Based Policies

Identity-based policies define what actions an IAM principal can perform. The following policy grants a Lambda execution role permission to publish messages to a specific topic:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:us-east-1:123456789012:order-events"
    }
  ]
}

For a service that needs to list and subscribe to topics, you would broaden the permissions while still scoping them tightly:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sns:ListTopics",
        "sns:Subscribe",
        "sns:Unsubscribe"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:us-east-1:123456789012:order-events"
    }
  ]
}

Resource-Based Policies

Resource-based policies are attached to the SNS topic itself. They are essential when you need to grant access to principals in other AWS accounts, or when you want to allow AWS services like S3 or EventBridge to publish to your topic without creating IAM roles. Here is a resource-based policy that allows an S3 bucket in another account to publish notifications:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:root"
      },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:us-east-1:123456789012:order-events",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3:::my-secure-bucket"
        }
      }
    }
  ]
}

The Condition block is crucial here. Without it, any resource in account 111122223333 could publish to the topic. The aws:SourceArn condition ensures only the specified bucket can send messages.

Using the AWS CLI to Apply a Resource-Based Policy

You can attach a resource-based policy using the AWS CLI. First, save the policy JSON to a file, then run:

aws sns set-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
  --attribute-name Policy \
  --attribute-value file://topic-policy.json

To verify the policy was applied correctly, retrieve it:

aws sns get-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
  --query 'Attributes.Policy' | jq .

Network Security for SNS

While IAM policies control who can access a topic, network security controls where requests can come from. This is particularly important for topics that should only be accessed from within a private network, or for HTTP/S endpoints that receive messages from SNS.

Restricting Access by IP Address

You can use the aws:SourceIp condition in a resource-based policy to restrict publishing to specific IP ranges. This is useful when on-premises systems or specific corporate networks need to publish to a topic:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:us-east-1:123456789012:internal-events",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": [
            "10.0.0.0/8",
            "172.16.0.0/12",
            "203.0.113.50/32"
          ]
        }
      }
    }
  ]
}

Be cautious when using Principal: "*". Always pair it with a strict condition to avoid accidentally opening the topic to the public internet.

VPC Endpoints for Private Connectivity

By default, SNS API calls traverse the public internet. For workloads running in a VPC, you can use a VPC endpoint (AWS PrivateLink) to keep traffic within the AWS network. This is especially important for compliance requirements that mandate no public internet exposure.

To create an interface VPC endpoint for SNS:

aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0abc123def456 \
  --service-name com.amazonaws.us-east-1.sns \
  --subnet-ids subnet-0aaa111 subnet-0bbb222 \
  --security-group-ids sg-0private123 \
  --vpc-endpoint-type Interface

VPC Endpoint Policies

A VPC endpoint policy is a resource-based policy attached to the endpoint itself. It controls which principals can use the endpoint and which actions they can perform through it. Here is a policy that restricts the endpoint to only allow publishing to a specific topic:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/MyAppRole"
      },
      "Action": [
        "sns:Publish"
      ],
      "Resource": [
        "arn:aws:sns:us-east-1:123456789012:internal-events"
      ]
    }
  ]
}

Apply this policy when creating or updating the endpoint:

aws ec2 modify-vpc-endpoint \
  --vpc-endpoint-id vpce-0abc123def \
  --policy-document file://endpoint-policy.json

Securing HTTP/S Subscriptions

When SNS delivers messages to HTTP or HTTPS endpoints, you must verify that incoming requests actually originate from AWS. SNS signs messages with its private key, and your endpoint should validate the signature. Here is a Python example using Flask:

from flask import Flask, request, abort
from sns_message_validator import SNSMessageValidator

app = Flask(__name__)
validator = SNSMessageValidator()

@app.route('/sns-endpoint', methods=['POST'])
def sns_endpoint():
    message_type = request.headers.get('x-amz-sns-message-type')
    body = request.get_json()

    if not body:
        abort(400)

    # Validate the message signature
    try:
        validator.validate_message(body)
    except Exception as e:
        app.logger.error(f"Invalid SNS message: {e}")
        abort(403)

    if message_type == 'SubscriptionConfirmation':
        # Visit the SubscribeURL to confirm the subscription
        import requests
        requests.get(body['SubscribeURL'])
        return '', 200

    if message_type == 'Notification':
        # Process the actual message
        process_message(body['Message'])
        return '', 200

    return '', 200

def process_message(message):
    print(f"Processing: {message}")

Always validate the signature before processing any message. Without validation, an attacker could send forged POST requests to your endpoint and inject arbitrary payloads.

Best Practices

Follow the Principle of Least Privilege

Grant only the specific actions needed. Avoid using "Resource": "*" in identity-based policies for SNS actions like sns:Publish or sns:Subscribe. Instead, list the exact topic ARNs:

{
  "Effect": "Allow",
  "Action": "sns:Publish",
  "Resource": [
    "arn:aws:sns:us-east-1:123456789012:order-events",
    "arn:aws:sns:us-east-1:123456789012:payment-events"
  ]
}

Use Condition Keys for Cross-Account Access

Whenever you grant cross-account access, always include aws:SourceAccount or aws:SourceArn conditions. This prevents the confused deputy problem, where an attacker uses a legitimate service in their own account to access your resources:

{
  "Effect": "Allow",
  "Principal": {
    "Service": "events.amazonaws.com"
  },
  "Action": "sns:Publish",
  "Resource": "arn:aws:sns:us-east-1:123456789012:order-events",
  "Condition": {
    "StringEquals": {
      "aws:SourceAccount": "123456789012"
    },
    "ArnLike": {
      "aws:SourceArn": "arn:aws:events:us-east-1:123456789012:rule/*"
    }
  }
}

Enable Server-Side Encryption

Encrypt SNS topics at rest using AWS KMS. This protects message payloads stored in the topic and ensures only authorized principals with access to the KMS key can decrypt messages:

aws sns set-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
  --attribute-name KmsMasterKeyId \
  --attribute-value arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ef-ghij-klmnopqrstuv

When using a customer managed key, ensure the key policy grants decrypt permissions to all subscribers, including SQS queues and Lambda functions that consume the messages.

Monitor with CloudTrail and CloudWatch

Enable AWS CloudTrail logging to capture all SNS API calls. Monitor for unusual activity such as unexpected CreateTopic, Subscribe, or Publish operations. Create CloudWatch alarms for spikes in message volume that could indicate abuse:

aws cloudwatch put-metric-alarm \
  --alarm-name "SNS-Publish-Spike" \
  --alarm-description "Alert on unusual SNS publish volume" \
  --metric-name NumberOfMessagesPublished \
  --namespace AWS/SNS \
  --statistic Sum \
  --period 300 \
  --threshold 10000 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 1 \
  --dimensions Name=TopicName,Value=order-events \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:security-alerts

Deny Non-HTTPS Subscriptions

For sensitive topics, use a resource-based policy to deny HTTP subscriptions, forcing all endpoints to use HTTPS:

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "sns:Subscribe",
  "Resource": "arn:aws:sns:us-east-1:123456789012:order-events",
  "Condition": {
    "StringEquals": {
      "sns:Protocol": "http"
    }
  }
}

Conclusion

Securing Amazon SNS requires a layered approach that combines precise IAM policies with network-level controls. Identity-based policies ensure only the right principals can interact with your topics, while resource-based policies enable secure cross-account and cross-service access with proper conditions to prevent confused deputy attacks. Network security through VPC endpoints, IP restrictions, and signature validation for HTTP endpoints adds another critical layer of defense. By following the principle of least privilege, enabling encryption, validating message signatures, and continuously monitoring with CloudTrail and CloudWatch, you can build a robust security posture that protects your event-driven architecture from both external threats and internal misconfigurations. Start by auditing your existing topics today, tighten overly permissive policies, and incrementally apply the practices outlined in this tutorial.

— Ad —

Google AdSense will appear here after approval

← Back to all articles