AWS Lambda using firebase-admin initializeApp timeout - amazon-web-services

I use Lambda to Firebase message. I ref this. But the lambda function still timeout because it cannot connect to google server.
Handler.js
/ [START imports]
const firebase = require('firebase-admin');
const serviceAccount = require("../serviceAccount.json");
module.exports.message = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const registrationToken = "xxxxxxx";
const payload = {
data: {
score: "850",
time: "2:45"
}
};
// [START initialize]
if(firebase.apps.length == 0) { // <---Important!!! In lambda, it will cause double initialization.
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: 'https://messaging-xxxxx.firebaseio.com'
});
}
// Send a message to the device corresponding to the provided
// registration token.
firebase.messaging().sendToDevice(registrationToken, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
callback(null, {
statusCode: 200,
body: JSON.stringify("Successful!"),
});
})
.catch(function(error) {
console.log("Error sending message:", error);
callback(null, {
statusCode: 500,
body: JSON.stringify({
"status": "error",
"message": error
})
})
});
};
CloudWatch
[Error: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "connect ETIMEDOUT 172.217.26.45:443".]
But I use same serviceAccount.json to run on my ec2 and work find.
Does someone encounter this?

After a couple hours struggling, I finally find the reason.
Because my Lambda using VPC to connect RDS and the network interface of VPC only have private IP.
AWS document:
When you add VPC configuration to a Lambda function, it can only access resources in that VPC. If a Lambda function needs to access both VPC resources and the public Internet, the VPC needs to have a Network Address Translation (NAT) instance inside the VPC.
So I need to create NAT inside the VPC.
I follow this Blog and problem solved.

Related

Problems accessing storage from Lambda function in Amplify

What I want to do?
I want to create REST API that returns data from my DynamoDB table which is being created by GraphQL model.
What I've done
Create GraphQL model
type Public #model {
id: ID!
name: String!
}
Create REST API with Lambda Function with access to my PublicTable
$ amplify add api
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: rest
? Provide a path (e.g., /book/{isbn}): /items
? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: listPublic
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the category storage
? Storage has 8 resources in this project. Select the one you would like your Lambda to access Public:#model(appsync)
? Select the operations you want to permit for Public:#model(appsync) create, read, update, delete
You can access the following resource attributes as environment variables from your Lambda function
API_MYPROJECT_GRAPHQLAPIIDOUTPUT
API_MYPROJECT_PUBLICTABLE_ARN
API_MYPROJECT_PUBLICTABLE_NAME
ENV
REGION
? Do you want to invoke this function on a recurring schedule? No
? Do you want to configure Lambda layers for this function? No
? Do you want to edit the local lambda function now? No
Successfully added resource listPublic locally.
Next steps:
Check out sample function code generated in <project-dir>/amplify/backend/function/listPublic/src
"amplify function build" builds all of your functions currently in the project
"amplify mock function <functionName>" runs your function locally
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
Succesfully added the Lambda function locally
? Restrict API access No
? Do you want to add another path? No
Successfully added resource rest locally
Edit my Lambda function
/* Amplify Params - DO NOT EDIT
API_MYPROJECT_GRAPHQLAPIIDOUTPUT
API_MYPROJECT_PUBLICTABLE_ARN
API_MYPROJECT_PUBLICTABLE_NAME
ENV
REGION
Amplify Params - DO NOT EDIT */
const AWS = require("aws-sdk");
const region = process.env.REGION
AWS.config.update({ region });
const docClient = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: "PublicTable"
}
async function listItems(){
try {
const data = await docClient.scan(params).promise()
return data
} catch (err) {
return err
}
}
exports.handler = async (event) => {
try {
const data = await listItems()
return { body: JSON.stringify(data) }
} catch (err) {
return { error: err }
}
};
Push my updates
$ amplify push
Open my REST API endpoint /items
{
"message": "User: arn:aws:sts::829736458236:assumed-role/myprojectLambdaRolef4f571b-dev/listPublic-dev is not authorized to perform: dynamodb:Scan on resource: arn:aws:dynamodb:us-east-1:8297345848236:table/Public-ssrh52tnjvcdrp5h7evy3zdldsd-dev",
"code": "AccessDeniedException",
"time": "2021-04-21T21:21:32.778Z",
"requestId": "JOA5KO3GVS3QG7RQ2V824NGFVV4KQNSO5AEMVJF66Q9ASUAAJG",
"statusCode": 400,
"retryable": false,
"retryDelay": 28.689093010346657
}
Problems
What I did wrong?
How do I access my table and why I didn't get it when I created it?
Why API_MYPROJECT_PUBLICTABLE_NAME and other constants are needed?
Decision
The problem turned out to be either the NodeJS version or the amplify-cli version. After updating amplify-cli and installing the node on the 14.16.0 version, everything worked.
I also changed the name of the table to what Amplify creates for us, although this code did not work before. The code became like this:
/* Amplify Params - DO NOT EDIT
API_MYPROJECT_GRAPHQLAPIIDOUTPUT
API_MYPROJECT_PUBLICTABLE_ARN
API_MYPROJECT_PUBLICTABLE_NAME
ENV
REGION
Amplify Params - DO NOT EDIT */
const AWS = require("aws-sdk");
const region = process.env.REGION
const tableName = process.env.API_MYPROJECT_PUBLICTABLE_NAME
AWS.config.update({ region });
const docClient = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: tableName
}
async function listItems(){
try {
const data = await docClient.scan(params).promise()
return data
} catch (err) {
return err
}
}
exports.handler = async (event) => {
try {
const data = await listItems()
return { body: JSON.stringify(data) }
} catch (err) {
return { error: err }
}
};

AWS Lambda in public subnet cannot access the internet

I'm trying to get a Lambda running inside a public subnet to communicate with the internet. I'm able to get the Lambda to hit www.google.com without a VPC (which the docs say runs one behind the scene) but cannot if I run the Lambda in a VPC.
Repro steps:
Create a Lambda (Node.js 12x) with the follow code. I named the Lambda 'curlGoogle'.
Run it to verify it succeeds and can fetch from www.google.com. There should be no VPC specified.
Go to the VPC Dashboard and use the VPC Wizard to create a VPC with a public subnet. I've tried a view values for IPv4 CIDR block (e.g. 10.1.0.0/16), IPv6 CIDR block, AZ. I usually leave 'Enable DNS hostnames' to Yes.
Change the Lambda to use the newly created VPC, Subnet and Security Group.
Verify this does not reach Google and times out.
I've tried modifications of this approach and haven't had any success (e.g. actually associating the subnet with the vpc, loosening all of settings on the Security Group and Network ACLs).
I originally tried following the one public and one private docs and failed to get that working.
Any ideas? Thanks!
- Dan
const http = require('http');
exports.handler = async (event) => {
return httprequest().then((data) => {
const response = {
statusCode: 200,
body: JSON.stringify(data),
};
return response;
});
};
function httprequest() {
return new Promise((resolve, reject) => {
const options = {
host: 'www.google.com',
path: '/',
port: 80,
method: 'GET'
};
const req = http.request(options, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = Buffer.concat(body).toString();
} catch(e) {
reject(e);
}
resolve(body);
});
});
req.on('error', (e) => {
reject(e.message);
});
// send the request
req.end();
});
}
AWS Lambda functions are never assigned a public IP address when in a VPC, even if they are in a public subnet. So they can never access the Internet directly when running in a VPC. You have to place Lambda functions in a private subnet with a route to a NAT Gateway in order to give them access to the Internet from within your VPC.

Not able to connect to AWS documentDb from Lambda

I'm trying to connect to AWS documentDB from Lambda function but, not able to connect.
MongoClient.connect never calls the callback function connected.
TLS is off on documentDB Cluster. I'm able to connect via mongo shell.
Lambda & documentDB are in same VPC & Security group.
'use strict';
module.exports.search = async (event, context, callback) => {
const MongoClient = require('mongodb').MongoClient;
const url = "mongodb://xxx:xxxx#xxx-db.cluster-xxx.us-east-2.docdb.amazonaws.com:27017";
console.log("Starting");
MongoClient.connect(url,
{
useNewUrlParser: true
},
function(err, client) {
if(err)
throw err;
console.log("Connected");
db = client.db('mydb');
col = db.collection('mycollection');
col.find({}).toArray().then(result => {
console.log(result);
return { statusCode: 200, body: result };
}).catch(err => {
console.log('=> an error occurred: ', err);
return { statusCode: 500, body: 'error' };
});
});
};
Output only prints starting which was consoled before calling Mongo.Connect.
How to identify or debug the issue ?
Just from looking at the current code I am pretty sure your function exit before it is able to complete. Therefore, your callback is not executed
Because MongoClient.connect runs asynchronously
Try to take a look at some resource around async/await/promise and Lambda
https://medium.com/tensult/async-await-on-aws-lambda-function-for-nodejs-2783febbccd9
How to wait for async actions inside AWS Lambda?

How to use WCF service hosted on IIS in an AWS lambda function?

I have this scenario in which a service hosted on IIS must be used by my AWS lambda function. When I try adding it as a service reference, I'm getting "An error occurred while attempting to discover services in the solution: No services found in the solution.. " error. I don't find any issue with the service, and it is working fine.
Is there any link that I'm missing to make my service available to be used by AWS?
If you are using node.js for Lambda then please do like below,
var soap = require('soap');
var url = 'YouServiceURL.svc?wsdl';
var soapOptions = {
forceSoap12Headers: true
};
var soapHeader = {
'wsa:Action': 'http://tempuri.org/MyBinding/MyOperation',
'wsa:To': 'YouServiceURL.svc'
};
exports.handler = function(event, context, callback) {
var params = {
param1: event.param1,
param2: event.param2
};
soap.createClient(url, soapOptions, function(err, client) {
if (err) callback(err);
client.addSoapHeader(soapHeader, '', 'wsa', 'http://www.w3.org/2005/08/addressing');
client.MyOperation(params, function(err, data) {
if (err) callback(err);
callback(null, data);
});
});
}
Please find sample code here

How can i confirm the subscription request HTTP from amazon SNS

I have been searching all over the web and nothing gives a clear answer to confirm the subscription request from amazon SNS. I already send the subscription from the amazon console to my website, but what's next? I am using amazon EC2 as my server with PHP.
Before you even configure the HTTP/HTTPS endpoint subscription through AWS management console, you need to make sure that the HTTP or HTTPS endpoint of your PHP web site has the capability to handle the HTTP POST requests that Amazon SNS generates. There are several types of SNS messages: SubscriptionConfirmation, Notification and UnsubscribeConfirmation. Your PHP code needs to get the header x-amz-sns-message-type from request and process it based on the message type. For SubscriptionConfirmation message, your PHP application needs to process the POST message body, which is a JSON document. In order to subscribe the topic, your PHP code needs to visit the "SubscriberURL" specified in the JSON body. Optionally, you should verify the signature to make sure the authenticity of message before subscribing the topic.
You can find more details on AWS documentation: http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html
Here is an express application (Node.js) which confirms the SNS subscription:
const express = require('express')
const request = require('request')
// parse urlencoded request bodies into req.body
const bodyParser = require('body-parser')
const app = express()
const port = 8080
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.post('/', (req, res) => {
let body = ''
req.on('data', (chunk) => {
body += chunk.toString()
})
req.on('end', () => {
let payload = JSON.parse(body)
if (payload.Type === 'SubscriptionConfirmation') {
const promise = new Promise((resolve, reject) => {
const url = payload.SubscribeURL
request(url, (error, response) => {
if (!error && response.statusCode == 200) {
console.log('Yess! We have accepted the confirmation from AWS')
return resolve()
} else {
return reject()
}
})
})
promise.then(() => {
res.end("ok")
})
}
})
})
app.listen(port, () => console.log('Example app listening on port ' + port + '!'))
To use it one needs to install required packages:
yarn add express request body-parser
Once you confirm the subscription AWS will send a POST request to the server with the following content:
{
"Type": "SubscriptionConfirmation",
"MessageId": "XXXXXXXX-1ee3-4de3-9c69-XXXXXXXXXXXX",
"Token": "SECRET_TOKEN",
"TopicArn": "arn:aws:sns:us-west-2:XXXXXXXXXXXX:ses-test",
"Message": "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:XXXXXXXXXXXX:ses-test. To confirm the subscription, visit the SubscribeURL included in this message.",
"SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:XXXXXXXXXXXX:ses-test&Token=SECRET_TOKEN",
"Timestamp": "2018-11-21T19:48:08.170Z",
"SignatureVersion": "1",
"Signature": "SECRET",
"SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.pem"
}
The payload contains SubscribeURL which is requested by the server.
The end point you have specified will get data from AWS SNS endpoint verification service, The same end point will be used to verify the end point and to get notifications from aws,
Simply dump the input sent by AWS SNS into one text file like,
$json_write_to_text = json_decode(file_get_contents("php://input"));
You will find all data sent by AWS SNS, but just find SubscriptionUrl (which will be specific for endpoint having valid token), Open this in browser you will have SubscriptionConfirmation status. That's it
Enjoy.
Spring cloud SNS subscription with annotation
spring cloud AWS has support for auto confirmation of subscriber, you just need to put this annotation "#NotificationSubscriptionMapping"
#Controller
#RequestMapping("/topicName")
public class NotificationTestController {
#NotificationSubscriptionMapping
public void handleSubscriptionMessage(NotificationStatus status) throws IOException {
//We subscribe to start receive the message
status.confirmSubscription();
}
#NotificationMessageMapping
public void handleNotificationMessage(#NotificationSubject String subject, #NotificationMessage String message) {
// ...
}
#NotificationUnsubscribeConfirmationMapping
public void handleUnsubscribeMessage(NotificationStatus status) {
//e.g. the client has been unsubscribed and we want to "re-subscribe"
status.confirmSubscription();
}
}
http://cloud.spring.io/spring-cloud-aws/spring-cloud-aws.html#_sns_support
I solved this using NodeJS backend. Lets say you have an API like this in HapiJS (Well it doesnt matter you can have another tech)
{
method: 'POST',
path: '/hello',
handler: ( request, reply ) => {
reply( Hello.print(request.payload) );
},
config: {
tags: ['api']
}
}
Just pass the payload you receive, on to your business logic.
In the business logic process it like this
'use strict';
const request = require('request');
exports.print = (payload) => {
payload = JSON.parse(payload);
if(payload.Type === 'SubscriptionConfirmation'){
return new Promise((resolve, reject) => {
const url = payload.SubscribeURL;
request(url, (error, response) => {
if (!error && response.statusCode == 200) {
console.log('Yess! We have accepted the confirmation from AWS');
return resolve();
}
else
return reject();
});
});
}
I am using request module from NPM to automatically accept such requests.
Another way would be to print the contents of payload and then click on the URL given in payload.SubscribeURL.
Once AWS accepts it you check the confirmation on the Subscriptions page where Subscription ARN would be changed from Pending Confirmation to a complex name-cum-SHA having your Topic name.