SNS permission management allows you to control access to topics by adding and removing permissions for AWS accounts and services to perform SNS actions.
Grants permission for AWS accounts or services to perform actions on an SNS topic.
/**
* Adds a statement to a topic's access control policy
* Grants permission for the specified AWS accounts to perform the specified actions
*/
class AddPermissionCommand {
constructor(input: AddPermissionInput);
}
interface AddPermissionInput {
/** ARN of the topic whose access control policy you wish to modify */
TopicArn: string;
/** Unique identifier for the new policy statement */
Label: string;
/** AWS account IDs of the users (principals) who will be given access */
AWSAccountId: string[];
/** Actions you want to allow for the specified principals */
ActionName: string[];
}Usage Examples:
import { SNSClient, AddPermissionCommand } from "@aws-sdk/client-sns";
const client = new SNSClient({ region: "us-east-1" });
// Allow another AWS account to publish to your topic
await client.send(new AddPermissionCommand({
TopicArn: "arn:aws:sns:us-east-1:123456789012:MyTopic",
Label: "CrossAccountPublish",
AWSAccountId: ["987654321098"],
ActionName: ["SNS:Publish"]
}));
// Allow multiple accounts to subscribe and receive messages
await client.send(new AddPermissionCommand({
TopicArn: "arn:aws:sns:us-east-1:123456789012:MyTopic",
Label: "MultiAccountAccess",
AWSAccountId: ["987654321098", "555666777888"],
ActionName: ["SNS:Subscribe", "SNS:Receive"]
}));
// Grant comprehensive access to a trusted account
await client.send(new AddPermissionCommand({
TopicArn: "arn:aws:sns:us-east-1:123456789012:MyTopic",
Label: "TrustedAccountFullAccess",
AWSAccountId: ["987654321098"],
ActionName: [
"SNS:Publish",
"SNS:Subscribe",
"SNS:Unsubscribe",
"SNS:Receive",
"SNS:GetTopicAttributes"
]
}));Common SNS Actions:
SNS:Publish - Publish messages to the topicSNS:Subscribe - Subscribe to the topicSNS:Unsubscribe - Unsubscribe from the topicSNS:Receive - Receive messages from subscriptionsSNS:GetTopicAttributes - Get topic attributesSNS:SetTopicAttributes - Set topic attributesSNS:AddPermission - Add permissions to the topicSNS:RemovePermission - Remove permissions from the topicSNS:ListSubscriptionsByTopic - List subscriptions for the topicSNS:DeleteTopic - Delete the topicRemoves a statement from a topic's access control policy.
/**
* Removes a statement from a topic's access control policy
* Revokes previously granted permissions
*/
class RemovePermissionCommand {
constructor(input: RemovePermissionInput);
}
interface RemovePermissionInput {
/** ARN of the topic whose access control policy you wish to modify */
TopicArn: string;
/** Unique label of the statement you want to remove */
Label: string;
}Usage Example:
// Remove previously granted permissions
await client.send(new RemovePermissionCommand({
TopicArn: "arn:aws:sns:us-east-1:123456789012:MyTopic",
Label: "CrossAccountPublish"
}));
console.log("Permission removed from topic");Allow other AWS accounts to publish messages to your topics:
const allowCrossAccountPublishing = async (
topicArn: string,
trustedAccounts: string[]
) => {
await client.send(new AddPermissionCommand({
TopicArn: topicArn,
Label: "CrossAccountPublishers",
AWSAccountId: trustedAccounts,
ActionName: ["SNS:Publish"]
}));
console.log(`Granted publish access to accounts: ${trustedAccounts.join(', ')}`);
};
// Usage
await allowCrossAccountPublishing(
"arn:aws:sns:us-east-1:123456789012:SharedTopic",
["987654321098", "555666777888"]
);Grant permissions for AWS services to publish to your topics:
// Allow CloudWatch Alarms to publish
await client.send(new AddPermissionCommand({
TopicArn: "arn:aws:sns:us-east-1:123456789012:AlertsTopic",
Label: "CloudWatchAlarmsAccess",
AWSAccountId: ["123456789012"], // Your own account
ActionName: ["SNS:Publish"]
}));
// The actual service principal is specified in the topic policy
// This requires setting a topic policy that includes the service principalFor more complex access patterns, you can set a complete topic policy using SetTopicAttributes:
import { SetTopicAttributesCommand } from "@aws-sdk/client-sns";
const setComplexTopicPolicy = async (topicArn: string) => {
const policy = {
Version: "2012-10-17",
Statement: [
{
Sid: "AllowOwnerFullAccess",
Effect: "Allow",
Principal: {
AWS: "arn:aws:iam::123456789012:root"
},
Action: "SNS:*",
Resource: topicArn
},
{
Sid: "AllowCrossAccountPublish",
Effect: "Allow",
Principal: {
AWS: [
"arn:aws:iam::987654321098:root",
"arn:aws:iam::555666777888:root"
]
},
Action: "SNS:Publish",
Resource: topicArn
},
{
Sid: "AllowCloudWatchAlarms",
Effect: "Allow",
Principal: {
Service: "cloudwatch.amazonaws.com"
},
Action: "SNS:Publish",
Resource: topicArn,
Condition: {
StringEquals: {
"aws:SourceAccount": "123456789012"
}
}
},
{
Sid: "AllowS3EventNotifications",
Effect: "Allow",
Principal: {
Service: "s3.amazonaws.com"
},
Action: "SNS:Publish",
Resource: topicArn,
Condition: {
StringEquals: {
"aws:SourceAccount": "123456789012"
}
}
}
]
};
await client.send(new SetTopicAttributesCommand({
TopicArn: topicArn,
AttributeName: "Policy",
AttributeValue: JSON.stringify(policy)
}));
console.log("Complex topic policy set successfully");
};List and review current topic permissions:
import { GetTopicAttributesCommand } from "@aws-sdk/client-sns";
const auditTopicPermissions = async (topicArn: string) => {
const attributes = await client.send(new GetTopicAttributesCommand({
TopicArn: topicArn
}));
const policyString = attributes.Attributes?.Policy;
if (policyString) {
const policy = JSON.parse(policyString);
console.log(`Topic: ${topicArn}`);
console.log("Access Policy Statements:");
policy.Statement?.forEach((statement: any, index: number) => {
console.log(`\nStatement ${index + 1}:`);
console.log(` Effect: ${statement.Effect}`);
console.log(` Principal: ${JSON.stringify(statement.Principal)}`);
console.log(` Action: ${JSON.stringify(statement.Action)}`);
if (statement.Condition) {
console.log(` Condition: ${JSON.stringify(statement.Condition)}`);
}
});
} else {
console.log("No custom policy found - using default permissions");
}
};Remove all custom permissions and reset to default policy:
const resetTopicPermissions = async (topicArn: string) => {
// Get current policy to extract statement labels
const attributes = await client.send(new GetTopicAttributesCommand({
TopicArn: topicArn
}));
const policyString = attributes.Attributes?.Policy;
if (policyString) {
const policy = JSON.parse(policyString);
// Remove each statement by label (if they have labels)
for (const statement of policy.Statement || []) {
if (statement.Sid && statement.Sid !== "DefaultStatement") {
try {
await client.send(new RemovePermissionCommand({
TopicArn: topicArn,
Label: statement.Sid
}));
console.log(`Removed permission: ${statement.Sid}`);
} catch (error) {
// Some statements might not be removable via RemovePermission
console.log(`Could not remove ${statement.Sid}: ${error.message}`);
}
}
}
}
// Alternatively, reset to a basic policy
const basicPolicy = {
Version: "2012-10-17",
Statement: [{
Sid: "DefaultStatement",
Effect: "Allow",
Principal: {
AWS: attributes.Attributes?.Owner
},
Action: "SNS:*",
Resource: topicArn
}]
};
await client.send(new SetTopicAttributesCommand({
TopicArn: topicArn,
AttributeName: "Policy",
AttributeValue: JSON.stringify(basicPolicy)
}));
console.log("Topic permissions reset to default");
};Grant only the minimum permissions required:
// Instead of granting all SNS actions
const grantMinimalAccess = async (topicArn: string, accountId: string) => {
// Only grant specific actions needed
await client.send(new AddPermissionCommand({
TopicArn: topicArn,
Label: "MinimalPublishAccess",
AWSAccountId: [accountId],
ActionName: ["SNS:Publish"] // Only publish, not subscribe or manage
}));
};Use topic policies with conditions for enhanced security:
const setConditionalAccess = async (topicArn: string) => {
const policy = {
Version: "2012-10-17",
Statement: [{
Sid: "AllowPublishFromSpecificIP",
Effect: "Allow",
Principal: {
AWS: "arn:aws:iam::987654321098:root"
},
Action: "SNS:Publish",
Resource: topicArn,
Condition: {
IpAddress: {
"aws:SourceIp": ["203.0.113.0/24"]
},
DateGreaterThan: {
"aws:CurrentTime": "2023-01-01T00:00:00Z"
},
DateLessThan: {
"aws:CurrentTime": "2024-12-31T23:59:59Z"
}
}
}]
};
await client.send(new SetTopicAttributesCommand({
TopicArn: topicArn,
AttributeName: "Policy",
AttributeValue: JSON.stringify(policy)
}));
};Implement regular auditing of topic permissions:
const auditAllTopicPermissions = async () => {
const { ListTopicsCommand } = await import("@aws-sdk/client-sns");
const topics = await client.send(new ListTopicsCommand());
for (const topic of topics.Topics || []) {
if (topic.TopicArn) {
console.log(`\n=== Auditing ${topic.TopicArn} ===`);
await auditTopicPermissions(topic.TopicArn);
}
}
};