or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-configuration.mddata-protection.mdindex.mdmessage-publishing.mdpagination.mdpermission-management.mdplatform-applications.mdresource-tagging.mdsms-management.mdsubscription-management.mdtopic-management.md
tile.json

permission-management.mddocs/

Permission Management

SNS permission management allows you to control access to topics by adding and removing permissions for AWS accounts and services to perform SNS actions.

Capabilities

Add Permission

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 topic
  • SNS:Subscribe - Subscribe to the topic
  • SNS:Unsubscribe - Unsubscribe from the topic
  • SNS:Receive - Receive messages from subscriptions
  • SNS:GetTopicAttributes - Get topic attributes
  • SNS:SetTopicAttributes - Set topic attributes
  • SNS:AddPermission - Add permissions to the topic
  • SNS:RemovePermission - Remove permissions from the topic
  • SNS:ListSubscriptionsByTopic - List subscriptions for the topic
  • SNS:DeleteTopic - Delete the topic

Remove Permission

Removes 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");

Access Control Patterns

Cross-Account Publishing

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"]
);

Service Integration

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 principal

Topic Policy Management

For 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");
};

Permission Auditing

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");
  }
};

Permission Cleanup

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");
};

Security Best Practices

Principle of Least Privilege

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
  }));
};

Conditional Access

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)
  }));
};

Regular Permission Audits

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);
    }
  }
};