AWS Lambda in public subnet cannot access the internet - amazon-web-services

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.

Related

AWS: Get the public Image id's (AMI), for an Account, which are used to launch EC2 instances

We are trying to create an Config rule, which should warn and notify when an public image is used to launch our EC2 instances in an account. This is to avoid any security issues and unwanted user scripts to be run against our instances. We use NODE to develop the logic and I am using describeImages method of ec2, to get the public images, by passing the is-public filter, the results are actually all the public images available in that account, but we need only public images which are being used to launch our instances, I couldn't figure out how to achieve this. Any help would be greatly appreciated. Sample code, which I am using to test this is as follows.
const aws = require ('aws-sdk')
aws.config.update({ region: 'us-west-2' });
const ec2 = new aws.EC2()
const getPublicImages = async ()=>{
let publicImages
const params = {
DryRun: false,
Filters: [
{
Name: 'is-public',
Values: ['true']
}
]
// Owners: [event['accountId']]
}
publicImages = await ec2
.describeImages(params)
.promise()
.then(images => {
return (images)
})
.catch(err => {
return err
})
return publicImages
}
const handleGetPublicImages = async ()=>{
const res = await getPublicImages()
console.log(res);
}
handleGetPublicImages()

Problem with AWS Web Application Firewall

I have an AWS EC2 instance (single machine) + Elastic IP + Route 53 (redirects the domain name to my EC2 machine).
I wanted to add AWS Web Application Firewall. So the configuration is:
EC2 instance + Application Load Balancer with Web Application Firewall + Route 53 (redirects the domain name to the load balancer).
However, with this new configuration, my Ajax scripts fail to execute. For example, the code
'use strict';
var url = require('url');
var target = 'https://DOMAIN_NAME';
exports.handler = function(event, context, callback) {
var urlObject = url.parse(target);
var mod = require(
urlObject.protocol.substring(0, urlObject.protocol.length - 1)
);
console.log('[INFO] - Checking ' + target);
var req = mod.request(urlObject, function(res) {
res.setEncoding('utf8');
res.on('data', function(chunk) {
console.log('[INFO] - Read body chunk');
});
res.on('end', function() {
console.log('[INFO] - Response end');
callback();
});
});
req.on('error', function(e) {
console.log('[ERROR] - ' + e.message);
callback(e);
});
req.end();
};
fails with "This combination of host and port requires TLS".
Some other Ajax requests fail too.
I've opened ports 80, 22, and 443 on the EC2 instance. And port 443 is opened in the Load Balancer.
I don't understand why these script fail with the new configuration. In the old configuration everything works fine.
UPDATE: It seems that one of the rules in the AWS WAF Core Rule Set is blocking my ajax requests. I guess, I'll just need to find out which one.

Amazon ECS Task, how to get public IP in NodeJS

I have a task (written in NodeJS) running in an ECS Fargate cluster. The task has a public IP assigned to it, but I would like to know the public IP, inside the task.
Previously I ran on EC2 and there this was pretty straightforward. I just did:
const AWS = require('aws-sdk')
const meta = new AWS.MetadataService()
async function getPublicIp() {
return new Promise((resolve, reject) => {
meta.request("/latest/meta-data/public-ipv4", function (err, data) {
if(err) {
return reject(err)
}
resolve(data)
})
})
}
The problem is I find no such thing for ECS.

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?

AWS Lambda using firebase-admin initializeApp timeout

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.