or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdmessage-filtering.mdpolicy-management.mdsubscription-management.mdtopic-management.md
tile.json

policy-management.mddocs/

Policy Management

⚠️ DEPRECATION NOTICE: AWS CDK v1 has reached End-of-Support on 2023-06-01. Consider migrating to AWS CDK v2.

IAM policy management for SNS topics with automatic policy creation, statement addition, and integration with CDK's permission grant system.

Capabilities

TopicPolicy Class

Creates and manages IAM policies for SNS topics.

/**
 * The policy for an SNS Topic
 * 
 * Policies define the operations that are allowed on this resource.
 * You almost never need to define this construct directly.
 * All AWS resources that support resource policies have a method called
 * addToResourcePolicy(), which will automatically create a new resource
 * policy if one doesn't exist yet, otherwise it will add to the existing policy.
 * 
 * Prefer to use addToResourcePolicy() instead.
 */
class TopicPolicy extends Resource {
  constructor(scope: Construct, id: string, props: TopicPolicyProps);
  
  /** The IAM policy document for this policy */
  readonly document: PolicyDocument;
}

Usage Examples:

import { TopicPolicy } from '@aws-cdk/aws-sns';
import { PolicyDocument, PolicyStatement, AnyPrincipal } from '@aws-cdk/aws-iam';

// Create policy with initial document
const policyDocument = new PolicyDocument({
  assignSids: true,
  statements: [
    new PolicyStatement({
      actions: ['sns:Subscribe'],
      principals: [new AnyPrincipal()],
      resources: [topic.topicArn]
    })
  ]
});

const topicPolicy = new TopicPolicy(this, 'MyTopicPolicy', {
  topics: [topic],
  policyDocument: policyDocument
});

// Add additional statements
topicPolicy.document.addStatements(
  new PolicyStatement({
    actions: ['sns:Publish'],
    principals: [new ServicePrincipal('lambda.amazonaws.com')],
    resources: [topic.topicArn]
  })
);

TopicPolicyProps Interface

Configuration properties for topic policies.

/**
 * Properties to associate SNS topics with a policy
 */
interface TopicPolicyProps {
  /** The set of topics this policy applies to */
  topics: ITopic[];
  
  /** 
   * IAM policy document to apply to topic(s)
   * @default empty policy document
   */
  policyDocument?: PolicyDocument;
}

Automatic Policy Management

addToResourcePolicy Method

The preferred way to manage topic policies is through the automatic policy creation system.

/**
 * Adds a statement to the IAM resource policy associated with this topic.
 * If this topic was created in this stack (new Topic), a topic policy
 * will be automatically created upon the first call to addToPolicy. If
 * the topic is imported (Topic.import), then this is a no-op.
 */
addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult;

Usage Examples:

import { PolicyStatement, ServicePrincipal, ArnPrincipal } from '@aws-cdk/aws-iam';

// Allow Lambda service to publish
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [new ServicePrincipal('lambda.amazonaws.com')],
  resources: [topic.topicArn]
}));

// Allow specific AWS account to subscribe
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Subscribe'],
  principals: [new ArnPrincipal('arn:aws:iam::123456789012:root')],
  resources: [topic.topicArn]
}));

// Allow CloudWatch Events to publish
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [new ServicePrincipal('events.amazonaws.com')],
  resources: [topic.topicArn]
}));

grantPublish Method

Grant publish permissions to identities using CDK's grant system.

/**
 * Grant topic publishing permissions to the given identity
 * @param identity - The identity to grant permissions to
 * @returns Grant object describing the permission grant
 */
grantPublish(identity: iam.IGrantable): iam.Grant;

Usage Examples:

import { Role, ServicePrincipal, User } from '@aws-cdk/aws-iam';
import { Function } from '@aws-cdk/aws-lambda';

// Grant publish to Lambda function
const lambdaFunction = new Function(this, 'Publisher', {
  // ... function configuration
});
topic.grantPublish(lambdaFunction);

// Grant publish to IAM role
const publisherRole = new Role(this, 'PublisherRole', {
  assumedBy: new ServicePrincipal('ec2.amazonaws.com')
});
topic.grantPublish(publisherRole);

// Grant publish to IAM user
const user = new User(this, 'PublisherUser');
topic.grantPublish(user);

Common Policy Patterns

Cross-Account Access

Allow other AWS accounts to interact with your topic:

import { AccountRootPrincipal, PolicyStatement } from '@aws-cdk/aws-iam';

// Allow another account to subscribe
topic.addToResourcePolicy(new PolicyStatement({
  actions: [
    'sns:Subscribe',
    'sns:Receive'
  ],
  principals: [new AccountRootPrincipal('123456789012')],
  resources: [topic.topicArn]
}));

