fix #251: verify signatures of SNS messages
This commit is contained in:
Родитель
dd49eddb0b
Коммит
5deb41a54e
|
@ -19,9 +19,6 @@ SMTP_URL=""
|
|||
EMAIL_FROM=""
|
||||
# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/using-configuration-sets.html
|
||||
SES_CONFIG_SET=""
|
||||
# username and password to authenticate messages coming back from SES
|
||||
SES_USERNAME=""
|
||||
SES_PASSWORD=""
|
||||
# 1: only log messages coming back from SES
|
||||
SES_NOTIFICATION_LOG_ONLY=
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ const kEnvironmentVariables = [
|
|||
"SMTP_URL",
|
||||
"EMAIL_FROM",
|
||||
"SES_CONFIG_SET",
|
||||
"SES_USERNAME",
|
||||
"SES_PASSWORD",
|
||||
"SES_NOTIFICATION_LOG_ONLY",
|
||||
"OAUTH_AUTHORIZATION_URI",
|
||||
"OAUTH_TOKEN_URI",
|
||||
|
|
|
@ -1,23 +1,32 @@
|
|||
"use strict";
|
||||
|
||||
const MessageValidator = require("sns-validator");
|
||||
|
||||
const DB = require("../db/DB");
|
||||
|
||||
|
||||
async function notification(req, res) {
|
||||
try {
|
||||
const notification = JSON.parse(req.body);
|
||||
// TODO: verifyNotification(notification) or use http basic auth
|
||||
await handleNotification(notification);
|
||||
const validator = new MessageValidator();
|
||||
|
||||
res.status(200).json(
|
||||
{status: "OK"}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("SES notification error: ", e);
|
||||
res.status(500).json(
|
||||
{info: "Internal error."}
|
||||
);
|
||||
}
|
||||
|
||||
async function notification(req, res) {
|
||||
const message = JSON.parse(req.body);
|
||||
return new Promise((resolve, reject) => {
|
||||
validator.validate(message, async (err, message) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
const body = "Access denied. " + err.message;
|
||||
res.status(401).send(body);
|
||||
return reject(body);
|
||||
}
|
||||
|
||||
await handleNotification(message);
|
||||
|
||||
res.status(200).json(
|
||||
{status: "OK"}
|
||||
);
|
||||
return resolve("OK");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const auth = require("basic-auth");
|
||||
|
||||
const AppConstants = require("./app-constants");
|
||||
|
||||
|
||||
function checkSESAuth (user, pass) {
|
||||
return user === AppConstants.SES_USERNAME && pass === AppConstants.SES_PASSWORD;
|
||||
}
|
||||
|
||||
|
||||
function sesAuth (req, res, next) {
|
||||
const credentials = auth(req);
|
||||
if (!credentials || !checkSESAuth(credentials.name, credentials.pass)) {
|
||||
res.setHeader("WWW-Authenticate", `Basic realm="${AppConstants.SERVER_URL}"`);
|
||||
res.status(401).send("Access denied");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helps handle errors for all async route controllers
|
||||
// See https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
|
||||
|
@ -30,6 +10,5 @@ function asyncMiddleware (fn) {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
sesAuth,
|
||||
asyncMiddleware,
|
||||
};
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -27,6 +27,7 @@
|
|||
"nodemailer": "^4.6.4",
|
||||
"nodemailer-express-handlebars": "^3.0.0",
|
||||
"pg": "^7.4.3",
|
||||
"sns-validator": "^0.3.4",
|
||||
"uuid": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -38,6 +39,7 @@
|
|||
"jest": "^23.5.0",
|
||||
"jest-tap-reporter": "^1.9.0",
|
||||
"node-mocks-http": "^1.7.0",
|
||||
"nodemon": "^1.18.3",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"nsp": "^3.2.1",
|
||||
"stylelint": "^9.2.0",
|
||||
|
@ -49,6 +51,11 @@
|
|||
"homepage": "https://github.com/mozilla/blurts-server",
|
||||
"license": "MPL-2.0",
|
||||
"main": "server.js",
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
"version.json"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"**/*.js",
|
||||
|
|
|
@ -4,7 +4,6 @@ const bodyParser = require("body-parser");
|
|||
const express = require("express");
|
||||
|
||||
const AppConstants = require("../app-constants");
|
||||
const {sesAuth} = require("../middleware");
|
||||
const {notification} = require("../controllers/ses");
|
||||
|
||||
|
||||
|
@ -16,7 +15,6 @@ if (AppConstants.SES_NOTIFICATION_LOG_ONLY) {
|
|||
console.log("SES Notification request body: ", req.body);
|
||||
});
|
||||
} else {
|
||||
router.use("/notification", sesAuth);
|
||||
router.post("/notification", textParser, notification);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Type" : "Notification",
|
||||
"MessageId" : "a33de653-86a5-509e-a94f-b5b8670d343e",
|
||||
"TopicArn" : "arn:aws:sns:eu-west-1:142069644989:Breach-Alerts-SNS-Bounce-Handler-Test-EU",
|
||||
"Subject" : "Amazon SES Email Event Notification",
|
||||
"Message" : "{\"eventType\":\"Complaint\",\"complaint\":{\"complainedRecipients\":[{\"emailAddress\":\"complaint@simulator.amazonses.com\"}],\"timestamp\":\"2018-08-13T19:43:36.073Z\",\"feedbackId\":\"0102016534d0ae14-2f329c45-acec-41eb-9bfb-fde35bdd6dfd-000000\",\"userAgent\":\"Amazon SES Mailbox Simulator\",\"complaintFeedbackType\":\"abuse\",\"arrivalDate\":\"2018-08-13T19:43:36.073Z\"},\"mail\":{\"timestamp\":\"2018-08-13T19:43:34.192Z\",\"source\":\"breach-alerts@mozilla.com\",\"sourceArn\":\"arn:aws:ses:eu-west-1:142069644989:identity/breach-alerts@mozilla.com\",\"sendingAccountId\":\"142069644989\",\"messageId\":\"0102016534d0a730-846fdbf2-14f0-4f28-8a94-e8f3a0ef5357-000000\",\"destination\":[\"complaint@simulator.amazonses.com\"],\"headersTruncated\":false,\"headers\":[{\"name\":\"Received\",\"value\":\"from [127.0.0.1] (ec2-54-195-19-74.eu-west-1.compute.amazonaws.com [54.195.19.74]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-2762311919) id bqzqHO8xSrImZmxP8QfY for complaint@simulator.amazonses.com; Mon, 13 Aug 2018 19:43:34 +0000 (UTC)\"},{\"name\":\"Content-Type\",\"value\":\"text/html\"},{\"name\":\"X-Ses-Configuration-Set\",\"value\":\"Breach-Alerts-Bounce-Handler-Test-EU\"},{\"name\":\"From\",\"value\":\"breach-alerts@mozilla.com\"},{\"name\":\"To\",\"value\":\"complaint@simulator.amazonses.com\"},{\"name\":\"Subject\",\"value\":\"Verify your email address to subscribe to Firefox Monitor.\"},{\"name\":\"Message-ID\",\"value\":\"<315963a8-05e7-88ab-2657-7841295f18fd@mozilla.com>\"},{\"name\":\"Content-Transfer-Encoding\",\"value\":\"quoted-printable\"},{\"name\":\"Date\",\"value\":\"Mon, 13 Aug 2018 19:43:34 +0000\"},{\"name\":\"MIME-Version\",\"value\":\"1.0\"}],\"commonHeaders\":{\"from\":[\"breach-alerts@mozilla.com\"],\"date\":\"Mon, 13 Aug 2018 19:43:34 +0000\",\"to\":[\"complaint@simulator.amazonses.com\"],\"messageId\":\"0102016534d0a730-846fdbf2-14f0-4f28-8a94-e8f3a0ef5357-000000\",\"subject\":\"Verify your email address to subscribe to Firefox Monitor.\"},\"tags\":{\"ses:operation\":[\"SendSmtpEmail\"],\"ses:configuration-set\":[\"Breach-Alerts-Bounce-Handler-Test-EU\"],\"ses:source-ip\":[\"54.195.19.74\"],\"ses:from-domain\":[\"mozilla.com\"],\"ses:caller-identity\":[\"groovecoder-breach-alerts-test\"]}}}\n",
|
||||
"Timestamp" : "2018-08-13T19:43:36.158Z",
|
||||
"SignatureVersion" : "1",
|
||||
"Signature" : "f7SzPsZND/a3eGyaXRXcpXqP+L8z5eNIdB3K0kSlG+Sq4x+QylTut8Gvvg6QwSOcesuvHlVmmxdRMQWn1hndSAX1/qzQEASs8ousZUDs8CES3MHsJP9jlzZRZBYQ0354Mssn11jm40rN1qKBMdYjTq30ArGCYfipugLgvvNK+v9zPl4TrJkE5Y3vi+UN7AK0rAKty/udPOuZ/M8ghj4tlzGnC8HlOvj42be8CInY2WTAY/u2TdGhwfT29RtY7+zhS4FlNNDvs440M+4pfHhvo+U6XkWcTXw1oejjNNMh6RS4jkofB3vbiH0lMdV0do2MhQOWCYQ3Un6jIHfOpyQGQ==",
|
||||
"SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-eaea6120e66ea12e88dcd8bcbddca752.pem",
|
||||
"UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:142069644989:Breach-Alerts-SNS-Bounce-Handler-Test-EU:44888dbc-b2bc-4089-9133-f85be406d0d3"
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"MessageId" : "73ed7828-153e-5e60-b776-2bd6480d2f7e",
|
||||
"Type" : "Notification",
|
||||
"MessageId" : "d53af48a-ce18-5709-9579-a7611a14918d",
|
||||
"TopicArn" : "arn:aws:sns:eu-west-1:142069644989:Breach-Alerts-SNS-Bounce-Handler-Test-EU",
|
||||
"Subject" : "Amazon SES Email Event Notification",
|
||||
"Message" : "{\"eventType\":\"Bounce\",\"bounce\":{\"bounceType\":\"Permanent\",\"bounceSubType\":\"General\",\"bouncedRecipients\":[{\"emailAddress\":\"bounce@simulator.amazonses.com\",\"action\":\"failed\",\"status\":\"5.1.1\",\"diagnosticCode\":\"smtp; 550 5.1.1 user unknown\"}],\"timestamp\":\"2018-08-13T18:45:32.904Z\",\"feedbackId\":\"01020165349b87e6-6170ebb9-9d69-40b0-9b84-51214b7c89dd-000000\",\"reportingMTA\":\"dsn; a4-11.smtp-out.eu-west-1.amazonses.com\"},\"mail\":{\"timestamp\":\"2018-08-13T18:45:32.004Z\",\"source\":\"breach-alerts@mozilla.com\",\"sourceArn\":\"arn:aws:ses:eu-west-1:142069644989:identity/breach-alerts@mozilla.com\",\"sendingAccountId\":\"142069644989\",\"messageId\":\"01020165349b84e4-529247a4-4204-4c85-bd99-6b283bd85952-000000\",\"destination\":[\"bounce@simulator.amazonses.com\"],\"headersTruncated\":false,\"headers\":[{\"name\":\"Received\",\"value\":\"from [127.0.0.1] (ec2-54-195-19-74.eu-west-1.compute.amazonaws.com [54.195.19.74]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-2762311919) id Yd1vUB46cMuDE1VCjMl4 for bounce@simulator.amazonses.com; Mon, 13 Aug 2018 18:45:31 +0000 (UTC)\"},{\"name\":\"Content-Type\",\"value\":\"text/html\"},{\"name\":\"X-Ses-Configuration-Set\",\"value\":\"Breach-Alerts-Bounce-Handler-Test-EU\"},{\"name\":\"From\",\"value\":\"breach-alerts@mozilla.com\"},{\"name\":\"To\",\"value\":\"bounce@simulator.amazonses.com\"},{\"name\":\"Subject\",\"value\":\"Verify your email address to subscribe to Firefox Monitor.\"},{\"name\":\"Message-ID\",\"value\":\"<c15a7b7a-653e-afa9-375c-9bbfa959699a@mozilla.com>\"},{\"name\":\"Content-Transfer-Encoding\",\"value\":\"quoted-printable\"},{\"name\":\"Date\",\"value\":\"Mon, 13 Aug 2018 18:45:31 +0000\"},{\"name\":\"MIME-Version\",\"value\":\"1.0\"}],\"commonHeaders\":{\"from\":[\"breach-alerts@mozilla.com\"],\"date\":\"Mon, 13 Aug 2018 18:45:31 +0000\",\"to\":[\"bounce@simulator.amazonses.com\"],\"messageId\":\"01020165349b84e4-529247a4-4204-4c85-bd99-6b283bd85952-000000\",\"subject\":\"Verify your email address to subscribe to Firefox Monitor.\"},\"tags\":{\"ses:operation\":[\"SendSmtpEmail\"],\"ses:configuration-set\":[\"Breach-Alerts-Bounce-Handler-Test-EU\"],\"ses:source-ip\":[\"54.195.19.74\"],\"ses:from-domain\":[\"mozilla.com\"],\"ses:caller-identity\":[\"groovecoder-breach-alerts-test\"]}}}\n",
|
||||
"Timestamp" : "2018-08-13T18:45:32.984Z",
|
||||
"Message" : "{\"eventType\":\"Bounce\",\"bounce\":{\"bounceType\":\"Permanent\",\"bounceSubType\":\"General\",\"bouncedRecipients\":[{\"emailAddress\":\"bounce@simulator.amazonses.com\",\"action\":\"failed\",\"status\":\"5.1.1\",\"diagnosticCode\":\"smtp; 550 5.1.1 user unknown\"}],\"timestamp\":\"2018-08-27T20:11:31.327Z\",\"feedbackId\":\"010201657d034625-c8dfb425-9962-4104-b480-8cd1761aa963-000000\",\"reportingMTA\":\"dsn; a4-12.smtp-out.eu-west-1.amazonses.com\"},\"mail\":{\"timestamp\":\"2018-08-27T20:11:30.533Z\",\"source\":\"breach-alerts@mozilla.com\",\"sourceArn\":\"arn:aws:ses:eu-west-1:142069644989:identity/breach-alerts@mozilla.com\",\"sendingAccountId\":\"142069644989\",\"messageId\":\"010201657d034365-d2209285-9b0c-438e-b75d-109b59764ca9-000000\",\"destination\":[\"bounce@simulator.amazonses.com\"],\"headersTruncated\":false,\"headers\":[{\"name\":\"Received\",\"value\":\"from [127.0.0.1] (ec2-54-74-203-173.eu-west-1.compute.amazonaws.com [54.74.203.173]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-3236554995) id 8g3PpKxw1KFDvWCHWsUS for bounce@simulator.amazonses.com; Mon, 27 Aug 2018 20:11:30 +0000 (UTC)\"},{\"name\":\"Content-Type\",\"value\":\"text/html\"},{\"name\":\"X-Ses-Configuration-Set\",\"value\":\"Breach-Alerts-Bounce-Handler-Test-EU\"},{\"name\":\"From\",\"value\":\"breach-alerts@mozilla.com\"},{\"name\":\"To\",\"value\":\"bounce@simulator.amazonses.com\"},{\"name\":\"Subject\",\"value\":\"Verify your email address to subscribe to Firefox Monitor.\"},{\"name\":\"Message-ID\",\"value\":\"<ef207157-e80a-89f5-a2f1-441c99bafd29@mozilla.com>\"},{\"name\":\"Content-Transfer-Encoding\",\"value\":\"quoted-printable\"},{\"name\":\"Date\",\"value\":\"Mon, 27 Aug 2018 20:11:30 +0000\"},{\"name\":\"MIME-Version\",\"value\":\"1.0\"}],\"commonHeaders\":{\"from\":[\"breach-alerts@mozilla.com\"],\"date\":\"Mon, 27 Aug 2018 20:11:30 +0000\",\"to\":[\"bounce@simulator.amazonses.com\"],\"messageId\":\"010201657d034365-d2209285-9b0c-438e-b75d-109b59764ca9-000000\",\"subject\":\"Verify your email address to subscribe to Firefox Monitor.\"},\"tags\":{\"ses:operation\":[\"SendSmtpEmail\"],\"ses:configuration-set\":[\"Breach-Alerts-Bounce-Handler-Test-EU\"],\"ses:source-ip\":[\"54.74.203.173\"],\"ses:from-domain\":[\"mozilla.com\"],\"ses:caller-identity\":[\"groovecoder-breach-alerts-test\"]}}}\n",
|
||||
"Timestamp" : "2018-08-27T20:11:31.445Z",
|
||||
"SignatureVersion" : "1",
|
||||
"Signature" : "F8teyD3keOj0bJr7dV4xVLzEUAfF90aEXbQp92Pg5tXrMldr1VQ3r6mDn/yl2nDT4pD3pOkCG9EGoqk2nvwv7lNiIZl87yxbKmSA/euFtbneuLB8R2Bp9ebCIYwc/xVRXEBZshtWLR8QFfB5W6TfxHndXBIMsqx68USPJMfngnMIHpIk7ztwJji6qN2Cy4dhImGkK6/sDRSDqM00iyHVNdheGxycQKyeN4vos8khlQVFwCxH0IsjrwAZBhtwwOuOLJ+OEC+FCKT6lMGpjhm4WuG9jrB6s6uQKfnl8nEjpga0Qyb0Smr1yLMsfUgAzdudAQ8hyyMlUCGP3xBJ3tPQHw==",
|
||||
"Signature" : "qVG4+hbkFeJzkrsxsCUAjmmhv3GTeF4TKqPKBcN96L0BQbsfrZkvYv1uhpGwv4ocTZjLyEo/qNNQyq4fXNzEaqQQxGi4540hZ7S06gB8jiq79S6s9CvlZISJ9jVsiFrhFOQRQ6APzqJRAQRO0lWXCKx6+fwzIdF8OOrv6lHIdx9FQZDWsAMypURXftjTkihzS7BuIIayI3C1l0mPl7C6Vx9psYQAgTq7HFMYosVgKSPdlxoNI6vdpl6OHApM5Z5BpjCA7wfCcvikJJF3tQ7/JQLTMoNZD8n/WJf0FXqjDbg3gd8MtYqIhd5GaT1rKtQCC55B67EiaWTkprIOBECF/w==",
|
||||
"SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-eaea6120e66ea12e88dcd8bcbddca752.pem",
|
||||
"UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:142069644989:Breach-Alerts-SNS-Bounce-Handler-Test-EU:44888dbc-b2bc-4089-9133-f85be406d0d3"
|
||||
"UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:142069644989:Breach-Alerts-SNS-Bounce-Handler-Test-EU:e1ccb7b4-4946-472c-8482-82ec7d65bb23"
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ require("./resetDB");
|
|||
const testNotifications = new Map();
|
||||
testNotifications.set("bounce", require("./ses-bounce-notification.json"));
|
||||
testNotifications.set("complaint", require("./ses-complaint-notification.json"));
|
||||
testNotifications.set("invalid", require("./invalid-signature-ses-complaint-notification.json"));
|
||||
|
||||
|
||||
const createRequestBody = function(notificationType, bounceSubType = null) {
|
||||
|
@ -71,3 +72,25 @@ test("ses notification with Complaint unsubscribes recipient", async () => {
|
|||
subscribers = await DB.getSubscribersByHashes([getSha1(testEmail)]);
|
||||
expect(subscribers.length).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
test("ses notification with invalid signature responds with error and doesn't change subscribers", async () => {
|
||||
const testEmail = "complaint@simulator.amazonses.com";
|
||||
|
||||
await DB.addSubscriber(testEmail);
|
||||
let subscribers = await DB.getSubscribersByHashes([getSha1(testEmail)]);
|
||||
expect(subscribers.length).toEqual(1);
|
||||
|
||||
const req = httpMocks.createRequest({
|
||||
method: "POST",
|
||||
url: "/ses/notification",
|
||||
body: createRequestBody("invalid"),
|
||||
});
|
||||
const resp = httpMocks.createResponse();
|
||||
|
||||
await expect(ses.notification(req, resp)).rejects.toMatch("invalid");
|
||||
expect(resp.statusCode).toEqual(401);
|
||||
|
||||
subscribers = await DB.getSubscribersByHashes([getSha1(testEmail)]);
|
||||
expect(subscribers.length).toEqual(1);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче