Hi there,
It is currently impossible to deploy an API with a GET method that successfully links to a Lambda function with Terraform v0.7.5.
I tried GET and now with v0.7.5 I also tried ANY in combination with AWS and AWS_PROXY. But it always ends up with
<AccessDeniedException>
<Message>Unable to determine service/operation name to be authorized</Message>
</AccessDeniedException>
I narrowed it down a bit already and found a manual workaround:
After deploying the API with terraform you have to manually go to the AWS Console for API Gateway and click on the Integration for the ANY method. There you click on the Lambda function Edit button and then on the Update button.
A new windows then tells you
You are about to give API Gateway permission to invoke your Lambda function:
arn:aws:lambda:us-east-1:123456789012:function:myLambdaFunction
and if you confirm this, your GET method magically starts working!
Obviously this workaround is not good enough since it can't be automated, but it might help to find the bug.
If you create the API and ANY method from the AWS console manually it will also ask you that question and everything works right away. So there must be a way to fix terraform to achieve the same functionality.
This is a serious show stopper if you want to get a serverless website server in Lambda.
v0.7.5
///**
// * IAM
// * -------------------------------------------------------------------------------------------------------------------
// */
//
/**
* IAM Role to enable APIGateway logging to CloudWatch.
*/
resource "aws_iam_role" "SeoAPIGatewayAccount" {
name = "seo_api_gateway_account"
assume_role_policy = "${file("./CloudWatch/APIGateway/AssumeRolePolicy.json")}"
}
/**
* IAM Role Policy to enable APIGateway logging to CloudWatch.
*/
resource "aws_iam_role_policy" "SeoAPIGatewayAccount" {
name = "seo_api_gateway_account"
role = "${aws_iam_role.SeoAPIGatewayAccount.id}"
policy = "${file("./CloudWatch/APIGateway/InlinePolicy.json")}"
}
/**
* IAM Role for the Seo Website Lambda Function.
*/
resource "aws_iam_role" "SeoLambdaWebsiteEndpoint" {
name = "seo_lambda_website_endpoint"
assume_role_policy = "${file("./Lambdas/WebsiteEndpoint/AssumeRolePolicy.json")}"
}
/**
* IAM Role Policy for the Seo Website Lambda Function.
*/
resource "aws_iam_role_policy" "SeoLambdaWebsiteEndpoint" {
name = "seo_lambda_website_endpoint"
role = "${aws_iam_role.SeoLambdaWebsiteEndpoint.id}"
policy = "${file("./Lambdas/WebsiteEndpoint/InlinePolicy.json")}"
}
/**
* Lambda
* -------------------------------------------------------------------------------------------------------------------
*/
/**
* Website Lambda Function.
*/
resource "aws_lambda_function" "SeoWebsite" {
filename = "./Lambdas/WebsiteEndpoint/.WebsiteEndpoint.compiled.zip"
function_name = "seo_website_endpoint"
role = "${aws_iam_role.SeoLambdaWebsiteEndpoint.arn}"
handler = "index.handler"
runtime = "nodejs4.3"
source_code_hash = "${base64sha256(file("./Lambdas/WebsiteEndpoint/.WebsiteEndpoint.compiled.zip"))}"
}
/**
* Allow APIGateway to access the Website Lambda Function.
*/
resource "aws_lambda_permission" "SeoWebsiteApiGateway" {
depends_on = [
"aws_lambda_function.SeoWebsite",
"aws_api_gateway_rest_api.SeoWebsite",
"aws_api_gateway_method.SeoWebsite"
]
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.SeoWebsite.function_name}"
principal = "apigateway.amazonaws.com"
}
resource "aws_lambda_permission" "SeoWebsiteApiGatewayMethod" {
depends_on = [
"aws_lambda_function.SeoWebsite",
"aws_api_gateway_rest_api.SeoWebsite",
"aws_api_gateway_method.SeoWebsite"
]
statement_id = "AllowExecutionFromAPIGatewayMethod"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.SeoWebsite.function_name}"
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${var.aws_region}:123456789012:${aws_api_gateway_rest_api.SeoWebsite.id}/*/*"
}
/**
* API Gateway
* -------------------------------------------------------------------------------------------------------------------
*/
/**
* Enable APIGateway logging to CloudWatch.
*/
resource "aws_api_gateway_account" "Seo" {
cloudwatch_role_arn = "${aws_iam_role.SeoAPIGatewayAccount.arn}"
}
/**
* Website
* ----------------------------------------------------------
*/
/**
* REST API for the Website Endpoint.
*/
resource "aws_api_gateway_rest_api" "SeoWebsite" {
name = "seo_website"
description = "Seo Website"
depends_on = [
"aws_lambda_function.SeoWebsite"
]
}
/**
* REST API Deployment for the Website Endpoint.
*/
resource "aws_api_gateway_deployment" "SeoWebsite" {
depends_on = [
"aws_api_gateway_method.SeoWebsite",
"aws_api_gateway_integration.SeoWebsite",
"aws_api_gateway_integration_response.SeoWebsite",
"aws_api_gateway_method_response.SeoWebsite"
]
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
stage_name = "prod"
}
/**
* API root (/)
* -----------------------------
*/
/**
* Website GET Method Request.
*/
resource "aws_api_gateway_method" "SeoWebsite" {
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "ANY"
authorization = "NONE"
}
/**
* Website POST Integration Request. TODO: type = "AWS_PROXY"
*/
resource "aws_api_gateway_integration" "SeoWebsite" {
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/${aws_lambda_function.SeoWebsite.arn}/invocations"
integration_http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
}
/**
* Website POST Integration Response for status 200.
*/
resource "aws_api_gateway_integration_response" "SeoWebsite" {
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
status_code = "${aws_api_gateway_method_response.SeoWebsite.status_code}"
}
/**
* Website POST Method Response for status 200.
*/
resource "aws_api_gateway_method_response" "SeoWebsite" {
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
status_code = "200"
response_models = {
"text/html" = "Empty"
}
}
GET method should work right away.
GET method is not working until you use the manual workaround as described earlier.
Please list the steps required to reproduce the issue, for example:
terraform applyPOST works out of the box, so it seems some special magic has to happen for GET since GET is only recently officially supported with Lambda via ANY.
But see below, CloudFormation seems to be able to deploy even the GET without this problem.
Based on this link it seems to work when using CloudFormation?!
Some more details here. This implies that while the method type can be ANY the integration type has to be POST. But this cannot be achieved with terraform I guess.
@stack72 Maybe the terraform documentation can be improved a bit in this area:
I finally figured it out. So basically the aws_lambda_permission needs to look like this:
source_arn = "arn:aws:execute-api:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.SeoWebsite.id}/*/*/"
The other important part which is not that well documented is the fact that lambda functions do only accept POST. So to achieve this with terraform we have to use the aws_api_gateway_integration and set the http_method to the API method type BUT the integration_http_method to POST.
Here the whole lot for everybody who comes across this problem again in the future:
/**
* IAM
* -------------------------------------------------------------------------------------------------------------------
*/
/**
* IAM Role to enable APIGateway logging to CloudWatch.
*/
resource "aws_iam_role" "SeoAPIGatewayAccount" {
name = "seo_api_gateway_account"
assume_role_policy = "${file("./CloudWatch/APIGateway/AssumeRolePolicy.json")}"
}
/**
* IAM Role Policy to enable APIGateway logging to CloudWatch.
*/
resource "aws_iam_role_policy" "SeoAPIGatewayAccount" {
name = "seo_api_gateway_account"
role = "${aws_iam_role.SeoAPIGatewayAccount.id}"
policy = "${file("./CloudWatch/APIGateway/InlinePolicy.json")}"
}
/**
* IAM Role for the Seo Website Lambda Function.
*/
resource "aws_iam_role" "SeoLambdaWebsiteEndpoint" {
name = "seo_lambda_website_endpoint"
assume_role_policy = "${file("./Lambdas/WebsiteEndpoint/AssumeRolePolicy.json")}"
}
/**
* IAM Role Policy for the Seo Website Lambda Function.
*/
resource "aws_iam_role_policy" "SeoLambdaWebsiteEndpoint" {
name = "seo_lambda_website_endpoint"
role = "${aws_iam_role.SeoLambdaWebsiteEndpoint.id}"
policy = "${file("./Lambdas/WebsiteEndpoint/InlinePolicy.json")}"
}
/**
* Lambda
* -------------------------------------------------------------------------------------------------------------------
*/
/**
* Website Lambda Function.
*/
resource "aws_lambda_function" "SeoWebsite" {
filename = "./Lambdas/WebsiteEndpoint/.WebsiteEndpoint.compiled.zip"
function_name = "seo_website_endpoint"
role = "${aws_iam_role.SeoLambdaWebsiteEndpoint.arn}"
handler = "index.handler"
runtime = "nodejs4.3"
source_code_hash = "${base64sha256(file("./Lambdas/WebsiteEndpoint/.WebsiteEndpoint.compiled.zip"))}"
}
/**
* Allow APIGateway to access the Website Lambda Function.
*/
resource "aws_lambda_permission" "SeoWebsiteApiGatewayMethod" {
depends_on = [
"aws_api_gateway_method.SeoWebsite",
"aws_api_gateway_method_response.SeoWebsite"
]
statement_id = "AllowExecutionFromAPIGatewayMethod"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.SeoWebsite.function_name}"
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.SeoWebsite.id}/*/*/"
}
/**
* API Gateway
* -------------------------------------------------------------------------------------------------------------------
*/
/**
* Enable APIGateway logging to CloudWatch.
*/
resource "aws_api_gateway_account" "Seo" {
cloudwatch_role_arn = "${aws_iam_role.SeoAPIGatewayAccount.arn}"
}
/**
* Domain
* ----------------------------------------------------------
*/
///**
// * Custom domain name for the Website Endpoint.
// */
//resource "aws_api_gateway_domain_name" "SeoWebsite" {
// domain_name = "${var.seo_domain}"
// certificate_name = "${var.seo_domain}"
// certificate_body = "${file("./Certificates/${var.seo_domain}.crt")}"
// certificate_chain = "${file("./Certificates/${var.seo_domain}.chain.crt")}"
// certificate_private_key = "${file("./Certificates/${var.seo_domain}.key")}"
//}
//
///**
// * Custom domain base path mapping for the Website Endpoint.
// */
//resource "aws_api_gateway_base_path_mapping" "SeoWebsite" {
// api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
// stage_name = "${aws_api_gateway_deployment.SeoWebsite.stage_name}"
// domain_name = "${aws_api_gateway_domain_name.SeoWebsite.domain_name}"
//}
/**
* Website
* ----------------------------------------------------------
*/
/**
* REST API for the Website Endpoint.
*/
resource "aws_api_gateway_rest_api" "SeoWebsite" {
name = "seo_website"
description = "Seo Website"
depends_on = [
"aws_lambda_function.SeoWebsite"
]
}
/**
* REST API Deployment for the Website Endpoint.
*/
resource "aws_api_gateway_deployment" "SeoWebsite" {
depends_on = [
"aws_api_gateway_method.SeoWebsite",
"aws_api_gateway_integration.SeoWebsite"
//"aws_api_gateway_integration_response.SeoWebsite",
//"aws_api_gateway_method_response.SeoWebsite"
]
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
stage_name = "prod"
}
/**
* API root (/)
* -----------------------------
*/
/**
* Website GET Method Request.
*/
resource "aws_api_gateway_method" "SeoWebsite" {
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "ANY"
authorization = "NONE"
}
/**
* Website GET Integration Request.
*/
resource "aws_api_gateway_integration" "SeoWebsite" {
depends_on = [
"aws_lambda_permission.SeoWebsiteApiGatewayMethod"
]
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/${aws_lambda_function.SeoWebsite.arn}/invocations"
integration_http_method = "POST" // Lambda function do only accept POST
}
/**
* Website GET Integration Response for status 200.
*/
resource "aws_api_gateway_integration_response" "SeoWebsite" {
depends_on = [
"aws_api_gateway_integration.SeoWebsite"
]
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
status_code = "${aws_api_gateway_method_response.SeoWebsite.status_code}"
}
/**
* Website GET Method Response for status 200.
*/
resource "aws_api_gateway_method_response" "SeoWebsite" {
rest_api_id = "${aws_api_gateway_rest_api.SeoWebsite.id}"
resource_id = "${aws_api_gateway_rest_api.SeoWebsite.root_resource_id}"
http_method = "${aws_api_gateway_method.SeoWebsite.http_method}"
status_code = "200"
response_models = {
"text/html" = "Empty"
}
}
I just wanted to say a huge thank you for following up and posting the solution to your problem. I burned several hours trying to get this hooked up correctly and for the life of me I could not figure out how to authorize API Gateway to hit the Lambda function. In my case, I wasn't using the wildcards (/*/*/) at the end of the source_arn value in the aws_lambda_permission resource. Once I added those everything worked beautifully.
🙌
Hi @BerndWessels I'd love it if you would open a PR with the changes to the docs - we always appreciate the documentation changes as we don't want people to have the bad experience that you had!
Thanks for posting a solution others can you
Paul
Definitely works, thanks! Was caught up on having to set
integration_http_method = "POST"
even though it was for a GET method.
Just want to say thanks!
I encountered this problem in CloudFormation (not Terraform) but this GitHub issue illustrated the problem. I'll expand on @BerndWessels's description above of the root cause and fix
Using a permission with a SourceArn like this
arn:aws:execute-api:us-west-2:123456789012:ygvrlkec05/*
worked fine for my POST endpoints but triggered the Unable to determine service/operation name to be authorized error message for calls to my GET endpoints.
I had to change the SourceArn, as @BerndWessels describes, to
arn:aws:execute-api:us-west-2:123456789012:ygvrlkec05/*/*
to get my GET and POST endpoints working.
Many thanks @BerndWessels
I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
Most helpful comment
@stack72 Maybe the
terraformdocumentation can be improved a bit in this area:I finally figured it out. So basically the
aws_lambda_permissionneeds to look like this:The other important part which is not that well documented is the fact that
lambda functionsdo only acceptPOST. So to achieve this withterraformwe have to use theaws_api_gateway_integrationand set thehttp_methodto theAPI method typeBUT theintegration_http_methodtoPOST.Here the whole lot for everybody who comes across this problem again in the future: