AccessDeniedException when creating API Gateway through Terraform - amazon-web-services

I am trying to create API Gateway / Lambda stack through Terraform.
This is my apigateway.tf file (I added "aws_api_gateway_authorizer" as per https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_authorizer):
resource "aws_api_gateway_rest_api" "handler_gateway" {
name = "captainai_webhook_handler"
description = "Entry point for captainai webhook handler"
tags = var.service_tags
}
resource "aws_api_gateway_integration" "lambda" {
rest_api_id = aws_api_gateway_rest_api.handler_gateway.id
resource_id = aws_api_gateway_method.events_post.resource_id
http_method = aws_api_gateway_method.events_post.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.handler_lambda.invoke_arn
}
resource "aws_api_gateway_integration" "lambda_root" {
rest_api_id = aws_api_gateway_rest_api.handler_gateway.id
resource_id = aws_api_gateway_method.events_post.resource_id
http_method = aws_api_gateway_method.events_post.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.handler_lambda.invoke_arn
}
resource "aws_api_gateway_domain_name" "handler_domain_name" {
regional_certificate_arn = var.domain_certificate_arn
domain_name = var.domain_name
tags = var.service_tags
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_api_gateway_resource" "events_proxy" {
rest_api_id = aws_api_gateway_rest_api.handler_gateway.id
parent_id = aws_api_gateway_rest_api.handler_gateway.root_resource_id
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "events_post" {
rest_api_id = aws_api_gateway_rest_api.handler_gateway.id
resource_id = aws_api_gateway_resource.events_proxy.id
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_deployment" "handler" {
depends_on = [
aws_api_gateway_integration.lambda,
aws_api_gateway_integration.lambda_root,
]
rest_api_id = aws_api_gateway_rest_api.handler_gateway.id
stage_name = "default"
}
resource "aws_iam_role" "invocation_role" {
name = "api_gateway_auth_invocation"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "invocation_policy" {
name = "default"
role = aws_iam_role.invocation_role.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
"Resource": "${aws_lambda_function.handler_lambda.arn}"
}
]
}
EOF
}
resource "aws_api_gateway_authorizer" "demo" {
name = "demo"
rest_api_id = aws_api_gateway_rest_api.handler_gateway.id
authorizer_uri = aws_lambda_function.handler_lambda.invoke_arn
authorizer_credentials = aws_iam_role.invocation_role.arn
}
Unfortunately, it is throwing me the following error, for both aws_api_gateway_rest_api and aws_api_gateway_domain_name:
Error: Error creating API Gateway: AccessDeniedException:
status code: 403, request id: ad1c834d-5ba9-462a-ac65-ed9852811632
on apigateway.tf line 1, in resource "aws_api_gateway_rest_api" "handler_gateway":
1: resource "aws_api_gateway_rest_api" "handler_gateway" {
Error: Error creating API Gateway Domain Name: AccessDeniedException:
status code: 403, request id: 1c11fda2-5618-4ae0-aeac-783288535d57
on apigateway.tf line 31, in resource "aws_api_gateway_domain_name" "handler_domain_name":
31: resource "aws_api_gateway_domain_name" "handler_domain_name" {
The only other thing that comes to my mind that I should define aws_iam_user resource somewhere but am not sure when and which roles to include.
Lambda function is in a separate lambda.tf file, as:
resource "aws_lambda_function" "handler_lambda" {
description = "The lambda handles webhook events from captainai."
filename = "data/captainai_webhook_handler.zip"
function_name = "captainai_webhook_handler"
role = aws_iam_role.iam_for_lambda.arn
handler = "captainai_webhook_handler.lambda_handler"
source_code_hash = filebase64sha256("data/captainai_webhook_handler.zip")
runtime = "python3.8"
timeout = 120
memory_size = 512
tags = var.service_tags
environment {
variables = {
WEBSOCKET_HTTP_HOST = var.websocket_http_host
WEBSOCKET_HTTP_HOST_LOGIN = var.websocket_http_host_login
WEBSOCKET_HTTP_HOST_PASSWORD = var.websocket_http_host_password
CAPTAINAI_SECRET_TOKEN = var.captainai_secret_token
}
}
}
resource "aws_lambda_permission" "api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.handler_lambda.arn
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.handler_gateway.execution_arn}/*/*"
}

Related

Enabling Managed Updates for Elastic Beanstalk with terraform

I am trying to enable managed updates with terraform but i am getting the following error
Error: ConfigurationValidationException: Configuration validation exception: Invalid option specification (Namespace: 'aws:elasticbeanstalk:managedactions', OptionName: 'ManagedActionsEnabled'): You can't enable managed platform updates when your environment uses the service-linked role 'AWSServiceRoleForElasticBeanstalk'. Select a service role that has the 'AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy' managed policy.
Terraform code:
resource "aws_elastic_beanstalk_environment" "eb_env" {
setting {
namespace = "aws:elasticbeanstalk:managedactions"
name = "ManagedActionsEnabled"
value = "True"
}
setting {
namespace = "aws:elasticbeanstalk:managedactions"
name = "ServiceRoleForManagedUpdates"
value = aws_iam_role.beanstalk_service.arn
}
setting {
namespace = "aws:elasticbeanstalk:managedactions"
name = "PreferredStartTime"
value = "Sat:04:00"
}
setting {
namespace = "aws:elasticbeanstalk:managedactions:platformupdate"
name = "UpdateLevel"
value = "patch"
}
}
resource "aws_iam_instance_profile" "beanstalk_service" {
name = "beanstalk-service-user"
role = "${aws_iam_role.beanstalk_service.name}"
}
resource "aws_iam_instance_profile" "beanstalk_ec2" {
name = "beanstalk-ec2-user"
role = "${aws_iam_role.beanstalk_ec2.name}"
}
resource "aws_iam_role" "beanstalk_service" {
name = "beanstalk-service"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "elasticbeanstalk.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "elasticbeanstalk"
}
}
}
]
}
EOF
}
resource "aws_iam_role" "beanstalk_ec2" {
name = "aws-elasticbeanstalk-ec2-role"
assume_role_policy = <<EOF
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_policy_attachment" "beanstalk_service_health" {
name = "elastic-beanstalk-service-health"
roles = ["${aws_iam_role.beanstalk_service.id}"]
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth"
}
resource "aws_iam_policy_attachment" "beanstalk_ec2_worker" {
name = "elastic-beanstalk-ec2-worker"
roles = ["${aws_iam_role.beanstalk_ec2.id}"]
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier"
}
resource "aws_iam_service_linked_role" "managedupdates_eb" {
aws_service_name = "managedupdates.elasticbeanstalk.amazonaws.com"
}
resource "aws_iam_policy_attachment" "beanstalk_ec2_web" {
name = "elastic-beanstalk-ec2-web"
roles = ["${aws_iam_role.beanstalk_ec2.id}"]
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier"
}
resource "aws_iam_policy_attachment" "beanstalk_ec2_container" {
name = "elastic-beanstalk-ec2-container"
roles = ["${aws_iam_role.beanstalk_ec2.id}"]
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker"
}
resource "aws_iam_policy_attachment" "beanstalk_service" {
name = "elastic-beanstalk-service"
roles = ["${aws_iam_role.beanstalk_service.id}"]
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy"
}
I did attempt to create a linked service role but that is not the solution for the error above.
setting {
namespace = "aws:elasticbeanstalk:managedactions"
name = "ServiceRoleForManagedUpdates"
value = aws_iam_service_linked_role.managedupdates_eb.arn
}
I was missing the following settings
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "ServiceRole"
value = aws_iam_role.beanstalk_service.id
}

Fix "User: anonymous is not authorized to perform"

I struggle to have an AWS Lambda function to connect to an AWS ElasticSearch cluster.
I have an AWS Lambda function defined as the following:
resource "aws_lambda_function" "fun1" {
function_name = "fun1"
role = aws_iam_role.ia0.arn
vpc_config {
security_group_ids = local.security_group_ids
subnet_ids = local.subnet_ids
}
environment {
variables = {
ELASTICSEARCH_ENDPOINT = "https://${aws_elasticsearch_domain.es.endpoint}"
}
}
}
resource "aws_iam_role" "ia0" {
name = "lambda-exec-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.ia0.id
policy_arn = aws_iam_policy.lambda_logging.arn
}
data "aws_iam_policy" "AWSLambdaBasicExecutionRole" {
arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "AWSLambdaBasicExecutionRole" {
role = aws_iam_role.ia0.id
policy_arn = data.aws_iam_policy.AWSLambdaBasicExecutionRole.arn
}
data "aws_iam_policy" "AWSLambdaVPCAccessExecutionRole" {
arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
resource "aws_iam_role_policy_attachment" "AWSLambdaVPCAccessExecutionRole" {
role = aws_iam_role.ia0.id
policy_arn = data.aws_iam_policy.AWSLambdaVPCAccessExecutionRole.arn
}
My VPC is defined like that:
locals {
security_group_ids = [aws_security_group.sg0.id]
subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
}
resource "aws_vpc" "vpc0" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.vpc0.id
cidr_block = cidrsubnet(aws_vpc.vpc0.cidr_block, 2, 1)
availability_zone = "eu-west-3a"
}
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.vpc0.id
cidr_block = cidrsubnet(aws_vpc.vpc0.cidr_block, 2, 2)
availability_zone = "eu-west-3b"
}
resource "aws_security_group" "sg0" {
vpc_id = aws_vpc.vpc0.id
}
Finally my cluster looks like that:
resource "aws_elasticsearch_domain" "es" {
domain_name = "es"
elasticsearch_version = "7.9"
cluster_config {
instance_count = 2
zone_awareness_enabled = true
instance_type = "t2.small.elasticsearch"
}
domain_endpoint_options {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
ebs_options {
ebs_enabled = true
volume_size = 10
}
vpc_options {
security_group_ids = local.security_group_ids
subnet_ids = local.subnet_ids
}
}
resource "aws_iam_role_policy" "rp0" {
name = "rp0"
role = aws_iam_role.ia0.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"es:*"
],
"Resource": [
"${aws_elasticsearch_domain.es.arn}",
"${aws_elasticsearch_domain.es.arn}/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"${aws_subnet.private_a.cidr_block}",
"${aws_subnet.private_b.cidr_block}"
]
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeVpcs",
"ec2:DescribeVpcAttribute",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeNetworkInterfaces",
"ec2:CreateNetworkInterface",
"ec2:CreateNetworkInterfacePermission",
"ec2:DeleteNetworkInterface"
],
"Resource": [
"*"
]
}
]
}
EOF
}
Despite of that I still get this answer
Response
{ responseStatus = Status {statusCode = 403, statusMessage = "Forbidden"}
, responseVersion = HTTP/1.1
, responseHeaders =
[("Date","xxx")
,("Content-Type","application/json")
,("Content-Length","72")
,("Connection","keep-alive")
,("x-amzn-RequestId","xxx")
,("Access-Control-Allow-Origin","*")
]
, responseBody = "{\"Message\":\"User: anonymous is not authorized to perform: es:ESHttpPut\"}\"
, responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose
}"
According to AWS documentation using CIDR should be sufficient, but in practice, something is missing.
Thanks in advance for your help.
you need to sign the request before making a http call to tell Elastic search from who is initiating the request. I don't know which programming language you are using, here is what we can do in NodeJs
For simple http call
let request = new (AWS as any).HttpRequest(endpoint, 'us-east-1');
let credentials = new AWS.EnvironmentCredentials('AWS');
let signers = new (AWS as any).Signers.V4(request, 'es');
signers.addAuthorization(credentials, new Date());
if you are using a package like #elastic/elasticsearch, you can combine http-aws-es to create a client which creates a signature , might look something like
let options = {
hosts: [ yourHost ],
connectionClass: require('http-aws-es'),
awsConfig: new AWS.Config({ region: 'us-east-1', credentials: new AWS.EnvironmentCredentials('AWS') })
};
client = require('elasticsearch').Client(options);

invalid ARN error on terraform when creating eks cluster

**resource "aws_iam_role" "eks_role" {
name = "eks_role"
assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "eks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
POLICY
}
resource "aws_iam_role_policy_attachment" "AmazonEKSClusterPolicy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = "aws_iam_role.eks_role.name"
}
resource "aws_iam_role_policy_attachment" "AmazonEKSServicePolicy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy"
role = "aws_iam_role.eks_role.name"
}
resource "aws_eks_cluster" "t3_eks" {
name = "t3_eks"
role_arn = "aws_iam_role.eks_role.arn"
vpc_config {
security_group_ids = var.sg
subnet_ids = var.subnets
endpoint_private_access = false
endpoint_public_access = true
}
depends_on = [
aws_iam_role_policy_attachment.AmazonEKSClusterPolicy,
aws_iam_role_policy_attachment.AmazonEKSServicePolicy,
]
}**
Error message
Error: "role_arn" (aws_iam_role.eks_role.arn) is an invalid ARN: arn: invalid prefix
on EKS\main.tf line 30, in resource "aws_eks_cluster" "t3_eks":
30: resource "aws_eks_cluster" "t3_eks" {
Please could someone guide as to what could be wrong?
Quotes are important with terraform. In 0.12 the quoted string "aws_iam_role.eks_role.arn" is just a string. In order for it to be interpolated as an actual variable, you need to remove the quotes:
resource "aws_eks_cluster" "t3_eks" {
name = "t3_eks"
role_arn = aws_iam_role.eks_role.arn
It is also possible to interpolate a variable inside of a string, which is required for terraform 0.11 or older:
resource "aws_eks_cluster" "t3_eks" {
name = "t3_eks"
role_arn = "${aws_iam_role.eks_role.arn}"

Terraform API Gateway with Lambda integration

I've started using terraform a few days ago, so I am a beginner in this topic. I want to create an API Gateway and a Lambda with a Layer as a custom runtime to run R script. Here are my Terraform files:
lambda.tf
resource "aws_lambda_function" "testlambda" {
function_name = "rlambda"
s3_bucket = "mybucket"
s3_key = "rlambda/v1.0.3/rlambda.zip"
handler = "main.main"
runtime = "provided"
memory_size = 512
timeout = 30
layers = [aws_lambda_layer_version.r_layer.arn]
role = aws_iam_role.lambda_role.arn
}
# IAM role for lambda
resource "aws_iam_role" "lambda_role" {
name = "lambda_role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
# Lambda Policy
resource "aws_lambda_permission" "apigw" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.testlambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.apigateway.execution_arn}/*/POST/dev"
depends_on = [
aws_api_gateway_rest_api.apigateway,
aws_api_gateway_resource.apiresource,
]
}
# Lambda Layer
resource "aws_lambda_layer_version" "r_layer" {
layer_name = "rlayer"
s3_bucket = "mybucket"
s3_key = "lambdalayer/v1.0.3/rlayer.zip"
}
# Cloudwatch Logging for Lambda
resource "aws_iam_policy" "lambda_logging" {
name = "lambda_logging"
path = "/"
description = "IAM policy for logging from a lambda"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.lambda_logging.arn
}
api_gateway.tf
resource "aws_api_gateway_rest_api" "apigateway" {
name = "ApiGatewayTest"
description = "Terraform Created Api Gateway"
binary_media_types = ["multipart/form-data", "application/octet-stream"]
}
resource "aws_api_gateway_resource" "apiresource" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
parent_id = aws_api_gateway_rest_api.apigateway.root_resource_id
path_part = "dev"
}
# Method
resource "aws_api_gateway_method" "method" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = "POST"
authorization = "NONE"
}
# Integration
resource "aws_api_gateway_integration" "integration" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_method.method.resource_id
http_method = aws_api_gateway_method.method.http_method
integration_http_method = "POST"
type = "AWS"
uri = aws_lambda_function.testlambda.invoke_arn
passthrough_behavior = "WHEN_NO_TEMPLATES"
request_templates = {
"multipart/form-data" = file("api_gateway_body_mapping.template")
}
depends_on = [aws_api_gateway_method.method]
}
# Method Response
resource "aws_api_gateway_method_response" "methodresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.method.http_method
status_code = "200"
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = true,
"method.response.header.Access-Control-Expose-Headers" = true,
"method.response.header.Content-Disposition" = true,
"method.response.header.Content-Type" = true
}
}
# Integration Response
resource "aws_api_gateway_integration_response" "integrationresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.method.http_method
status_code = aws_api_gateway_method_response.methodresponse.status_code
content_handling = "CONVERT_TO_BINARY"
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = "'*'",
"method.response.header.Access-Control-Expose-Headers" = "'Content-Disposition'",
"method.response.header.Content-Disposition" = "'attachment'",
"method.response.header.Content-Type" = "'application/octet-stream'"
}
depends_on = [aws_api_gateway_integration.integration]
}
# CORS Method
resource "aws_api_gateway_method" "cors_method" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = "OPTIONS"
authorization = "NONE"
}
# CORS Integration
resource "aws_api_gateway_integration" "cors_integration" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.cors_method.http_method
type = "MOCK"
request_templates = {
"application/json" = <<EOF
{"statusCode": 200}
EOF
}
depends_on = [aws_api_gateway_method.cors_method]
}
# CORS Method Response
resource "aws_api_gateway_method_response" "cors_methodresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.cors_method.http_method
status_code = "200"
response_models = {
"application/json" = "Empty"
}
response_parameters = {
"method.response.header.Access-Control-Allow-Headers" = true,
"method.response.header.Access-Control-Allow-Methods" = true,
"method.response.header.Access-Control-Allow-Origin" = true
}
depends_on = [aws_api_gateway_method.cors_method]
}
# CORS Integration Response
resource "aws_api_gateway_integration_response" "cors_integrationresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.cors_method.http_method
status_code = aws_api_gateway_method_response.cors_methodresponse.status_code
response_parameters = {
"method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
"method.response.header.Access-Control-Allow-Methods" = "'OPTIONS,POST'",
"method.response.header.Access-Control-Allow-Origin" = "'*'"
}
response_templates = {
"application/json" = <<EOF
EOF
}
depends_on = [
aws_api_gateway_method_response.cors_methodresponse,
aws_api_gateway_integration.cors_integration,
]
}
# Deployment
resource "aws_api_gateway_deployment" "apideployment" {
depends_on = [
aws_api_gateway_integration.integration,
aws_api_gateway_integration.cors_integration
]
rest_api_id = aws_api_gateway_rest_api.apigateway.id
stage_name = "dev"
}
output "base_url" {
value = aws_api_gateway_deployment.apideployment.invoke_url
}
api_gateway_body_mapping.template
{
"body": "$input.body",
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))"
#if($foreach.hasNext),#end
#end
}
}
So terraform init and apply completes, but API Gateway shows Internal Server Error. (Calling it from postman, with this url: https://(randomchars).execute-api.eu-central-1.amazonaws.com/dev/dev)
"Execution failed due to configuration error: Unable to transform request" - This is the error from the api's cloudwatch log.
If I go to the Integration Request of the POST method execution on the AWS console, and I re-add the Function name (rlambda) it adds another policy to the lambda (Exactly the same as it was before, with different Sid of course) and I re-deploy my API, then it works completely. (It does not work if I only do the deploy API part.)
So my questions are:
Is there anything happening in the background when I re-add my Function name ? (Except the policy thing)
What to change in my code to be able to call api gateway right after terraform apply?
Okay so after 5 days of suffering I realized what is the problem.
On the AWS console you are not able to set the Integration Request's content_handling and it is only an Optional parameter in Terraform as well.
When you are re-assigning your lambda's name on the console, not only the lambda's policy got updated, but also the integration request's content_handling got set to CONVERT_TO_TEXT. I don't know why it is changing and why to this value. Maybe it's based on the lambda - there is no information about this.
So I added this row to the aws_api_gateway_integration:
content_handling = "CONVERT_TO_TEXT"
And it solved my problem. Hope nobody will run into this again.

Terraform Lambda Issues

I am trying to build an deploy an Lambda written in Go and want to use terraform to deploy. I followed the steps on the terraform site.
But the difference between that example and my app is I have multiple subroutes. When I try to call the application I get this error in the API Gateway test:
Sat Sep 22 11:06:31 UTC 2018 : Endpoint response headers: {Date=Sat,
22 Sep 2018 11:06:31 GMT, Content-Length=130, Connection=keep-alive,
x-amzn-RequestId=8f57fab6-be57-11e8-a99b-2ba9ede2859c} Sat Sep 22
11:06:31 UTC 2018 : Lambda invocation failed with status: 403. Lambda
request id: 8f57fab6-be57-11e8-a99b-2ba9ede2859c Sat Sep 22 11:06:31
UTC 2018 : Execution failed due to configuration error: Sat Sep 22
11:06:31 UTC 2018 : Method completed with status: 500
I'm not sure what I need, here is my code:
variable "app_version" {
}
variable "region" {
default = "us-east-1"
}
variable account_id {
default = "412092673045"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_lambda_function" "example" {
function_name = "EXAMPLE"
# The bucket name as created earlier with "aws s3api create-bucket"
s3_bucket = "example-core"
s3_key = "v${var.app_version}/main.zip"
# "main" is the filename within the zip file (main.js) and "handler"
# is the name of the property under which the handler function was
# exported in that file.
handler = "main"
runtime = "go1.x"
role = "${aws_iam_role.lambda_exec.arn}"
environment={
variables = {
REDIS_URL = "XXXXXXXX"
REDIS_PASSWORD = "XXXXXXX"
}
}
}
# IAM role which dictates what other AWS services the Lambda function
# may access.
resource "aws_iam_role" "lambda_exec" {
name = "serverless_example_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_lambda_permission" "allow_api_gateway" {
function_name = "${aws_lambda_function.example.function_name}"
statement_id = "AllowExecutionFromApiGateway"
action = "lambda:InvokeFunction"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_iam_role.lambda_exec.arn}"
}
resource "aws_api_gateway_rest_api" "example" {
name = "ServerlessExample"
description = "Terraform Serverless Application Example"
}
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = "${aws_api_gateway_rest_api.example.id}"
parent_id = "${aws_api_gateway_rest_api.example.root_resource_id}"
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "proxy" {
rest_api_id = "${aws_api_gateway_rest_api.example.id}"
resource_id = "${aws_api_gateway_resource.proxy.id}"
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda" {
rest_api_id = "${aws_api_gateway_rest_api.example.id}"
resource_id = "${aws_api_gateway_method.proxy.resource_id}"
http_method = "${aws_api_gateway_method.proxy.http_method}"
integration_http_method = "ANY"
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${var.region}:${var.account_id}:function:${aws_lambda_function.example.function_name}/invocations"
}
resource "aws_api_gateway_method" "proxy_root" {
rest_api_id = "${aws_api_gateway_rest_api.example.id}"
resource_id = "${aws_api_gateway_rest_api.example.root_resource_id}"
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda_root" {
rest_api_id = "${aws_api_gateway_rest_api.example.id}"
resource_id = "${aws_api_gateway_method.proxy_root.resource_id}"
http_method = "${aws_api_gateway_method.proxy_root.http_method}"
integration_http_method = "ANY"
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${var.region}:${var.account_id}:function:${aws_lambda_function.example.function_name}/invocations"
}
resource "aws_api_gateway_deployment" "example" {
depends_on = [
"aws_api_gateway_integration.lambda",
"aws_api_gateway_integration.lambda_root",
]
rest_api_id = "${aws_api_gateway_rest_api.example.id}"
stage_name = "api"
}
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.example.arn}"
principal = "apigateway.amazonaws.com"
# The /*/* portion grants access from any method on any resource
# within the API Gateway "REST API".
source_arn = "${aws_api_gateway_deployment.example.execution_arn}/*/*"
}
output "base_url" {
value = "${aws_api_gateway_deployment.example.invoke_url}"
}
resource "aws_iam_policy" "lambda_logging" {
name = "lambda_logging"
path = "/"
description = "IAM policy for logging from a lambda"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = "${aws_iam_role.lambda_exec.name}"
policy_arn = "${aws_iam_policy.lambda_logging.arn}"
}