We launched the cloudonaut blog in 2015. Since then, we have published 389 articles, 90 podcast episodes, and 98 videos. Our weekly newsletter keeps you up-to-date. Subscribe now!.
Subscribe
Our weekly newsletter keeps you up-to-date. Subscribe now! It's all free.
Are you implementing an HTTP/HTTPS endpoint for SNS? If so, you should definetly verify the incoming messages. Otherwise, anyone on the Internet can deliver messages to your HTTP/HTTPS endpoint. Which is a security risk.
How do you verify incoming messages? The SNS documentation answers this question:
You should verify the authenticity of a notification, subscription confirmation, or unsubscribe confirmation message sent by Amazon SNS.
In a nutshell, each SNS message contains a signature that we have to verify.
The npm module sns-validator does the job. Unfortunately, the module is old and lacks support for save caching and certificate download retries. Therefore, I decided to implement this on my own, which wasn’t as hard as expected. Let’s get started.
First, you need to install a few dependencies:
request and requestretry to perform HTTP(S) requests with retries
lru-cache to safely cache certificates without running out of memory
Install the modules with:
npm i request requestretry lru-cache
Create a new JavaScript file (e.g., index.js) and import the dependencies we need:
We also have to come up with a way to download the certificate that we need to verify the signature. The certificate is attached to the message in the form of a URL. We have to download the certificate before we can verify the signature. Downloading things can fail for many reasons. Therefore, we retry failed download requests. To optimize for performance, we also want to cache downloaded certificates. Let’s look at the code.
const CERT_CACHE = new LRU({max: 5000, maxAge: 1000 * 60});
const verify = crypto.createVerify('sha1WithRSAEncryption'); fieldsForSignature(message.Type).forEach(key => { if (key in message) { verify.write(`${key}\n${message[key]}\n`); } }); verify.end(); const result = verify.verify(certificate, message.Signature, 'base64'); cb(null, result);
You can test the code with a message like this:
validate({ Type: 'Notification', MessageId: '4c807a89-9ef9-543b-bfab-2f4ed41e91b4', TopicArn: 'arn:aws:sns:us-east-1:853553028582:marbot-dev-alert-Topic-8CT7ZJRNSA5Y', Subject: 'INSUFFICIENT_DATA: "insufficient test" in US East (N. Virginia)', Message: '{"AlarmName":"insufficient test","AlarmDescription":null,"AWSAccountId":"853553028582","NewStateValue":"INSUFFICIENT_DATA","NewStateReason":"tets","StateChangeTime":"2019-08-09T10:19:19.614+0000","Region":"US East (N. Virginia)","OldStateValue":"OK","Trigger":{"MetricName":"CallCount2","Namespace":"AWS/Usage","StatisticType":"Statistic","Statistic":"AVERAGE","Unit":null,"Dimensions":[{"value":"API","name":"Type"},{"value":"PutMetricData","name":"Resource"},{"value":"CloudWatch","name":"Service"},{"value":"None","name":"Class"}],"Period":300,"EvaluationPeriods":1,"ComparisonOperator":"GreaterThanThreshold","Threshold":1.0,"TreatMissingData":"- TreatMissingData: missing","EvaluateLowSampleCountPercentile":""}}', Timestamp: '2019-08-09T10:19:19.644Z', SignatureVersion: '1', Signature: 'gnCKAUYX6YlBW3dkOmrSFvdB6r82Q2He+7uZV9072sdCP0DSaR46ka/4ymSdDfqilqxjJ9hajd9l7j8ZsL98vYdUbut/1IJ2hsuALF9nd/HwNLPPWvKXaK/Y3Hp57izOpeBAkuR6koitSbXX50lEj7FraaMVQfpexm01z7IUcx4vCCvZBTdQLbkWw+TYWkWNsMrqarW39zy474SmTBCSZlz1eoV6tCwYk2Z2G2awiXpnfsQRRZvHn4ot176oY+ADAFJ0sIa44effQXq+tAWE6/Z3M5rjtfg6OULDM+NGEmnVZL3xyWK8bIzB48ZclQo3ZsvLPGmCNQLlFpaP/3fGGg==', SigningCertURL: 'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-6aad65c2f9911b05cd53efda11f913f9.pem', UnsubscribeURL: 'https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:853553028582:marbot-dev-alert-Topic-8CT7ZJRNSA5Y:86a160f0-c3c5-4ae1-ae50-2903eede0af1' }, (err, result) => { if (err) { console.log(err); } else { console.log('result', result); } });
Summary
You should verify the authenticity of a message sent by Amazon SNS. The SNS documentation provides an in-depth description of the needed steps which can be implemented in Node.js as shown in this blog post.
Michael Wittig
I’ve been building on AWS since 2012 together with my brother Andreas. We are sharing our insights into all things AWS on cloudonaut and have written the book AWS in Action. Besides that, we’re currently working on bucketAV, HyperEnv for GitHub Actions, and marbot.
Here are the contact options for feedback and questions.