Cross-AWS Account Communication in AWS using SNS and Lambdas

The SaaS Enthusiast
6 min readMay 6, 2024

--

An iconic radio tower or satellite dish emitting waves to smaller receiving dishes or devices, symbolizing the broadcast nature of SNS.

Managing communications between different microSaaS or SaaS services across AWS accounts requires a robust and scalable messaging system. AWS SNS (Simple Notification Service) provides a highly efficient solution for such scenarios through its publish-subscribe messaging model. Unlike AWS SQS (Simple Queue Service), which is designed primarily for point-to-point messaging, SNS allows for broadcasting messages instantaneously to multiple subscribers, including AWS Lambda functions, which can reside in different AWS accounts.

This capability is essential when an event in one service needs to trigger an immediate response in other services or systems, irrespective of their account boundaries. This article explains why AWS SNS is preferred over SQS for broadcasting events across services and details how to configure SNS to trigger a Lambda function in another AWS account. By walking through a practical setup, we’ll demonstrate how SNS can be used to facilitate real-time, cross-account interactions in a secure and manageable way.

Creating the SNS Topic

Before we can publish messages from our services, we need to set up an SNS topic that our applications can interact with. This setup includes defining the topic, setting appropriate permissions, and exporting the topic ARN for easy access across our Serverless services.

To begin, we define an SNS topic using AWS CloudFormation resources within our Serverless configuration. This is managed in a separate YAML file to keep our configurations organized:

# resources/sns.yml
Resources:
NewStudentTopic:
Type: "AWS::SNS::Topic"
Properties:
TopicName: ${self:custom.newStudentTopicName}
DisplayName: "New Student Notification Topic"

SNSTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
Topics:
- Ref: NewStudentTopic
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
AWS: "arn:aws:iam::XXXXXXXXXXXX:root"
Action: "sns:Subscribe"
Resource: !Ref NewStudentTopic

Outputs:
NewStudentTopicARN:
Description: "ARN of the New Student Topic"
Value: !Ref NewStudentTopic
Export:
Name: ${self:service}-${self:provider.stage}-NewStudentTopicARN

Updating IAM Role for SNS Access

Additionally, any Lambda function or service that publishes to this topic will need appropriate permissions. This involves updating the IAM role to include permissions for publishing messages to the SNS topic.

# Add to the IAM role section in your serverless.yml
iamRoleStatements:
- Effect: "Allow"
Action: "sns:Publish"
Resource:
Fn::ImportValue:
!Sub ${self:service}-${self:provider.stage}-NewStudentTopicARN

Here, we’re granting the Lambda function the sns:Publish action on the SNS topic we created. The Resource uses the Fn::ImportValue function to dynamically import the ARN of the SNS topic, which we exported in the sns.yml file.

Setting Up the Broadcaster Account with AWS SNS

Creating a Reusable SNS Service

To facilitate cross-account messaging in AWS, we begin by setting up a broadcaster service that is responsible for publishing messages to an SNS topic. This involves creating an SNS client that can be reused across different parts of the application, enhancing maintainability and testability. To achieve this, we encapsulate the SNS client logic within a dedicated service file, named SNSService.js. This service will manage all interactions with the SNS topic, including message publication and error handling.

// SNSService.js
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";

class SNSService {
constructor(region) {
this.client = new SNSClient({ region });
}

async publishMessage(TopicArn, Message) {
const params = {
TopicArn,
Message,
MessageStructure: 'string',
};

try {
const data = await this.client.send(new PublishCommand(params));
console.log("Message sent successfully:", data);
return data;
} catch (err) {
console.error("Failed to send message:", err);
throw err;
}
}
}

export default SNSService;

Integrating SNS Service with Business Logic

Once the SNSService is established, we integrate it into our application's workflow. This integration is done in a Lambda function handler where business logic is executed. To enhance the architecture, we utilize middy, a popular middleware framework for AWS Lambda, which allows us to cleanly separate the business logic from side effects such as message publishing.

