I am working on an task which involves Lambda function running inside VPC.
This function is supposed to push messages to SQS and lambda execution role has policies : AWSLambdaSQSQueueExecutionRole and AWSLambdaVPCAccessExecutionRole added.
Lambda functions :
# Create SQS client
sqs = boto3.client('sqs')
queue_url = 'https://sqs.ap-east-1a.amazonaws.com/073x08xx43xx37/xyz-queue'
# Send message to SQS queue
response = sqs.send_message(
QueueUrl=queue_url,
DelaySeconds=10,
MessageAttributes={
'Title': {
'DataType': 'String',
'StringValue': 'Tes1'
},
'Author': {
'DataType': 'String',
'StringValue': 'Test2'
},
'WeeksOn': {
'DataType': 'Number',
'StringValue': '1'
}
},
MessageBody=(
'Testing'
)
)
print(response['MessageId'])
On testing the execution result is as :
{
"errorMessage": "2020-07-24T12:12:15.924Z f8e794fc-59ba-43bd-8fee-57f417fa50c9 Task timed out after 3.00 seconds"
}
I increased the Timeout from Basic Settings to 5 seconds & 10
seconds as well. But the error kept coming.
If anyone has faced similar issue in past or is having an idea how to get this resolved, Please help me out.
Thanks you in advance.
When an AWS Lambda function is configured to use an Amazon VPC, it connects to a nominated subnet of the VPC. This allows the Lambda function to communicate with other resources inside the VPC. However, it cannot communicate with the Internet. This is a problem because the Amazon SQS public endpoint lives on the Internet and the function is timing-out because it is unable to reach the Internet.
Thus, you have 3 options:
Option 1: Do not connect to a VPC
If your Lambda function does not need to communicate with a resource in the VPC (such as the simple function you have provided above), simply do not connect it to the VPC. When a Lambda function is not connected to a VPC, it can communicate with the Internet and the Amazon SQS public endpoint.
Option 2: Use a VPC Endpoint
A VPC Endpoint provides a means of accessing an AWS service without going via the Internet. You would configure a VPC endpoint for Amazon SQS. Then, when the Lambda function wishes to connect with the SQS queue, it can access SQS via the endpoint rather than via the Internet. This is normally a good option if the Lambda function needs to communicate with other resources in the VPC.
Option 3: Use a NAT Gateway
If the Lambda function is configured to use a private subnet, it will be able to access the Internet if a NAT Gateway has been provisioned in a public subnet and the Route Table for the private subnet points to the NAT Gateway. This involves extra expense and is only worthwhile if there is an additional need for a NAT Gateway.
If you're using the boto3 python library in a lambda in a VPC, and it's failing to connect to an sqs queue through a vpc endpoint, you must set the endpoint_url when creating the sqs client. Issue 1900 describes the background behind this.
The solution looks like this (for an sqs vpc endpoint in us-east-1):
sqs_client = boto3.client('sqs',
endpoint_url='https://sqs.us-east-1.amazonaws.com')
Then call send_message or send_message_batch as normal.
You need to place your lambda inside your VPC then set up a VPC endpoint for SQS or NAT gateway, When you add your lambda function to a subnet, make sure you ONLY add it to the private subnets, otherwise nothing will work.
Reference
https://docs.aws.amazon.com/lambda/latest/dg/vpc.html
https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/
I am pretty convinced that you cannot call an SQS queue from within a VPC using Lambda using an SQS endpoint. I'd consider it a bug, but maybe the Lambda team did this for a reason. In any case, You will get a message timeout. I cooked up a simple test Lambda
import json
import boto3
import socket
def lambda_handler(event, context):
print('lambda-test SQS...')
sqsDomain='sqs.us-west-2.amazonaws.com'
addr1 = socket.gethostbyname(sqsDomain)
print('%s=%s' %(sqsDomain, addr1))
print('Creating sqs client...')
sqs = boto3.client('sqs')
print('Sending Test Message...')
response = sqs.send_message(
QueueUrl='https://sqs.us-west-2.amazonaws.com/1234567890/testq.fifo',
MessageBody='Test SQS Lambda!',
MessageGroupId='test')
print('SQS send response: %s' % response)
return {
'statusCode': 200,
'body': json.dumps(response)
}
I created a VPC, subnet, etc per - Configuring a Lambda function to access resources in a VPC. The EC2 instance in this example has no problem invoking SQS through the private endpoint from the CLI per this tutorial.
If I drop my simple Lambda above into the same VPC and subnet, with SQS publishing permissions etc. and invoke the test function it will properly resolve the IP address of the SQS endpoint within the subnet, but the call will timeout (making sure your Lambda timeout is more than 60 seconds to let boto fail). Enabling boto debug logging further confirms that the IP is resolved correctly and the HTTP request to SQS times out.
I didn't try this with a non-FIFO queue but as the HTTP call is failing on connection request this shouldn't matter. It's got to be a routing issue from the Lambda as the EC2 in the same subnet works.
I modified my simple Lambda and added an SNS endpoint and did the same test which worked. The issue issue appears to be specific to SQS best I can tell.
import json
import boto3
import socket
def testSqs():
print('lambda-test SQS...')
sqsDomain='sqs.us-west-2.amazonaws.com'
addr1 = socket.gethostbyname(sqsDomain)
print('%s=%s' %(sqsDomain, addr1))
print('Creating sqs client...')
sqs = boto3.client('sqs')
print('Sending Test Message...')
response = sqs.send_message(
QueueUrl='https://sqs.us-west-2.amazonaws.com/1234567890/testq.fifo',
MessageBody='Test SQS Lambda!',
MessageGroupId='test')
print('SQS send response: %s' % response)
return {
'statusCode': 200,
'body': json.dumps(response)
}
def testSns():
print('lambda-test SNS...')
print('Creating sns client...')
sns = boto3.client('sns')
print('Sending Test Message...')
response = sns.publish(
TopicArn='arn:aws:sns:us-west-2:1234567890:lambda-test',
Message='Test SQS Lambda!'
)
print('SNS send response: %s' % response)
return {
'statusCode': 200,
'body': json.dumps(response)
}
def lambda_handler(event, context):
#return testSqs()
return testSns()
I think your only options are NAT (per John above), bounce your calls off a local EC2 (NAT will be simpler, cheaper, and more reliable), or use a Lambda proxy outside the VPC. Which someone else suggested in a similar post. You could also subscribe an SQS queue to an SNS topic (I prototyped this and it works) and route it out that way too, but that just seems silly unless you absolutely have to have SQS for some obscure reason.
I switched to SNS. I was just hoping to get some more experience with SQS. Hopefully somebody can prove me wrong, but I call it a bug.
Related
We have a Lambda function that sends messages to SQS queue.
We are using boto3.
We have built a new environment and Lambda is running in a VPC on a private subnet.
The VPC end point is com.amazonaws.eu-west-2.sqs
Lambda code
sqs = boto3.resource('sqs')
# Get the queue
queue = sqs.get_queue_by_name(QueueName=QueueID)
This gives us the following error
EndpointConnectionError: Could not connect to the endpoint URL: "https://eu-west-2.queue.amazonaws.com/"
We have tried the following change
sqs = boto3.client('sqs')
# Get the queue
queue = sqs.get_queue_url(QueueName=QueueID, QueueOwnerAWSAccountId='xxxxxxxxxxxx')
We get the same error
It is a legacy endpoint issue but we do not know how to use the new endpoints in the Lambda function.
Because you're using a VPC endpoint for SQS you need to override the address that boto3 is using by default.
Something like this:
sqs = boto3.resource('sqs', endpoint_url="https://com.amazonaws.eu-west-2.sqs")
My goal is to send a SMS message through AWS Pinpoint using NodeJS and the AWS Pinpoint Gateway from behind a VPC. The code I am using works well on my local machine and in SAM where there are no VPC restrictions. I added a SNS and SMS VPC endpoint with security groups that give me access to each service individually. I can send an SMS using the aws-sdk SNS service.
I cannot seem to get Lambda to use pinpoint to send an SMS message. Is this possible? I cannot seem to find ports for the Pinpoint service? Is it possible to avoid exposing internet access to the VPN to send messages through AWS Pinpoint?
Role: Assigned All Pinpoint roles, SMS and SNS permissions for testing.
Message Configuration:
const AWS = require("aws-sdk");
AWS.config.update({region: process.env.SMS_REGION});
const PINPOINT = new AWS.Pinpoint({apiVersion: '2016-12-01'});
const PINPOINT_APP_ID = process.env.PINPOINT_APP_ID;
const params = {
ApplicationId: PINPOINT_APP_ID,
MessageRequest: {
Addresses: {
[number]: {
ChannelType: 'SMS'
}
},
MessageConfiguration: {
SMSMessage: {
Body: message,
OriginationNumber: origination_number,
SenderId: "senderId",
MessageType: process.env.SMS_MESSAGE_TYPE
}
}
}
};
await PINPOINT.sendMessages(params, (err, data) => {});
Region: us-west-2
There are no VPC interface endpoints for AWS Pinpoint, as shown in this AWS list. Therefore, you need to go through internet to access the service.
For your lambda, you have to place it in private subnet with NAT gateway in public subnet. Thanks to the NAT, your lambda function will be able to access pinpoint service.
The alternative would be to use private API gateway which integrates with other lambda as a proxy. Not ideal solution, but at the end of the day you have to go through internet, one way or the other.
I am running a Node(12.x) Lambda in AWS. The purpose of this lambda is to interact with Cloudformation stacks, and I'm doing that via the aws-sdk. When testing this lambda locally using lambda-local, it executes successfully and the stack can be seen in CREATING state in AWS console.
However, when I push and run this lambda in AWS, it fails after 15 seconds, and I get this error:
{"errorType":"TimeoutError","errorMessage":"Socket timed out without establishing a connection","code":"TimeoutError","message":"Socket timed out without establishing a connection","time":"2020-06-29T03:10:27.668Z","region":"us-east-1","hostname":"cloudformation.us-east-1.amazonaws.com","retryable":true,"stack":["TimeoutError: Socket timed out without establishing a connection"," at Timeout.connectTimeout [as _onTimeout] (/var/task/node_modules/aws-sdk/lib/http/node.js:69:15)"," at listOnTimeout (internal/timers.js:549:17)"," at processTimers (internal/timers.js:492:7)"]}
This lead me to investigate the lambda timeout and the possible configuration changes I could make found in https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-retry-timeout-sdk/ and https://aws.amazon.com/premiumsupport/knowledge-center/lambda-vpc-troubleshoot-timeout/ but nothing worked.
I found a couple of similar issues such as AWS Lambda: Task timed out which include possible suggestions such as lambda timeout and lambda memory issues, but Ive set mine to 30 seconds and the logs show max memory used is 88MB out of possible 128MB, but I tried with an increase anyway, and no luck.
The curious part is that it fails without establishing a connection to hostname cloudformation.us-east-1.amazonaws.com. How is that possible when the role assigned to the lambda has full Cloudformation privileges? I'm completely out of ideas so any help would be greatly appreciated. Heres my code:
TEST EVENT:
{
"stackName": "mySuccessfulStack",
"app": "test"
}
Function my handler calls (createStack):
const AWS = require('aws-sdk');
const templates = {
"test": {
TemplateURL: "https://<bucket>.s3.amazonaws.com/<path_to_file>/test.template",
Capabilities: ["CAPABILITY_IAM"],
Parameters: {
"HostingBucket": "test-hosting-bucket"
}
}
}
async function createStack(event) {
AWS.config.update({
maxRetries: 2,
httpOptions: {
timeout: 30000,
connectTimeout: 5000
}
});
const cloudformation = new AWS.CloudFormation();
const { app, stackName } = event;
let stackParams = templates[app];
stackParams['StackName'] = app + "-" + stackName;
let formattedTemplateParams = [];
for (let [key, value] of Object.entries(stackParams.Parameters)) {
formattedTemplateParams.push({"ParameterKey":key, "ParameterValue": value})
}
stackParams['Parameters'] = formattedTemplateParams;
const result = await cloudformation.createStack(stackParams).promise();
return result;
}
Lambda function in a VPC does not public IP address nor internet access. From docs:
Connect your function to private subnets to access private resources. If your function needs internet access, use NAT. Connecting a function to a public subnet does not give it internet access or a public IP address.
There are two common solutions for that:
place lambda function in a private subnet and setup NAT gateway in public subnet. Then set route table from private subnet to the NAT device. This will enable the lambda to access the internet and subsequently CloudFormation service.
setup a VPC interface endpoint for CloudFormation. This will allow your lambda function in private subnet to access CloudFormation without the internet.
I have a lambda which is attempting to make a REST call to an on-prem server outside of AWS. We have the lambda running from a VPC which has a VPN connection to our local resources. The same rest call runs successfully from EC2 with the VPC but the lambda request hangs. The security groups are open. Any ideas how to debug this?
Here is the bulk of the lambda
def lambda_handler(event, context):
config = configparser.ConfigParser()
config.read('config')
pattern = re.compile(".*"+config['DEFAULT']['my-pattern'])
logger.info(event['Records'])
sns_json = event['Records'][0]['Sns']
sns_message = json.loads(sns_json['Message'])
logger.info(sns_message['Records'][0]['s3'])
s3_object = sns_message['Records'][0]['s3']
new_file_name = s3_object['object']['key']
bucket = s3_object['bucket']['name']
if pattern.match(new_file_name):
new_json = {"text": "New file (" + new_file_name + ") added to the bucket. " + bucket,
"title": config['DEFAULT']['default_message_title']}
webhook_post = requests.get("http://some-ip:4500/")
logger.info("Webhook Post Status: " + str(webhook_post.status_code) + str(webhook_post))
logger.info("Skip teams webhook");
outgoing_message_dict = {
's3Bucket': bucket,
'somefile': new_file_name
}
return outgoing_message_dict
I don't receive any errors from the request, it just hangs until my lambda times-out.
I believe I found the source of the problem. Ultimately I believe the issue is with our on-prem firewall. The VPN tunnel wasn't active at all times. Others have mentioned that it needs to be activated from the on-prem network. I created an ec2 instance and connected to it, activating the VPN. What I ran the lambda shortly after, I could successfully reach the local REST endpoint I was trying connect to.
I have not implemented the final solution yet, but from the firewall we should be able to set the connection to have a keep-alive ping so our connection does not time-out. I hope this helps others. Thank you for the feedback!
I'm trying to notify an SNS topic from a CloudWatch alarm that's in a different region. The reason is that I want SMS alerting, which isn't available in the region where my services are. If I enter the ARN of the subscription and save the changes in the console, I get "There was an error saving the alarm. Please try again." Trying again does not help. Using a topic in the local region does work, but that's not what I need.
Is there a way to notify a topic in a different region? If not, is there another easy way I can achieve my goal?
Didn't find any docs that explicitly say this can't be done but tried to set an SNS from us-east-1 as an action of an alarm in eu-west-1 using the CLI and I got this:
An error occurred (ValidationError) when calling the PutMetricAlarm operation: Invalid region us-east-1 specified. Only eu-west-1 is supported.
So I'll assume it's not supported.
To get the functionality you need you can use AWS Lambda. Lets say your service is in a region where SMS is not supported, I'll use eu-central-1 as an example.
Setup would go like this:
[us-east-1] Create your SNS topic that can send SMS messages, in the region where SMS is supported.
[eu-central-1 Create a lambda function that sends messages to the SNS topic from step 1 in the region where your service is.
[eu-central-1] Create an SNS topic in the region where your service is.
For the SNS topic configure subscription with AWS Lambda protocol and point it to lambda from step 2.
[eu-central-1] Create your alarm in the region where your service is and put the SNS topic from step 3 as an action.
To add to #Tartaglia's answer, here's the source of such a lambda function using Python 3, cobbled together from various sources because I don't have time to do it properly:
import json
import logging
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
session = boto3.Session(region_name='eu-west-1') # EU (Ireland)
sns_client = session.client('sns')
def lambda_handler(event, context):
logger.info('Received event: %s', event)
for record in event['Records']:
sns_message = record['Sns']
response = sns_client.publish(
TopicArn='YOUR TOPIC ARN HERE',
Subject=sns_message.get('Subject', None),
Message=sns_message.get('Message', None))
logger.info('Publish response: %s', response)
return 'OK'