Lambda SES forwarding function AWS - amazon-web-services

I'm using this email AWS SES forwarding script on a registered domain in AWS, along with releavant MX and TXT records. Personal emails im testing this with are also authorised as im in sandbox mode. I've set a rule in SES, pushing emails to an s3 bucket. After i call the aformentioned lambda function to redirect emails to anther gmail account.
var defaultConfig = {
fromEmail: "info#domainInRoute53.co.uk",
subjectPrefix: "",
emailBucket: "s3.xxxxx.xxxxx",
emailKeyPrefix: "emails/",
forwardMapping: {
"info#domainInRoute53.co.uk": [
"personal#gmail.com",
"personal#hotmail.co.uk"
]
}
}; ..etc..
It writes to the s3 bucket but i cant seem to get it to forward to the personal email addresses.
I've tried numerous edits on the inline and managed policies in the IAM roles for the lamdba function to select, as suggested in the link I'm following above on step 2. Any ideas why this is failing?

Related

Is it possible to send emails via AWS SES on AWS LightSail instance?

I have tried sending emails on my LightSail instance via AWS SES using the following Node.js JavaScript AWS SDK code, but it failed with the following error message. The email sending code works fine on my development computer. (The email sending code is from https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/ses-examples-sending-email.html.)
(code here)
import { readFile } from 'fs/promises';
import * as path from 'path';
import { SESClient } from "#aws-sdk/client-ses";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const awsConfigFileFullName = "aws_config.json";
let awsConfigFileFullPath = path.join(__dirname, awsConfigFileFullName);
const awsConfig = await readFile(awsConfigFileFullPath).then(json => JSON.parse(json)).catch(() => null);
aws_ses_client = new SESClient({ region: awsConfig.region, accessKeyId: awsConfig.accessKeyId, secretAccessKey: awsConfig.secretAccessKey });
const createSendEmailCommand = (toAddress, fromAddress, htmlContent, textContent, emailSubject) => {
return new SendEmailCommand({
Destination: {
/* required */
CcAddresses: [
/* more items */
],
ToAddresses: [
toAddress,
/* more To-email addresses */
],
},
Message: {
/* required */
Body: {
/* required */
Html: {
Charset: "UTF-8",
Data: htmlContent,
},
Text: {
Charset: "UTF-8",
Data: textContent,
},
},
Subject: {
Charset: "UTF-8",
Data: emailSubject,
},
},
Source: emailSenderName + '<' + fromAddress + '>',
ReplyToAddresses: [
/* more items */
],
});
};
const sendEmailCommand = createSendEmailCommand(
recipientEmailAddress,
senderEmailAddress,
htmlEmailContent,
textEmailContent,
emailSubject
);
try {
await aws_ses_client.send(sendEmailCommand);
} catch (e) {
console.error("Failed to send email.", e);
}
(error here)
AccessDenied: User arn:aws:sts::(some number):assumed-role/AmazonLightsailInstanceRole/i-(some alphanumeric number)is not authorized to perform ses:SendEmail' on resource
`arn:aws:ses:us-east-1:(some number):identity/(recipient email
address)'
After doing some search online on the issue, thinking that the error was caused by port 25 restriction on Lightsail instance restriction (https://aws.amazon.com/premiumsupport/knowledge-center/lightsail-port-25-throttle/), I sent a restriction removal request to AWS, but the request was declined, telling me to "consider looking into the Simple Email Service". I sent a reply asking if sending emails via AWS SES was possible on LightSail instance, but got a reply saying "we cannot grant your request"; I feel that the second reply was either automated or wasn't even thoroughly read by the person who reviewed the email.
I have a multisite WordPress installed on my LightSail webserver, which can send emails via AWS SES with WP Mail SMTP plugin, with TLS encryption using SMTP port 587. I think this is a proof that emails can be sent on a LightSail instance via AWS SES. Emails do get sent by my WordPress on my LightSail webserver, everyday, using my AWS SES SMTP credentials; so, maybe the AWS SES SMTP server must be directly contacted in the LightSail instance email sending code to send emails, instead of using the authenticated SES client object in the code?
I'm thinking that maybe the assumed role AmazonLightsailInstanceRole doesn't have AWS SES email sending allowed. I've checked my AWS IAM web console, and there was no role named AmazonLightsailInstanceRole; it doesn't look like I can modify the policy on the assumed role AmazonLightsailInstanceRole.
Can AWS SES email-sending permission be granted to the assumed role AmazonLightsailInstanceRole? If so, how?
Is it possible to send emails via AWS SES on an AWS LightSail instance? If so, how can it be done in Node.js JavaScript AWS SDK code? Any verifications, references and/or directions would be very helpful.
Sounds like an IAM issue - the instance role is the fallback option if you dont pass your own credentials. Lines 6..8 of the code is looking for a file (aws_config.json) which contains an API key and secret and a default region, then on line 9 its passing those values into SESClient. When you finally call aws_ses_client.send(sendEmailCommand) it will use the instance default credentails provided by lightsail which wont have access to resources in your aws account like SES. Check the following:
Does file aws_config.json exist?
Does it contain api key/secret/region for a valid user in your AWS account?
Does that user have a suitable IAM policy including ses:SendEmail and possibly other iam permissions?
FYI catch(() => null) could be hiding an error - you should use console.log in your catch() statements to write an error (at least while your debugging).
UPDATE
While testing my Node.js Express web app on an EC2 instance, to resolve the error I encountered, I came across the following: "Could not load credentials from any providers" while using dynamodb locally in Node.
Because I was using #aws-sdk/ses-client (version 3), not version 2, I should've used the following:
aws_s3_client = new S3Client({ region: awsConfig.region, credentials: {accessKeyId: awsConfig.accessKeyId, secretAccessKey: awsConfig.secretAccessKey }});
aws_ses_client = new SESClient({ region: awsConfig.region, credentials: {accessKeyId: awsConfig.accessKeyId, secretAccessKey: awsConfig.secretAccessKey }});
Using the above code enabled using both AWS SES and S3 from my web app running on a LightSail instance. So, the answer is, yes, it is possible to send emails via AWS SES on AWS LightSail instance.

Can I verify my domain once in AWS SES and allow my other accounts to use this?

I have successfully verified my domain in AWS SES on one account. I would like my other accounts to send from this domain, and as such I have created an Authorization policy with this in mind (via Terraform):
data "aws_iam_policy_document" "this" {
statement {
actions = ["ses:SendEmail", "ses:SendRawEmail"]
resources = ["${aws_ses_domain_identity.this.arn}"]
effect = "Allow"
principals {
identifiers = ["123456789100", "1234567890101", "1234567890102"]
type = "AWS"
}
}
}
and attached this to the verified domain.
However, when I try to send from another account (listed in the identifiers group above) I am unable to send, being given an error:
554 Access denied: User `arn:aws:iam::1234567890101:user/ses_user' is not authorized to perform `ses:SendRawEmail' on resource `arn:aws:ses:eu-west-1:1234567890101:identity/my.name#domain.com'
The ses_user on that account has the following IAM policy (again, in TF):
data "aws_iam_policy_document" "ses_user" {
statement {
actions = ["ses:SendEmail", "ses:SendRawEmail"]
resources = ["*"]
}
}
I was under the impression that cross-account email sending was possible here. Am I mistaken? Perhaps I am missing something.
Thanks
sources:
https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-policy-examples.html
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy
No responses, but I understand that it is possible to do this with delegated senders. The SMTP sender user needs to include X headers in the email when it is sent (these are stripped by SES before being sent). The verified domain then simply requires the SES Auth policy to allow the account ID of the delegated account to send emails. This can be bundled with other conditions (ie can only send to certain destinations) in the same policy, as per here:
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-identity-owner-tasks-policy.html
See also:
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks-email.html
I chose not to go down this road, but it is possible.
For an example of how to insert Headers, you might be using Postfix for example in which case use header_checks with the prepend directive:
http://www.postfix.org/header_checks.5.html

Can I move an AWS Lambda site to a private domain?

I'm using Zappa to deploy a Flask app. It works (site). Obviously I'd like to not have it stuck behind the aws domain and put it on my personal domain.
Everything I'm searching keeps talking about hosting a Lambda site with S3 and API Gateway. Is there no way to just deploy my little app to a custom domain?
Edit
Following #mislav's answer, I was able to get my google domain working with AWS. But when I try to finish by running zappa certify I get an error about the domain existing:
raise error_class(parsed_response, operation_name)
botocore.errorfactory.BadRequestException: An error occurred
(BadRequestException) when calling the CreateDomainName operation: The
domain name you provided already exists.
My zappa_settings.json is
{
"dev": {
"app_function": "ping_app.app",
"aws_region": "us-west-1",
"profile_name": "Breuds",
"project_name": "breuds",
"runtime": "python3.6",
"s3_bucket": "zappa-ping-redshift",
"slim_handler": true,
"certificate_arn": "arn:aws:acm:us-east-1:010174774769:certificate/3a92c204-5788-42fc-bc65-74aaae8c1b3f",
"domain": "breuds.com"
}
}
I am beginning to think I am doing something convoluted on my domain side. I'm using Google Domains (since I have a custom domain for my email, just using that), but that seems to cause a headache trying to get AWS to talk to it.
It’s actually explained in zappa’s readme.
Deploying to a Domain With AWS Certificate Manager
Amazon provides their own free alternative to Let's Encrypt called AWS Certificate Manager (ACM). To use this service with Zappa:
Verify your domain in the AWS Certificate Manager console.
In the console, select the N. Virginia (us-east-1) region and request a certificate for your domain or subdomain (sub.yourdomain.tld), or request a wildcard domain (*.yourdomain.tld).
Copy the entire ARN of that certificate and place it in the Zappa setting certificate_arn.
Set your desired domain in the domain setting.
Call $ zappa certify to create and associate the API Gateway distribution using that certificate.
There are also instructions to use your existing certificate etc.
Don’t let it fool you, the title of the section makes it sound like it’s only about certificates, but there are detailed instructions regarding using you own domain.
Edit:
I’m including my own zappa settings for reference.
{
"common": {
"app_function": "app.__hug_wsgi__",
"aws_region": "eu-central-1",
"s3_bucket": "excuse-generator",
"profile_name": "mislavcimpersak",
"remote_env": "s3://excuse-generator/secrets.json",
"certificate_arn": "arn:aws:acm:us-east-1:500819636056:certificate/3edb9c1c-12c5-4601-89a9-dc42df840fa6"
},
"prod": {
"extends": "common",
"domain": "function.xkcd-excuse.com"
},
"dev": {
"extends": "common",
"debug": true,
"keep_warm": false,
"domain": "function-dev.xkcd-excuse.com"
}
}
Step by step guide for domain bought on namecheap.com and served through cloudflare.com
buy domain on namecheap.com/use existing
register new site on cloudflare.com/use existing
cloudflare will give you (most probably) two nameservers
enter under https://ap.www.namecheap.com/domains/domaincontrolpanel/xkcd-excuse.com/domain DNS - choose "Custom DNS" nameservers given on cloudflare
go to AWS ACM and request a new certificate (certificate must be created in us-east-1 region)
enter if necessary multiple subdomains (foo.example.com & foo-dev.example.com)
make note of ACM ARN given from AWS management console
in zappa_settings.json enter under certificate_arn your ARN
in zappa_settings.json under route53_enabled put false - this is a must
in zappa_settings.json under domain enter domain for each stage ie. foo.example.com and foo-dev.example.com
run zappa certify <stage_name>
it should say: "Created a new domain name with supplied certificate. Please note that it can take up to 40 minutes for this domain to be created and propagated through AWS, but it requires no further work on your part.
Certificate updated!"
go to CloudFlare DNS interface and enter
CNAME: foo - i3jtsjkdeu4wxo.cloudfront.net
CNAME: foo-dev - d2jtsjkdeu4wxo.cloudfront.net
wait for 40 minutes and check your domain(s), they should serve your Lambda function
You might not need API Gateway. It will depend on site functionality. You can host a static website using S3 with a custom domain. You can include client-side javascript(angular/react etc..) API Gateway is really for handling http requests and passing it on to defined resources(Lambda or others).
If your site will require backend capability then you have few options.
1) Build lambda functions and then use API Gateway(REST API) to interact with these functions.
yourhostname.com -> S3 -> API Gateway(aws hostname) -> lambda
2) Use AWS JS SDK to interact with your lambda functions directly.
yourhostname.com -> S3 -> AWS SDK -> lambda
3) Use API Gateway and then forwarding to Lambda or self hosted web server(flask node) using EC2 instance
yourhostname.com -> API Gateway(aws hostname) -> lambda
yourhostname.com -> self hosted web server
Here is a picture that might make it a bit clearer.
Correct me if I am wrong flask is used for backend development to build microservices. To host it you would use either build lambda functions or host it on your own instance.
https://andrich.blog/2017/02/12/first-steps-with-aws-lambda-zappa-flask-and-python/
Let me know if this helps.

How do I download my IoT certificate created via CloudFormation?

I'm using AWS CloudFormation to create an IoT Thing, Policy and Certificate. My stack creates successfully, however, I can't access the certificate file that CloudFormation creates.
Looking at the aws docs here the only output you can get from the certificate via CloudFormation is the ARN and the Certificate ID. However, there is no way to retrieve your certificate using the ARN or Certificate ID that I can see.
If you upload your certificate signing request (CSR) via the AWS IoT Console, it displays a download link that you can get your certificate file.
Unfortunately I need to use CloudFormation to create the IoT Certificate. However it looks like you can download the certificate after it's been created. Specifically it states:
Certificates can be retrieved at any time
I have been unsuccessfully scouring the docs and web interface to figure out how I can download my certificate "at any time". I'm relatively new to the whole world of certs and private keys so hopefully I missed something easy.
Does anyone know if it is possible to get your certificate from an IoT Certificate created by CloudFormation?
Certificates created using CloudFormation (Via a CSR) can be retrieved via the following ways
Aws IoT webpage
Just navigate to Security - Certificates, click on ... and select Download.
AWS CLI
As you mention the CLI is also an option
aws iot describe-certificate --certificate-id fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd3
Will return
{
"certificateDescription": {
"certificateArn": "arn:aws:iot:eu-central-1:xxxxxx",
"status": "ACTIVE",
"certificateId": "fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd3",
"lastModifiedDate": 1519840881.49,
"certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDsTCCApmg.....VsAzFQ==\n-----END CERTIFICATE-----\n",
"transferData": {},
"ownedBy": "123456789",
"creationDate": 1519840820.888
}
Amazon IoT SDK
Can also be used to retrieve the certificate content (PEM format) as a String based on a certificate ID (that you can output via cloudformation)
import com.amazonaws.services.iot.AWSIot;
import com.amazonaws.services.iot.AWSIotClientBuilder;
import com.amazonaws.services.iot.model.DescribeCertificateRequest;
import com.amazonaws.services.iot.model.DescribeCertificateResult;
DescribeCertificateRequest describeCertificateRequest = new DescribeCertificateRequest();
describeCertificateRequest.setCertificateId("fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd371fcd3");
DescribeCertificateResult describeCertificateResult = awsIot.describeCertificate(describeCertificateRequest);
describeCertificateResult.getCertificateDescription().getCertificatePem();
AFAIK it is not possible to output it as a variable within a cloudformation template.
Ah I found a way using the AWS CLI. But there really needs to be a way to get it via CloudFormation :(
http://docs.aws.amazon.com/cli/latest/reference/iot/describe-certificate.html

AWS SES with Meteor

I'm trying to out a Meteor package to interface with AWS SES called tarang:email-ses built by #Akshat.
I'm on Meteor #1.* running on a AWS EC2 instance. When I a test run with the code below, no email was sent out.
Meteor Code
I've set up the AWS access key ID and secret access key and use it here:
Meteor.startup(function () {
Email.configSES({
AWSAccessKeyID: 'access-key',
AWSSecretKey: 'secret-key'
});
});
I've also verified my emails and domain. Here I make sure I'm sending from my verified sender SES address:
Accounts.emailTemplates.from = 'Domain Name <support#domain-name.com>';
Then in a Meteor method, I create a new user and send and enrollment email like so (this works if I deploy to meteor.com, without the Accounts.emailTemplates.from of course):
if (Meteor.user() && adminUser(this.userId)) {
var accountId = Accounts.createUser({
'username': doc.name,
'email': doc.email
});
Accounts.sendEnrollmentEmail(accountId);
}
Questions
Is the code to set things up for the email-ses package correct?
I thought this package abstracted out the Amazon SES API to Send Email (and allowed for native Meteor email calls). Is there a requirement to set up SMTP on AWS?
To send email in Meteor the easiest thing to do is to set the SMTP server and username information using process.env.MAIL_URL variable. The Accounts and Email packages will automatically use it to access the SMTP server whenever you send a message.
To enable SMTP access with AWS SES:
Log into the AWS Management Console
Select the appropriate region where you set up SES.
Select SES from the list of Application Services
Click the SMTP Settings menu item in the left menu
Click the Create My SMTP Credentials button
Use the default IAM username that's provided. This is a special IAM user that will only have access to the SMTP server. You shouldn't use your main IAM username/password when accessing the SMTP server.
Make sure to note the SMTP server name. You'll need it later.
After the account is created download the credentials.
Now that you have an account set up, simply copy and paste the following line to the top of your Meteor startup file and replace the username and password from the values in the credential file that you just downloaded and the server name that was provided.
process.env.MAIL_URL = 'smtp://username:password#smtp-server-name:465';
Please note that both the sending and receiving email addresses must be verified in the AWS SES management console if you haven't received production access yet. If you don't do this Meteor will throw an error in the console and the message will not be sent.
#brian-shamblen does not answer the question directly, but rather proposes an alternative solution to send emails.
You do not have to set up SMTP on AWS, nor you need to set environment variable inside your project process.env.MAIL_URL.
Log into the AWS Management Console
Go to IAM Service (!!not SES)
Create new IAM User
Save credentials (AWSAccessKeyID and AWSSecretKey). These are the credentials you'll use in Email.configSES({...})
Create inline Policy for this user:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
}
]
}
Make sure to use verified domail in Accounts.emailTemplates.from. In order to verify domain in AWS, go to SES services -> Domains and follow instructions.
This should let you send emails.