lambda (terraform)
Reference
AWS Lambda Functions (serverless)
Outgoing connections via NATGW
Only if it's needed to connect to non-public resources available only if connecting from the static IP addresses of the NATGW.
For lambda to make outgoing connections via NATGW, it is needed to connect the lambda to the VPC.
For example (for 'cou' organization'), for attaching the lambda to the VPC, definition inside the lambda:
vpc_config {
subnet_ids = var.private_subnets_id
security_group_ids = [
aws_security_group.auth_cou.id,
]
}
It's important to take into account that also is needed to add a policy to allow the lambda function rise network interfaces.
resource "aws_iam_role_policy_attachment" "auth_cou_policy_vpc" {
role = aws_iam_role.auth_cou.id
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
In case of using serverless framework, this is the link to the documentation:
Lambda@Edge sample (intercepts 403 and 404 responses from origin S3 and redirects user to /index.html)
Solution for the reload page [F5] issue when frontend is deployed to an S3 bucket (served by cloudfront).
The Lambda@Edge is configured in block lambda_function_association inside the aws_cloudfront_distribution.
Ref: own title val, final project def
Frontend (css, html, javascript) in S3 bucket
The goal is to have, once deployed, an index.html file placed in the S3 bucket root so that the Lambda@Edge can return it to the user's browser.
Note: This sample uses Angular framework, but the idea is the same for any another frontend development
src/assets/index.html
The file to be copied to the S3 bucket root
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="description" content="package.json postbuild copies it to the dist root">
<script type="text/javascript">
window.location.href ="/myapp_front/en/index.html"
</script>
</head>
<body>
</body>
</html>
package.json
Notice the "postbuild" (copy:index) and the "devDependencies" (cpx-fixed)
{
"name": "myapp_front",
"version": "5.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"start:ca": "ng serve --configuration=ca",
"build:ca": "ng build --configuration=production-ca",
"start:es": "ng serve --configuration=es",
"build:es": "ng build --configuration=production-es",
"extract": "ng xi18n --output-path=locale",
"package": "npm install && npm run build && npm run build:ca && npm run build:es",
"postbuild": "npm run copy:index && node -p \"require('./package.json').version\" > dist/defensatf_front/version",
"copy:index": "cpx-fixed 'src/assets/index.html' 'dist/'"
},
"private": true,
"dependencies": {
...
},
"devDependencies": {
...
"cpx-fixed": "^1.6.0",
...
}
}
Terraform CloudFront
modules/lambda/variables.tf
variable "iam_role_lambdaedge_exec_arn" {
description = "ARN of the IAM execution role for Lambda@Edge"
type = string
nullable = false
}
modules/lambda/outputs.tf
output "lambdaedge_s3_origin_response_qualified_arn" {
value = aws_lambda_function.lambdaedge_s3_origin_response.qualified_arn
}
modules/lambda/lambdaedge.tf
# Get the default tags from the provider
data "aws_default_tags" "app" {}
locals {
env = data.aws_default_tags.app.tags.env
ci = data.aws_default_tags.app.tags.ci
}
provider "aws" {
alias = "n_virginia"
default_tags {
tags = {
env = local.env
ci = local.ci
Department = data.aws_default_tags.app.tags.Department
Program = data.aws_default_tags.app.tags.Program
}
}
region = "us-east-1"
}
#DOC. When CloudFront uses a Lambda@Edge, you need to wait a few hours (!) after you delete the distribution to delete the function.
# Keep trying to terraform destroy until you succeed.
resource "aws_lambda_function" "lambdaedge_s3_origin_response" {
#Makes sure this function goes to us-east-1 no matter where the rest of the resources are deployed
provider = aws.n_virginia
#Required args
function_name = "${local.env}-${local.ci}-lamdaedge-s3-origin-response"
role = var.iam_role_lambdaedge_exec_arn
#Optional args
description = "Redirect to /index.html if S3 response is 403 or 404 (F5 reload)"
filename = "${path.module}/lambda_file/edge_s3_origin_response-23.3.13.zip"
handler = "index.handler"
memory_size = 128
publish = true
runtime = "nodejs14.x"
source_code_hash = filebase64sha256("${path.module}/lambda_file/edge_s3_origin_response-23.3.13.zip")
timeout = 1
tracing_config {
mode = "Active"
}
}
modules/lambda/lambda_file/edge_s3_origin_response-23.3.13.zip
Note: Archive contains only one file, with name "index.js" and the following content
"use strict";
/**
* Region us-east-1 (N. Virginia): You must be in this Region to create Lambda@Edge functions.
*
* This function updates the HTTP status code in the response to 302, to redirect to the index.html. Note the following:
* 1. The function is triggered in a CloudFront origin response
* 2. The response status from the origin server is an error status code (403 or 404)
*/
exports.handler = async (event, context) => {
const request = event.Records[0].cf.request;
const response = event.Records[0].cf.response;
if (response.status == 403 || response.status == 404) {
const redirect_path = "/index.html";
response.status = 302;
response.statusDescription = "Found";
/* Drop the body, as it is not required for redirects */
delete response.body;
response.headers["cou-lambdaedge"] = [{ key: "cou-lambdaedge", value: context.functionName + " v" + context.functionVersion }];
response.headers["location"] = [{ key: "Location", value: redirect_path }];
}
return response;
};
modules/iam/iam.tf
#Lambda@Edge execution role with permission to upload logs to CloudWatch and trigger from CloudFront
resource "aws_iam_role" "lambdaedge_exec" {
#Required args
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
#Optional args
description = "${var.env}-${var.ci} Lambda@Edge execution role with permission to upload logs to CloudWatch and trigger from CloudFront"
name = "${var.env}-${var.ci}-lambdaedge-role"
path = "/${var.env}/${var.ci}/"
}
modules/iam/outputs.tf
output "role_lambdaedge_exec_arn" {
value = aws_iam_role.lambdaedge_exec.arn
}
modules/cloudfront/variables.tf
variable "lambda_lambdaedge_s3_origin_response_qualified_arn" {
description = "Qualified ARN of Lambda@Edge of CloudFront function association for the event type origin-response"
type = string
nullable = false
}
modules/cloudfront/outputs.tf
modules/cloudfront/cloudfront.tf
resource "aws_cloudfront_distribution" "myapp" {
...
# Cache behavior with precedence 0
ordered_cache_behavior {
path_pattern = "/myapp_front/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "${var.bucket_myappp}-${var.env}"
forwarded_values {
query_string = false
headers = ["Origin", "Authorization"]
cookies {
forward = "none"
}
}
lambda_function_association {
event_type = "origin-response" #{viewer-request, origin-request, viewer-response, origin-response}
lambda_arn = var.lambda_lambdaedge_s3_origin_response_qualified_arn
include_body = false #The IncludeBody option can only be used with viewer-request or origin-request events
}
min_ttl = 0
default_ttl = var.default-ttl
max_ttl = var.max-ttl
compress = true
viewer_protocol_policy = "redirect-to-https"
}
...
}
pre/main.tf
module "cloudfront" {
source = "../modules/cloudfront"
...
lambda_lambdaedge_s3_origin_response_qualified_arn = module.lambda.lambdaedge_s3_origin_response_qualified_arn
...
}
module "lambda" {
source = "../modules/lambda"
...
iam_role_lambdaedge_exec_arn = module.iam.role_lambdaedge_exec_arn
...
}