// handler.js
import middy from '@middy/core';
import { SNSService } from './SNSService';

const handlerLogic = async (event) => {
// Business logic here
console.log("Executing business logic");
return { status: 'success', data: event.data };
};

const snsMiddleware = (opts) => {
const snsService = new SNSService(opts.region);

return {
after: async (handler, next) => {
await snsService.publishMessage(opts.topicArn, JSON.stringify(handler.response));
next();
}
};
};

export const handler = middy(handlerLogic)
.use(snsMiddleware({
region: 'us-east-1',
topicArn: 'arn:aws:sns:us-east-1:123456789012:my-topic'
}));

In this setup:

  • SNSService is responsible for all AWS SNS interactions, abstracting the complexity of SNS operations away from the business logic.
  • handler.js defines the Lambda function's business logic and utilizes middleware to handle SNS message publishing after the main business logic has completed.

This approach not only simplifies unit testing by isolating the SNS communication into a separate service but also enhances the Lambda function’s readability and maintainability.

Setting Up the Subscriber Lambda in the Consumer Account

In the consumer account, we need to create a Lambda function that will be triggered by the SNS topic from the broadcaster’s account. This setup involves not only creating the Lambda function but also ensuring that it has the necessary permissions to be invoked by an SNS topic from another AWS account.

Creating the Lambda Function

Here’s how you can define the Lambda function in your Serverless configuration:

# serverless.yml in the consumer account

functions:
handleNewStudent:
handler: handlers.handleNewStudent
events:
- sns:
arn: ${env:NEW_STUDENT_TOPIC_ARN} # Ensure this environment variable is set to the ARN of the SNS topic from the other account

In this setup:

  • handleNewStudent: This is the name of the Lambda function.
  • handler: Points to the function within your codebase that will handle the events.
  • events: Defines the trigger for this function, which in this case is an SNS topic. The ARN for the SNS topic is passed as an environment variable which you should set in your environment or via your CI/CD pipeline.

Configuring Cross-Account Permissions

To allow the SNS topic from another account to invoke this Lambda function, you need to set up permissions explicitly. This can be configured in the AWS Management Console or using the AWS CLI, but using the Serverless Framework, you can include it directly in your serverless.yml:

resources:
Resources:
SNSInvokeLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt HandleNewStudentLambdaFunction.Arn
Action: "lambda:InvokeFunction"
Principal: "sns.amazonaws.com"
SourceArn: ${env:NEW_STUDENT_TOPIC_ARN} # ARN of the SNS topic from the other account
SourceAccount: ${env:BROADCASTER_ACCOUNT_ID} # Account ID of the broadcaster account

This IAM permission:

  • FunctionName: Uses the !GetAtt to reference the Lambda function's ARN.
  • Action: Specifies what action is being allowed, in this case, “lambda:InvokeFunction”.
  • Principal: Specifies who is allowed to perform the action, “sns.amazonaws.com”, meaning SNS service.
  • SourceArn: Specifies which SNS topic ARN is allowed to trigger this Lambda.
  • SourceAccount: Specifies the AWS account ID from which the SNS topic is allowed to invoke this Lambda.

Ensuring Environment Variables are Set

Ensure that the NEW_STUDENT_TOPIC_ARN and BROADCASTER_ACCOUNT_ID environment variables are correctly set in your deployment environment. These variables contain the ARN of the SNS topic and the account ID of the broadcaster, respectively.

Deploying the Configuration

Deploy your service using the Serverless Framework:

serverless deploy

Empower Your Tech Journey:

Explore a wealth of knowledge designed to elevate your tech projects and understanding. From safeguarding your applications to mastering serverless architecture, discover articles that resonate with your ambition.

New Projects or Consultancy

For new project collaborations or bespoke consultancy services, reach out directly and let’s transform your ideas into reality. Ready to take your project to the next level?

Protecting Routes With AWS

Mastering Serverless Series

Advanced Serverless Techniques

--

--