Terraform - what is the URI to invoke lambda via alias? - aws-lambda

Question
To invoke a Lambda from API GW, invoke_arn can be used for aws_lambda_function resources.
invoke_arn - The ARN to be used for invoking Lambda Function from API Gateway.
resource "aws_api_gateway_integration" "videos" {
rest_api_id = "${aws_api_gateway_rest_api.24_hour_video.id}"
resource_id = "${aws_api_gateway_method.videos_get.resource_id}"
http_method = "${aws_api_gateway_method.videos_get.http_method}"
integration_http_method = "GET"
type = "AWS_PROXY" # Lambda Proxy
uri = "${aws_lambda_function.list_videos.invoke_arn}"
}
What is to set in uri to invoke the same lambda via an alias?
resource "aws_lambda_alias" "lambda_alias_list_videos" {
name = "get_video_list"
description = "Alias to lambda_list_videos"
function_name = "${aws_lambda_function.list_videos.arn}"
function_version = "$LATEST"
}

The aws_lambda_alias resource creates an alias which points to a specific version of a Lambda function. The alias itself is not invocable.
Instead you should create an aws_lambda_function Data Source that points to the aliased version, and use its invoke_arn property. You can use the qualifier argument in an aws_lambda_function to specify either a version or an alias name (see AWS Lambda Invoke Docs for more info).
Your example shows you have already created an alias named get_video_list, which points to the $LATEST version. You need to create a new Data Source that points to this alias:
data "aws_lambda_function" "my_function_get_video_list" {
function_name = "your-function-name"
qualifier = "get_video_list"
}
You can now get the invocation ARN of the aliased function:
${aws_lambda_function.my_function.get_video_list.invoke_arn}

aws_lambda_alias resource has invoke_arn attribute (see docs) which is designed for API Gateway. For example:
resource "aws_lambda_alias" "api_function_alias_live" {
name = "live"
function_name = aws_lambda_function.api_function.function_name
function_version = "1"
}
resource "aws_api_gateway_integration" "proxy_integration" {
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
resource_id = aws_api_gateway_resource.proxy_resource.id
http_method = aws_api_gateway_method.proxy_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_alias.api_function_alias_live.invoke_arn
}

Related

How to make integrations in AWS Api Gateway v2 (http) with lambda alias and stage variables in terraform

I have several lambdas with aliases (develop, production, staging..) and when I try to make an integration with his route like this
resource "aws_apigatewayv2_route" "http_routes" {
for_each = local.lambda_integrations
api_id = aws_apigatewayv2_api.api_http.id
route_key = each.key
target = "integrations/${aws_apigatewayv2_integration.http_integrations[each.key].id}"
}
resource "aws_apigatewayv2_integration" "http_integrations" {
for_each = local.lambda_integrations
api_id = aws_apigatewayv2_api.api_http.id
integration_type = "AWS_PROXY"
integration_method = "POST"
integration_uri = "arn:aws:apigateway:${var.auth.region}:lambda:path/2015-03-31/functions/${module.lambdas_functions[index(module.lambdas_functions.*.function_name,each.value.lambda)].arn}:$${stageVariables.alias}/invocations"
}
and
# Main Permission
resource "aws_lambda_permission" "permission_lambda" {
for_each = local.lambda_integrations
statement_id = can(each.value.statement) ? each.value.statement : "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = module.lambdas_functions[
index(module.lambdas_functions.*.function_name,each.value.lambda)
].function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.api_http.execution_arn}/*/*"
}
# Stage develop permission
resource "aws_lambda_permission" "permission_lambda_alias_develop" {
for_each = local.lambda_integrations
statement_id = can(each.value.statement) ? each.value.statement : "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = module.lambdas_functions[
index(module.lambdas_functions.*.function_name,each.value.lambda)
].function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.api_http.execution_arn}/*/*"
qualifier = "develop"
}
this works with a [number] version in CloudWatch, but in the lambda trigger I get this:
and although the code works, it does not seem to me to be correct.
what could be the best and correct approach to this situation?.
Regards
What worked for me is this:
Destroyed the API Gateway components (used target)
terraform destroy --var-file VAR_FILES --target API_AND_LAMBDA_MODULES
Renamed the stage to "$default"
Made the value of the perm_source_arn parameter as
"arn:aws:execute-api:${REGION}:${ACCOUNT_ID}:${API_ID}/*/*${ROUTE_KEY_PATH}"
After these, just re-apply.

Lambda Web API returning 403 for verbs other than POST

