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.
Related
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}/*/*"
}
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);
I'm having trouble trying to set this infrastructure: I need an Aurora serverless cluster running PostgreSQL and access it using Secrets Manager. I also want to rotate the secret using a Lambda function every X amount of days.
However, I can't get the Lambda function to connect to the RDS cluster even with the original credentials. What am I doing wrong? Is it not possible to do this?
This is my Terraform code:
# --- LOCALS
# ---
locals {
db_role_name = "MYAPP-app-backuprestore"
db_name = "MYAPP-rds-${var.region}"
option_group_name = "MYAPP-rds-optiongroup"
security_group_name = "MYAPP-vpc-scg"
db_subnet_group_name = "MYAPP-vpc-sng"
rotation_lambda_function_name = "MYAPP-secretsmanager-rotationlambda-${var.region}"
rotation_lambda_role_name = "MYAPP-app-rotationlambda"
dbi_credentials_secret_name = "MYAPP/rds/master-credentials"
dbi_name = "MYAPP-rds-${var.region}"
backup_bucket_name = var.backup_bucket_name != "" ? var.backup_bucket_name : "MYAPP-data-${var.region}-${var.target_account_id}"
backup_location = var.backup_object_prefix == "" ? local.backup_bucket_name : "${local.backup_bucket_name}/${var.backup_object_prefix}"
common_tags = {
"owner:technical" = var.technical_owner
"owner:business" = var.business_owner
migrated = "False"
environment = var.environment
}
db_tags = merge(
local.common_tags,
{
c7n_start = 1
confidentiality = "restricted"
Name = local.db_name
}
)
role_tags = merge(
local.common_tags,
{
Name = local.db_role_name
}
)
option_group_tags = merge(
local.common_tags,
{
Name = local.option_group_name
}
)
security_group_tags = merge(
local.common_tags,
{
Name = local.security_group_name
}
)
db_subnet_group_tags = merge(
local.common_tags,
{
Name = local.db_subnet_group_name
}
)
rotation_lambda_tags = merge(
local.common_tags,
{
Name = local.rotation_lambda_function_name
}
)
rotation_lambda_role_tags = merge(
local.common_tags,
{
Name = local.rotation_lambda_role_name
}
)
dbi_credentials_secret_tags = merge(
local.common_tags,
{
Name = local.dbi_credentials_secret_name
}
)
}
# --- OPTION GROUP
# ---
resource "aws_iam_role" "rds_restore_role" {
name = local.db_role_name
tags = local.role_tags
assume_role_policy = <<-POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
POLICY
}
resource "aws_iam_role_policy" "rds_backup_policy" {
role = aws_iam_role.rds_restore_role.id
policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListContentInBackupBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::${local.backup_bucket_name}",
"Condition": {
"StringLike": {
"s3:prefix": [
"${var.backup_object_prefix}",
"${var.backup_object_prefix}/*"
]
}
}
},
{
"Sid": "GetBucketLocation",
"Effect": "Allow",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::${local.backup_bucket_name}"
},
{
"Sid": "ReadWriteObjects",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::${local.backup_location}/*"
},
{
"Sid": "CheckAccessToBucketAndObjects",
"Effect": "Allow",
"Action": "s3:HeadBucket",
"Resource": "*"
}
]
}
EOF
}
# --- SECURITY GROUP
# ---
data "aws_vpcs" "vpc_ids" {}
resource "aws_security_group" "vpc_security_group" {
name = local.security_group_name
description = ""
tags = local.security_group_tags
vpc_id = tolist(data.aws_vpcs.vpc_ids.ids)[0]
ingress {
description = "Allow incoming connections from network"
from_port = 3306
to_port = 3306
protocol = "tcp"
cidr_blocks = [var.dbi_secgroup]
self = true
}
# Allows rotation Lambda to reach Secrets Manager API
egress {
description = "Allow outgoing connections"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# --- SUBNET
# ---
data "aws_subnet_ids" "private_subnets" {
vpc_id = tolist(data.aws_vpcs.vpc_ids.ids)[0]
filter {
name = "tag:aws:cloudformation:logical-id"
values = ["PrivateSubnet1", "PrivateSubnet2"]
}
}
resource "aws_db_subnet_group" "db_subnet_group" {
name = local.db_subnet_group_name
subnet_ids = data.aws_subnet_ids.private_subnets.ids
tags = local.db_subnet_group_tags
}
# --- AURORA SERVERLESS
resource "aws_rds_cluster" "default" {
cluster_identifier = local.db_name
vpc_security_group_ids = [ aws_security_group.vpc_security_group.id ]
db_subnet_group_name = aws_db_subnet_group.db_subnet_group.id
engine_mode = "serverless"
engine = "aurora-postgresql"
engine_version = "10.7"
master_username = var.dbi_user_name
master_password = var.dbi_password
backup_retention_period = 30
storage_encrypted = true
apply_immediately = true
database_name = "foobar"
scaling_configuration {
auto_pause = true
max_capacity = 2
min_capacity = 2
seconds_until_auto_pause = 500
}
skip_final_snapshot = true
lifecycle {
ignore_changes = [
"engine_version",
]
}
}
# --- SECRET MANAGER
resource "aws_secretsmanager_secret" "db_instance_credentials_secret" {
name = local.dbi_credentials_secret_name
description = ""
tags = local.dbi_credentials_secret_tags
}
resource "aws_secretsmanager_secret_version" "db_instance_credentials_secret_values" {
secret_id = aws_secretsmanager_secret.db_instance_credentials_secret.id
secret_string = jsonencode({
username: var.dbi_user_name,
password: var.dbi_password,
engine: "postgres",
host: aws_rds_cluster.default.endpoint,
port: 5432,
dbInstanceIdentifier: aws_rds_cluster.default.id
})
}
resource "aws_ssm_parameter" "db_instance_credentials_secret_name" {
name = "MYAPP/dbi_credentials_secret_arn"
type = "String"
value = aws_secretsmanager_secret.db_instance_credentials_secret.arn
}
# -- Rotation
resource "aws_secretsmanager_secret_rotation" "db_instance_credentials_rotation" {
secret_id = aws_secretsmanager_secret.db_instance_credentials_secret.id
rotation_lambda_arn = aws_lambda_function.secret_rotation_lambda.arn
rotation_rules {
automatically_after_days = var.lambda_rotation_days
}
}
# --- LAMBDA
# ---
resource "aws_lambda_function" "secret_rotation_lambda" {
filename = "lambda/${var.rotation_lambda_filename}.zip"
function_name = local.rotation_lambda_function_name
role = aws_iam_role.lambda_rotation_role.arn
handler = "lambda_function.lambda_handler"
source_code_hash = filebase64sha256("lambda/${var.rotation_lambda_filename}.zip")
runtime = "python3.7"
vpc_config {
subnet_ids = data.aws_subnet_ids.private_subnets.ids
security_group_ids = [aws_security_group.vpc_security_group.id]
}
timeout = 300
description = ""
environment {
variables = {
SECRETS_MANAGER_ENDPOINT = "https://secretsmanager.${var.region}.amazonaws.com"
}
}
tags = local.rotation_lambda_tags
}
resource "aws_iam_role" "lambda_rotation_role" {
name = local.rotation_lambda_role_name
tags = local.rotation_lambda_role_tags
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" "policy_AWSLambdaBasicExecutionRole" {
role = aws_iam_role.lambda_rotation_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "policy_AWSLambdaVPCAccessExecutionRole" {
role = aws_iam_role.lambda_rotation_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
data "aws_iam_policy_document" "SecretsManagerRDSAuroraServerlessRotationSingleUserRolePolicy" {
statement {
actions = [
"ec2:CreateNetworkInterface",
"ec2:DeleteNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DetachNetworkInterface",
]
resources = ["*"]
}
statement {
actions = [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage",
]
resources = [
"arn:aws:secretsmanager:${var.region}:${var.target_account_id}:secret:*",
]
condition {
test = "StringEquals"
variable = "secretsmanager:resource/AllowRotationLambdaArn"
values = [aws_lambda_function.secret_rotation_lambda.arn]
}
}
statement {
actions = ["secretsmanager:GetRandomPassword"]
resources = ["*"]
}
}
resource "aws_iam_policy" "SecretsManagerRDSAuroraRotationSingleUserRolePolicy" {
path = "/"
policy = data.aws_iam_policy_document.SecretsManagerRDSAuroraRotationSingleUserRolePolicy.json
}
resource "aws_iam_role_policy_attachment" "SecretsManagerRDSAuroraRotationSingleUserRolePolicy" {
role = aws_iam_role.lambda_rotation_role.name
policy_arn = aws_iam_policy.SecretsManagerRDSAuroraRotationSingleUserRolePolicy.arn
}
resource "aws_lambda_permission" "allow_secret_manager_call_roation_lambda" {
function_name = aws_lambda_function.secret_rotation_lambda.function_name
statement_id = "AllowExecutionSecretManager"
action = "lambda:InvokeFunction"
principal = "secretsmanager.amazonaws.com"
}
The lambda/ folder has the code I downloaded from a Lambda function I set up manually to do the rotation, which I later deleted. The lambda_function.py code fails at this point:
def set_secret(service_client, arn, token):
# First try to login with the pending secret, if it succeeds, return
pending_dict = get_secret_dict(service_client, arn, "AWSPENDING", token)
conn = get_connection(pending_dict)
if conn:
conn.close()
logger.info("setSecret: AWSPENDING secret is already set as password in PostgreSQL DB for secret arn %s." % arn)
return
logger.info("setSecret: unable to log with AWSPENDING credentials")
curr_dict = get_secret_dict(service_client, arn, "AWSCURRENT")
# Now try the current password
conn = get_connection(curr_dict)
if not conn:
# If both current and pending do not work, try previous
logger.info("setSecret: unable to log with AWSCURRENT credentials")
try:
conn = get_connection(get_secret_dict(service_client, arn, "AWSPREVIOUS"))
except service_client.exceptions.ResourceNotFoundException:
logger.info("setSecret: Unable to log with AWSPREVIOUS credentials")
conn = None
It can't connect to the RDS cluster with any of the secrets, even though I can connect from the console using those credentials (username and password).
So I have this terraform that seems to deploy a websocket api connection to AWS BUT....
Once deployed, when I connect, I consistently get "429 too many requests" errors.
Using terraform 0.13.4.
I've turned up the requests manually in the console but every time I wscat -c {MYENDPOINT} I get a 429.
Can't find anything online or anything in the manuals about this.
Here is the terraform. Wondering if anyone can see if I'm missing something in my routes or integrations?
Here is the response I keep getting from the logs:
(VH_SDESljoEF7tg=) Gateway response body: { "message": "Too Many Requests", "connectionId": "VH_SDd21joECIeg=", "requestId": "VH_SDESljoEF7tg=" }
and
(VH_SDESljoEF7tg=) Key throttle limit exceeded for RestApi k27g2ypii6, Stage test, Resource $connect, HttpMethod GET. Limit: 42.00 Burst: 0
resource "aws_apigatewayv2_api" "websocket-api" {
name = "websocket-api"
protocol_type = "WEBSOCKET"
}
resource "aws_apigatewayv2_integration" "chatRoomConnectIntegration" {
api_id = aws_apigatewayv2_api.websocket-api.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.ChatRoomConnectFunction.invoke_arn
integration_method = "POST"
}
resource "aws_apigatewayv2_route" "connectRoute" {
api_id = aws_apigatewayv2_api.websocket-api.id
route_key = "$connect"
target = "integrations/${aws_apigatewayv2_integration.chatRoomConnectIntegration.id}"
}
resource "aws_apigatewayv2_deployment" "deploy" {
api_id = aws_apigatewayv2_api.websocket-api.id
description = "testing deployment"
triggers = {
redeployment = sha1(join(",", list(
jsonencode(aws_apigatewayv2_integration.chatRoomConnectIntegration),
jsonencode(aws_apigatewayv2_route.connectRoute),
)))
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_apigatewayv2_stage" "test-stage" {
api_id = aws_apigatewayv2_api.websocket-api.id
name = "test"
access_log_settings {
destination_arn = aws_cloudwatch_log_group.access_logs.arn
format = "$context.identity.sourceIp - - [$context.requestTime] \"$context.httpMethod $context.routeKey $context.protocol\" $context.status $context.responseLength $context.requestId $context.integrationErrorMessage"
}
default_route_settings {
data_trace_enabled = true
logging_level = "INFO"
throttling_rate_limit = 42
}
route_settings {
route_key = "$connect"
data_trace_enabled = true
logging_level = "INFO"
throttling_rate_limit = 42
}
}
resource "aws_api_gateway_account" "api_gateway_accesslogs" {
cloudwatch_role_arn = aws_iam_role.cloudwatch.arn
}
resource "aws_iam_role" "cloudwatch" {
name = "api_gateway_cloudwatch_global"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy" "cloudwatch" {
name = "default"
role = aws_iam_role.cloudwatch.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:GetLogEvents",
"logs:FilterLogEvents"
],
"Resource": "*"
}
]
}
EOF
}
resource "aws_lambda_permission" "allow_api_gateway" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.ChatRoomConnectFunction.arn
statement_id = "AllowExecutionFromApiGateway"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.websocket-api.execution_arn}/*/*/*"
}
output "endpoint" {
value = aws_apigatewayv2_stage.test-stage.invoke_url
}
I can't explain the cause of the throttling, but I added this block to my aws_apigatewayv2_stage resource, triggered a new deployment, and now I'm able to connect using wscat:
default_route_settings {
throttling_rate_limit = 100
throttling_burst_limit = 50
}
(relevant docs here)
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}"
}