Problem: Netlify serverless functions run on AWS Lambda. So AWS_ is a reserved prefix in Netlify, meaning I can't use e.g. AWS_SECRET_ACCESS_KEY for my own environment var that I set in the Netlify admin panel.
But the only way I have been auble to authenticate Nodemailer with AWS SES (the email service) is with #aws/aws-sdk and its defaultProvider function that requires process.env.AWS_SECRET_ACCESS_KEY and process.env.AWS_ACCESS_KEY_ID – spelled exactly like that:
import 'dotenv/config'
import nodemailer from 'nodemailer'
import aws from '#aws-sdk/client-ses'
import { defaultProvider } from '#aws-sdk/credential-provider-node'
const ses = new aws.SES({
apiVersion: '2019-09-29',
region: 'eu-west-1',
defaultProvider,
rateLimit: 1
})
const sesTransporter = nodemailer.createTransport({ SES: { ses, aws } })
When building the function locally with the Netlify CLI, emails are sent.
It fails with 403 and InvalidClientTokenId: The security token included in the request is invalid in the live Netlify environment.
Netlify doesn't have a solution afaik, but mention in a forum post that custom env variables in AWS is a thing. I haven't been able to find anything in searches (they didn't provide any links). The AWS docs are pretty unhelpful as always :/
So the question is, how can this be done?
I thought I was clever when I tried the following, but setting the vars just before creating the SES Transport apparently doesn't help:
// Trick Netlify reserved env vars:
process.env.AWS_ACCESS_KEY_ID = process.env.ACCESS_KEY_ID
process.env.AWS_SECRET_ACCESS_KEY = process.env.SECRET_KEY
console.log('AWS access key id ', process.env.AWS_ACCESS_KEY_ID) // Logs the correct key!
console.log('AWS sec key ', process.env.AWS_SECRET_ACCESS_KEY ) // Logs the correct
Related
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.
I enjoy using the AWS SDK without having to specify where to find the credentials, it makes it easier to configure on multiple environment where different types of credentials are available.
The AWS SDK for Ruby searches for credentials [...]
Is there some way I can retrieve the code that does this to configure Faraday with AWS ? In order to configure Faraday I need something like
faraday.request(:aws_sigv4,
service: 'es',
credentials: credentials,
region: ENV['AWS_REGION'],
)
Now I would love that this credentials be picked "automatically" as in the aws sdk v3. How Can I do that ?
(ie where is the code in the AWS SDK v3 that does something like
credentials = Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
unless credentials.set?
credentials = Aws::InstanceProfileCredentials.new
end
...
The class Aws::CredentialProviderChain is the one in charge of resolving the credentials, but it is tagged as #api private so there's currently no guarantee it will still be there after updates (I've opened a discussion to make it public).
If you're okay to use it, you can resolve credentials like this. I'm going to test it in CI (ENV creds), development (Aws config creds), and staging/production environments (Instance profile creds).
Aws::CredentialProviderChain.new.resolve
You can use it in middlewares like this (for example configuring Elasticsearch/Faraday)
faraday.request(:aws_sigv4,
service: 'es',
credentials: Aws::CredentialProviderChain.new.resolve,
region: ENV['AWS_REGION'],
)
end
I'm using the Amplify graphql client in my project. After custom auth I have the Access Key ID and Secret Access Key.
I need to provide these to the Amplify configuration but I can find no documentation whatsoever about it. The closest documentation I've seen is :
const myAppConfig = {
// ...
'aws_appsync_graphqlEndpoint': 'https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql',
'aws_appsync_region': 'us-east-1',
'aws_appsync_authenticationType': 'AWS_IAM',
// ...
}
Amplify.configure(myAppConfig);
But it does not specify where should the credentials be entered.
They are specified for the AWS Appsync SDK but not the graphql client.
Any assistance or insights are greatly appreciated.
Thanks
I resolved it by using AWS.config
import AWS from "aws-sdk";
AWS.config.credentials = new AWS.Credentials(your credentials here)
Amplify.configure then works fine
I'm trying to ListProjects in AWS Device Farm.
Here's my code:
const AWS = require('aws-sdk');
AWS.config.update({ region:'us-east-1' });
const credentials = new AWS.SharedIniFileCredentials({ profile: '***' });
AWS.config.credentials = credentials;
const devicefarm = new AWS.DeviceFarm();
async function run() {
let projects = await devicefarm.listProjects().promise();
console.log(projects);
}
run();
I'm getting this error:
UnknownEndpoint: Inaccessible host: devicefarm.us-east-1.amazonaws.com'.
This service may not be available in the `us-east-1' region.
According to http://docs.amazonaws.cn/en_us/general/latest/gr/rande.html, Device Farm is only available in us-west-2?
Changing AWS.config.update({ region:'us-east-1' }); to AWS.config.update({ region:'us-west-2' }); worked:
Working code:
const AWS = require('aws-sdk');
AWS.config.update({ region:'us-west-2' });
var credentials = new AWS.SharedIniFileCredentials({ profile: '***' });
AWS.config.credentials = credentials;
var devicefarm = new AWS.DeviceFarm();
async function run() {
let projects = await devicefarm.listProjects().promise();
console.log(projects);
}
run();
I faced the same issue and realised that uploads were failing because my internet connection was too slow to resolve the DNS of bucket URL. However, slow connection is not the only reason for this error -- network/server/service/region/data center outage could also be the possible root cause.
AWS provides a dashboard to get health reports of the services offered and the same is available at this link. API to access the services' health is also available.
I had the same issue and checked all the github issues as well as SO answers, not much help.
If you are also in the corporate environment as I am and get this error locally, it is quite possible because you did not have the proxy setup in the SDK.
import HttpsProxyAgent from 'https-proxy-agent';
import AWS from 'aws-sdk';
const s3 = new AWS.S3({
apiVersion: '2006-03-01',
signatureVersion: 'v4',
credentials,
httpOptions: { agent: new HttpsProxyAgent(process.env.https_proxy) },
});
Normally the code might work in your ec2/lambda as they have vpc endpoint but locally you might need proxy in order to access the bucket url: YourBucketName.s3.amazonaws.com
I got the same error, I have resolved following way
Error:
Region: 'Asia Pacific (Mumbai) ap-south-1' which I choose
Solution:
In region instead of writing the entire name, you should only mention the region code
Region: ap-south-1
Well for anyone still having this issue, I managed to solve it by changing the endpoint parameter passed in to the AWS.config.update() from an ARN string example arn:aws:dynamodb:ap-southeast-<generated from AWS>:table/<tableName> to the URL example https://dynamodb.aws-region.amazonaws.com, but replacing aws-region with your region in my case ap-southeast-1.
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.Summary.html
I have the same problem. I just change the origin in the .env file.
Previous value:-
"Asia Pacific (Mumbai) ap-south-1"
Corrected value:-
"ap-south-1"
I just kept trying amplify init until it worked.
Scenario:
I create an app on Amazon, and use Login with Amazon, which returns an "access_token". Then I run:
AWS.config.credentials = new AWS.WebIdentityCredentials({
RoleArn: 'arn:aws:iam::416942672???:role/???_amazon_role',
ProviderId: 'www.amazon.com',
WebIdentityToken:"?????????"
});
AWS.config.region = 'us-west-2';
dynamodb = new AWS.DynamoDB() dynamodb.listTables({}, function a(error,data){
alert( "error: " + JSON.stringify(error) );
alert( JSON.stringify(data) );
});
When I later run the ListTable function it will return:
error: {"message":"Missing credentials in config","code":"SigningError","name":"SigningError","statusCode":403,"retryable":false}
I found it seems that I have to call AssumeRoleWithWebIdentity. But how can I call it in AWS SDK for JavaScript? Or is there any other process I missed?
The process of Getting Temporary Credentials indeed requires a call to AssumeRoleWithWebIdentity - this low level API call is implied in the higher level AWS SDK for JavaScript credentials provider call new AWS.WebIdentityCredentials() though, see e.g. Class: AWS.WebIdentityCredentials:
Represents credentials retrieved from STS Web Identity Federation
support.
By default this provider gets credentials using the
AWS.STS.assumeRoleWithWebIdentity() service operation. This operation
requires a RoleArn containing the ARN of the IAM trust policy for the
application for which credentials will be given. In addition, the
WebIdentityToken must be set to the token provided by the identity
provider. See constructor() for an example on creating a credentials
object with proper RoleArn and WebIdentityToken values.
Given the error message "Missing credentials in config", you are obviously passing an incorrect WebIdentityToken, which isn't a surprise given you just specified some ????????? placeholders ;) - since you already use Login with Amazon, which returns an access_token, you'll just need to pass the content of that ACCESS_TOKEN instead of those ????????? placeholders as value for WebIdentityToken and should be all set.
For other readers: Section 6. Putting it all together of Configuring Web Identity Federation in the Browser provides an example for retrieving the access token via Facebook Login. As noted there, Other identity providers will have a similar setup step that involves loading the respective SDK, logging in, and receiving an access token - in particular, how to handle Login with Amazon is documented in detail within Getting Started for Web.
Use AWS instance in this way:
var aws = AWS ;
var region = aws.config.region = 'us-east-1';
var cred = new aws.Credentials(<YOUR_ACCESS_KEY_ID>,
<YOUR_SECRET_ACCESS_KEY>, sessionToken = null);
To get ACCESS KEY ID and SECRET ACCESS_KEY go to:
AWS IAM>Users>Security Credentials>Access Keys
Now use aws instance for further operations.