I have created a Serverless Lambda Web API using a built-in template. The web api is deployed to AWS using CloudFormation and everything is working fine (i.e. All GET, POST etc).
I have then checked the resources created by CloudFormation and created all of the resources using Terraform. I have double checked and REST API in the API Gateway, Lambda, Lambda permissions and triggers are exactly the same when created from both serverless template and terraform scripts.
The issue is the when using the resources created from Terraform, I am not able to call the Web API using HTTP verb other then POST. Below is my terraform script for the API Gateway.
resource "aws_api_gateway_rest_api" "SimulationServer_api_gateway_rest" {
name = "SimulationServer"
description = "Terraform Serverless Application Example"
}
resource "aws_api_gateway_resource" "api_gw_proxy_resource" {
rest_api_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.id
parent_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.root_resource_id
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "api_gw_proxy_method" {
rest_api_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.id
resource_id = aws_api_gateway_resource.api_gw_proxy_resource.id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "api_gw_proxy_integration" {
rest_api_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.id
resource_id = aws_api_gateway_method.api_gw_proxy_method.resource_id
http_method = aws_api_gateway_method.api_gw_proxy_method.http_method
integration_http_method = "ANY"
type = "AWS_PROXY"
uri = aws_lambda_function.SimulationServer.invoke_arn
}
resource "aws_api_gateway_method" "api_gw_proxy_root_method" {
rest_api_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.id
resource_id =
aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.root_resource_id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "api_gw_proxy_root_integration" {
rest_api_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.id
resource_id = aws_api_gateway_method.api_gw_proxy_root_method.resource_id
http_method = aws_api_gateway_method.api_gw_proxy_root_method.http_method
integration_http_method = "ANY"
type = "AWS_PROXY"
uri = aws_lambda_function.SimulationServer.invoke_arn
}
resource "aws_api_gateway_deployment" "api_gw_deployment" {
depends_on = [
aws_api_gateway_integration.api_gw_proxy_integration,
aws_api_gateway_integration.api_gw_proxy_root_integration,
]
rest_api_id = aws_api_gateway_rest_api.SimulationServer_api_gateway_rest.id
stage_name = var.env
}
output "base_url" {
value = aws_api_gateway_deployment.api_gw_deployment.invoke_url
}
It turned out to be very strange but simple solution.
Instead of using,
integration_http_method = "ANY"
When I used,
integration_http_method = "POST"
Then it started working for all HTTP Verbs (GET, POST, PUT etc)

How can I add a AWS cloudwatch event to a aws_lambda_function which is based on a container image with terraform?

I want to achieve that my lambda function which is based on a docker image in ecr is triggered by a scheduled cloudwatch event.
The problem is that I can not attach the function_name myFunction from the module "lambda_function_container_image" to the aws_lambda_permission.
It works when I have a normal lambda function like, but not with the lambda function from a image URI:
resource "aws_lambda_function" "myFunction" {
function_name = "myFunction"
role = aws_iam_role.lambda_execution_role.arn
handler = "exports.handler"
runtime = "python3.8"
}
I have the following code:
AWS CloudWatch event:
resource "aws_cloudwatch_event_rule" "every_five_minutes" {
name = "every-five-minutes"
description = "Fires every five minutes"
schedule_expression = "rate(5 minutes)"
}
Lambda function based on container image:
module "lambda_function_container_image" {
source = "terraform-aws-modules/lambda/aws"
function_name = "myFunction"
description = "awesome function"
create_package = false
image_uri = "${data.aws_caller_identity.current.account_id}.dkr.ecr${var.aws_region}.amazonaws.com/container_name"
package_type = "Image"
}
Lambda permission:
resource "aws_lambda_permission" "allow_cloudwatch_to_call_myFunction" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.myFunction.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.every_five_minutes.arn
}
I get the following error with the current aws_lambda_permission:
Error message:
Error: Reference to undeclared resource
-> points to function_name in aws_lambda_permission
You need to reference function_name through the module you are using. According to the documentation of terraform-aws-modules/lambda/aws the module has an output called lambda_function_name.
That means, that the following should work for you:
resource "aws_lambda_permission" "allow_cloudwatch_to_call_myFunction" {
[...]
function_name = module.lambda_function_container_image.lambda_function_name
[...]
}

Terraform API Gateway for use with Lambda Proxy Integration

I've spent the day fighting with API Gateway and AWS Serverless Express to no avail. My goal is to deploy an API Gateway, via Terraform (v0.12), that proxies all requests to an AWS Serverless Express based lambda. The connection between API Gateway and Lambda seems to exist, but tenuous, as any invocation (from API Gateway console or Postman) respond with 502 Bad Gateway, apparently due to timeout (so states the lambda CloudWatch logs). It does not appear that the lambda code actually runs, only that it spins up ineffectually.
The API Gateway and Lambda should support path parameters and query strings:
GET /some/path/:id get by id
GET /some/path?query=param get
collection
POST /some/path create resource
PATCH /some/path/:id update resource
DELETE /some/path/:id remove resource
After several false starts, I've tried to make the API Gateway Terraform module as flexible as possible:
resource "aws_api_gateway_rest_api" "rest_api" {
name = "${var.application_name} API"
description = var.description
}
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.rest_api.id
parent_id = aws_api_gateway_rest_api.rest_api.root_resource_id # aws_api_gateway_resource.version.id
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "method" {
rest_api_id = aws_api_gateway_rest_api.rest_api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "NONE"
request_parameters = {
"method.request.path.proxy" = true
}
}
resource "aws_api_gateway_integration" "integration" {
rest_api_id = aws_api_gateway_rest_api.rest_api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${local.region}:lambda:path/2015-03-31/functions/${var.lambda_arn}/invocations"
}
resource "aws_api_gateway_deployment" "apig_deployment" {
depends_on = [
"aws_api_gateway_resource.proxy",
"aws_api_gateway_method.method",
"aws_api_gateway_integration.integration"
]
rest_api_id = aws_api_gateway_rest_api.rest_api.id
stage_name = var.api_stage_name
lifecycle {
create_before_destroy = true
}
}
resource "aws_lambda_permission" "apig_to_lambda" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = var.function_name
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${local.region}:${local.account_id}:${aws_api_gateway_rest_api.rest_api.id}/*/*/*" # TODO: lock this down
}
You are getting 502 error that indicates that the response received by api gateway is not proper.
Having said that your application seems to return json response. Can you try below/confirm the settings?
Add the correct binary mime types in your lambda configuration. See here for more details.
Allow the same mime types or wildcard on api gateway side as below.
resource "aws_api_gateway_rest_api" "rest_api" {
name = "${var.application_name} API"
description = var.description
// Wildcard mimes, accept any
binary_media_types = ["*/*"]
}

Lambda chaining - Invoke lambda from another lambda using terraform

I am trying to invoke one AWS lambda from another and perform lambda chaining. The rationale behind doing this is AWS does not provide multiple trigger from same S3 bucket.
I have created one lambda, with an s3 trigger. The java code of first lambda will listen to S3 event and contains the invocation of another lambda. The second lambda will be invoked from first lambda. Both the lambda creation is done by terraform.
Lambda A has S3 trigger. This will be invoked on S3 event on a particular bucket. Lambda A will do the processing and will invoke Lambda B using invoke request. Lambda B invocation from Lambda A code in java is :
public class EventHandler implements RequestHandler<S3Event, String> {
#Override
public String handleRequest(S3Event event, Context context) throws RuntimeException {
InvokeRequest req = new InvokeRequest()
.withFunctionName("LambdaFunctionB")
.withPayload(json);
return "Lambda B invoked"
}
}
Both the lambdas are created using terraform. Scripts below:
Lambda A terraform:
module "lambda_function" {
source = "Git Path"
absolute_artifact_path = "../lambda.jar"
lambda_function_name = "LambdaFunctionA"
lambda_function_description = ""
lambda_function_runtime = "java8"
lambda_handler_name = "EventHandler"
lambda_execution_role_name = "lambda-iam-role"
lambda_memory = "512"
dead_letter_target_arn = "error-handling-arn"
}
resource "aws_lambda_permission" "allow_bucket" {
statement_id = "statementId"
action = "lambda:InvokeFunction"
function_name = "${module.lambda_function.lambda_arn}"
principal = "s3.amazonaws.com"
source_arn = "s3.bucket.arn"
}
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = "bucketName"
lambda_function {
lambda_function_arn = "${module.lambda_function.lambda_arn}"
events = ["s3:ObjectCreated:*"]
filter_prefix = "path/subPath"
}
}
Lambda B terraform:
module "lambda_function" {
source = "git path"
absolute_artifact_path = "../lambda.jar"
lambda_function_name = "LambdaFunctionB"
lambda_function_description = ""
lambda_function_runtime = "java8"
lambda_handler_name = "LambdaBEventHandler"
lambda_execution_role_name = "lambda-iam-role"
lambda_memory = "512"
dead_letter_target_arn = "error-handling-arn"
}
resource "aws_lambda_permission" "allow_lambda" {
statement_id = "AllowExecutionFromLambda"
action = "lambda:InvokeFunction"
function_name = "${module.lambda_function.lambda_arn}"
principal = "s3.amazonaws.com"
source_arn = "arn:aws:lambda:eu-west-1:xxxxxxxxxx:function:LambdaFunctionA"
}
lambda-iam-role has below policies attached
AmazonS3FullAccess
AWSLambdaBasicExecutionRole
AWSLambdaVPCAccessExecutionRole
AmazonSNSFullAccess
CloudWatchEventsFullAccess
Expectation was that Lambda A should successfully invoke Lambda B. But I am getting AccessDeniedException in Lambda A logs and it is not able to invoke Lambda B. Error is
com.amazonaws.services.lambda.model.AWSLambdaException: User: arn:aws:sts::xxxxxxxxx:assumed-role/lambda-iam-role/LambdaFunctionA is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:eu-west-1:xxxxxxxxx:function:LambdaFunctionB (Service: AWSLambda; Status Code: 403; Error Code: AccessDeniedException; Request ID: f495ede3-b3cb-47a1-b884-16996545233d)
Hope this helps you, not exactly similar but its invoking one lambda from another lambda Github
I think the lambda needs this policy as well "lambda:InvokeFunction"
I found an answer online, using the aws-sdk.
var aws = require('aws-sdk');
var lambda = new aws.Lambda({
region: 'default'
});
lambda.invoke({
FunctionName: 'name_of_your_lambda_function',
Payload: JSON.stringify(event, null, 2) // pass params
}, function(error, data) {
if (error) {
context.done('error', error);
}
if(data.Payload){
context.succeed(data.Payload)
}
});
You can find the doc here: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html
Hope it helps
:)

Resources