AWS: Encrypted SQS with SNS Subscription using KMS

December 17th, 2020 507 Words

To decouple services on AWS, it’s a common pattern to use Amazon SQS and Amazon SNS. With AWS Key Management Service, you can encrypt the messages stored in the SNS topic and SQS queue. For the AWS Cloud Development Kit using TypeScript, you can easily create an architecture for secure message processing.

Resources

You need to create resources in Amazon SQS, Amazon SNS, AWS Key Management Service, and AWS Lambda for this example setup. Instead of using the KMS key directly for each service, it’s recommended to create an alias for every use case. To test the subscription, an AWS Lambda function can just publish a message to the created SNS topic.

import * as sns from "@aws-cdk/aws-sns";
import * as subs from "@aws-cdk/aws-sns-subscriptions";
import * as lambdaNode from "@aws-cdk/aws-lambda-nodejs";
import * as sqs from "@aws-cdk/aws-sqs";
import * as cdk from "@aws-cdk/core";
import * as kms from "@aws-cdk/aws-kms";
import * as iam from "@aws-cdk/aws-iam";

export class ExampleStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * Create key in AWS Key Management Service
     */
    const key = new kms.Key(this, "ExampleKey");

    /**
     * Create queue in Amazon SQS
     */
    const queue = new sqs.Queue(this, "ExampleQueue", {
      visibilityTimeout: cdk.Duration.seconds(300),
      encryption: sqs.QueueEncryption.KMS,
      dataKeyReuse: cdk.Duration.minutes(5),
      encryptionMasterKey: key.addAlias("example/sqs"),
    });

    /**
     * Create topic in Amazon SNS
     */
    const topic = new sns.Topic(this, "ExampleTopic", {
      masterKey: key.addAlias("example/sns"),
    });

    /**
     * Create function in AWS Lambda
     */
    const func = new lambdaNode.NodejsFunction(this, "ExampleFunction", {
      entry: "src/handler.ts",
      handler: "run",
      environment: {
        TOPIC_ARN: topic.topicArn,
      },
    });
  }
}

Permissions

To enable Amazon SNS to send messages to encrypted Amazon SQS queues, you need to configure a resource policy for the key in AWS Key Management Service.

/**
 * Allow SNS to use key in KMS
 */

key.addToResourcePolicy(
  new iam.PolicyStatement({
    sid: "sns-allow",
    effect: iam.Effect.ALLOW,
    principals: [new iam.ServicePrincipal("sns")],
    actions: ["kms:Decrypt", "kms:GenerateDataKey"],
  })
);

With the granted permission, the queue can be subscribed to the topic.

/**
 * Subscribe Amazon SQS queue to Amazon SNS topic
 */

topic.addSubscription(
  new subs.SqsSubscription(queue, {
    rawMessageDelivery: true,
  })
);

Of course, the function in AWS Lambda needs permission to access Amazon SNS and AWS Key Management Service as well:

/**
 * Grant permission to access AWS Key Management Service with AWS Lambda function
 */

key.grant(func, "kms:Decrypt", "kms:GenerateDataKey");

/**
 * Grant permission to access Amazon SNS topic with AWS Lambda function
 */

topic.grantPublish(func);

AWS Lambda

If you use TypeScript to create functions in AWS Lambda, you can use @aws-cdk/aws-lambda-nodejs to compile and bundle your source code without any additional configuration.

To test the subscription, publish a message to Amazon SNS using the AWS SDK:

import { Handler } from "aws-lambda";
import * as AWS from "aws-sdk";

export const run: Handler = async (event) =>
  new AWS.SNS({ apiVersion: "2010-03-31" })
    .publish({
      TopicArn: process.env.TOPIC_ARN,
      Message: JSON.stringify(event),
    })
    .promise();

Using this architecture, you can securely process messages in SQS and subscribe to SNS topics. Of course, you could split this up using multiple keys in AWS Key Management Service or configure the resource policy to allow cross-account usage for your key.