// Allow another account to publish with conditions
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [new AccountRootPrincipal('123456789012')],
  resources: [topic.topicArn],
  conditions: {
    StringEquals: {
      'sns:TopicArn': topic.topicArn
    }
  }
}));

Service Integration Policies

Common patterns for AWS service integration:

import { ServicePrincipal } from '@aws-cdk/aws-iam';

// CloudWatch Events
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [new ServicePrincipal('events.amazonaws.com')],
  resources: [topic.topicArn]
}));

// S3 Event Notifications
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [new ServicePrincipal('s3.amazonaws.com')],
  resources: [topic.topicArn],
  conditions: {
    StringEquals: {
      'aws:SourceAccount': this.account
    }
  }
}));

// CodeStar Notifications
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [new ServicePrincipal('codestar-notifications.amazonaws.com')],
  resources: [topic.topicArn]
}));

Conditional Access

Use conditions to restrict access based on various criteria:

// Time-based access
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [publisherRole],
  resources: [topic.topicArn],
  conditions: {
    DateGreaterThan: {
      'aws:CurrentTime': '2023-01-01T00:00:00Z'
    },
    DateLessThan: {
      'aws:CurrentTime': '2024-01-01T00:00:00Z'
    }
  }
}));

// IP address restriction
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [publisherRole],
  resources: [topic.topicArn],
  conditions: {
    IpAddress: {
      'aws:SourceIp': ['203.0.113.0/24', '198.51.100.0/24']
    }
  }
}));

// MFA requirement
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'],
  principals: [user],
  resources: [topic.topicArn],
  conditions: {
    Bool: {
      'aws:MultiFactorAuthPresent': 'true'
    },
    NumericLessThan: {
      'aws:MultiFactorAuthAge': '3600' // 1 hour
    }
  }
}));

Organization-Wide Access

Allow access from within your AWS Organization:

import { OrganizationPrincipal } from '@aws-cdk/aws-iam';

topic.addToResourcePolicy(new PolicyStatement({
  actions: [
    'sns:Subscribe',
    'sns:Receive'
  ],
  principals: [new OrganizationPrincipal('o-example12345')],
  resources: [topic.topicArn]
}));

Policy Validation and Best Practices

Policy Document Configuration

Configure policy documents with appropriate settings:

import { PolicyDocument } from '@aws-cdk/aws-iam';

const topicPolicy = new TopicPolicy(this, 'Policy', {
  topics: [topic],
  policyDocument: new PolicyDocument({
    // Assign SIDs automatically for uniqueness
    assignSids: true,
    // Minimize policy size for better performance
    minimize: true
  })
});

Security Best Practices

  1. Principle of Least Privilege: Grant only the minimum permissions required
  2. Use Conditions: Add conditions to restrict access where appropriate
  3. Regular Audits: Periodically review topic policies for unnecessary permissions
  4. Account Boundaries: Be careful with cross-account access
  5. Service-Specific Principals: Use specific service principals rather than wildcards
// Good: Specific permissions with conditions
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:Publish'], // Specific action
  principals: [new ServicePrincipal('lambda.amazonaws.com')], // Specific service
  resources: [topic.topicArn], // Specific resource
  conditions: {
    StringEquals: {
      'aws:SourceAccount': this.account // Account boundary
    }
  }
}));

// Avoid: Overly broad permissions
topic.addToResourcePolicy(new PolicyStatement({
  actions: ['sns:*'], // Too broad
  principals: [new AnyPrincipal()], // Too permissive
  resources: ['*'] // Too broad
}));

Policy Troubleshooting

Common issues and solutions:

// Issue: Policy size limits
// Solution: Use conditions and minimize permissions

// Issue: Circular dependencies
// Solution: Create policies in separate stacks or use imports

// Issue: Policy conflicts
// Solution: Check existing policies before adding new statements
const result = topic.addToResourcePolicy(statement);
if (!result.statementAdded) {
  console.log('Policy statement was not added (imported topic or conflict)');
}

Integration with Other CDK Features

Grants System Integration

Topics automatically integrate with CDK's grants system:

// Grant systems work automatically with topics
const grant = topic.grantPublish(lambdaFunction);

// Check if grant was applied
if (grant.success) {
  console.log('Publish permissions granted successfully');
}

// Access the policy dependable for ordering
grant.policyDependable; // Can be used for depends-on relationships

Cross-Stack References

Handle topic policies across CDK stacks:

// Stack A: Create topic and export
export class TopicStack extends Stack {
  public readonly topic: ITopic;
  
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    
    this.topic = new Topic(this, 'SharedTopic');
  }
}

// Stack B: Import topic and add policy
export class ConsumerStack extends Stack {
  constructor(scope: Construct, id: string, props: ConsumerStackProps) {
    super(scope, id, props);
    
    // Use imported topic (policies won't be created automatically)
    props.importedTopic.grantPublish(myLambda);
  }
}