227 lines
6.9 KiB
Terraform
227 lines
6.9 KiB
Terraform
/**
|
|
* # LambdaAccessKey
|
|
*
|
|
* Module to create a lambda function, which assumes to a certain role and
|
|
* get temporary access credentials. The lambda function url is protected
|
|
* by cloudfront and origin access control. Credentials are encrypted. Once
|
|
* resources are deployed, run client.py to send http request and decrypt
|
|
* the response
|
|
*
|
|
* Cloudfront fixed-rate pricing cannot be controlled by terraform or awscli
|
|
* at time of writing. Change to the free plan on aws console.
|
|
*
|
|
* To destroy the cloudfront distribution, you need to cancel the fixed rate plan
|
|
*/
|
|
|
|
# data sources
|
|
data "aws_caller_identity" "current" {}
|
|
|
|
# IAM role to assume role to
|
|
resource "random_uuid" "ExternalId" {}
|
|
|
|
module "TargetIam" {
|
|
source = "../iam-role-v2"
|
|
trusted-entity = jsonencode(
|
|
{
|
|
"Version" : "2012-10-17",
|
|
"Statement" : [
|
|
{
|
|
"Effect" : "Allow",
|
|
"Principal" : {
|
|
"AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
|
|
},
|
|
"Action" : "sts:AssumeRole",
|
|
"Condition" : {
|
|
"StringEquals" : {
|
|
"sts:ExternalId" : random_uuid.ExternalId.id
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
)
|
|
role-name = var.role_name
|
|
description = "Trusted access role"
|
|
path = "/"
|
|
max-session-duration = 14400 # 4 hours
|
|
attach-managed-policies = [
|
|
"arn:aws:iam::aws:policy/IAMFullAccess",
|
|
"arn:aws:iam::aws:policy/PowerUserAccess"
|
|
]
|
|
}
|
|
|
|
# Lambda execution IAM role
|
|
resource "aws_iam_policy" "LamdaExecRole" {
|
|
name_prefix = "${var.role_name}-LambdaExecRole"
|
|
description = "Lambda execution role policy"
|
|
policy = jsonencode(
|
|
{
|
|
"Version" : "2012-10-17",
|
|
"Statement" : [
|
|
{
|
|
"Effect" : "Allow",
|
|
"Action" : "logs:CreateLogGroup",
|
|
"Resource" : "arn:aws:logs:us-east-1:${data.aws_caller_identity.current.account_id}:*"
|
|
},
|
|
{
|
|
"Effect" : "Allow",
|
|
"Action" : [
|
|
"logs:CreateLogStream",
|
|
"logs:PutLogEvents"
|
|
],
|
|
"Resource" : [
|
|
"arn:aws:logs:us-east-1:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${var.role_name}:*"
|
|
]
|
|
},
|
|
{
|
|
"Effect" : "Allow",
|
|
"Action" : [
|
|
"sts:AssumeRole"
|
|
],
|
|
"Resource" : [
|
|
module.TargetIam.role-arn
|
|
]
|
|
}
|
|
]
|
|
}
|
|
)
|
|
}
|
|
|
|
module "LambdaExecRole" {
|
|
source = "../iam-role-v2"
|
|
trusted-entity = "lambda.amazonaws.com"
|
|
role-name = "${var.role_name}-LambdaExecRole"
|
|
description = "Lambda execution role"
|
|
path = "/"
|
|
max-session-duration = 3600 # 1 hour
|
|
attach-managed-policies = [
|
|
aws_iam_policy.LamdaExecRole.arn
|
|
]
|
|
}
|
|
|
|
# Lambda function
|
|
resource "random_password" "this" {
|
|
length = 50
|
|
override_special = "~@#%^&*-_=+:;<,>./"
|
|
}
|
|
|
|
resource "local_file" "FunctionCode" {
|
|
content = templatefile("${path.module}/FunctionCode.tpl", {
|
|
target_role = module.TargetIam.role-arn
|
|
encryption_pass = random_password.this.result
|
|
external_id = random_uuid.ExternalId.result
|
|
})
|
|
filename = "${path.module}/FunctionCode.py"
|
|
}
|
|
|
|
data "archive_file" "LambdaZip" {
|
|
type = "zip"
|
|
source_file = local_file.FunctionCode.filename
|
|
output_path = "${path.module}/FunctionCode.zip"
|
|
}
|
|
|
|
# Lambda function
|
|
resource "aws_lambda_function" "this" {
|
|
filename = data.archive_file.LambdaZip.output_path
|
|
code_sha256 = data.archive_file.LambdaZip.output_base64sha256
|
|
function_name = var.role_name
|
|
role = module.LambdaExecRole.role-arn
|
|
handler = "FunctionCode.lambda_handler"
|
|
runtime = "python3.14"
|
|
architectures = ["arm64"]
|
|
reserved_concurrent_executions = 2
|
|
}
|
|
|
|
resource "aws_lambda_function_url" "this" {
|
|
function_name = aws_lambda_function.this.function_name
|
|
authorization_type = "AWS_IAM"
|
|
}
|
|
|
|
resource "aws_lambda_permission" "AllowCloudFrontServicePrincipal" {
|
|
statement_id = "AllowCloudFrontServicePrincipal"
|
|
action = "lambda:InvokeFunctionUrl"
|
|
function_name = aws_lambda_function.this.function_name
|
|
principal = "cloudfront.amazonaws.com"
|
|
source_arn = ""
|
|
}
|
|
|
|
resource "aws_lambda_permission" "AllowCloudFrontServicePrincipalInvokeFunction" {
|
|
statement_id = "AllowCloudFrontServicePrincipalInvokeFunction"
|
|
action = "lambda:InvokeFunction"
|
|
function_name = aws_lambda_function.this.function_name
|
|
principal = "cloudfront.amazonaws.com"
|
|
source_arn = ""
|
|
}
|
|
|
|
|
|
# CloudFront flatrate plan
|
|
resource "aws_cloudfront_origin_access_control" "CloudfrontOac" {
|
|
name = "lambda-function-url-oac"
|
|
description = "OAC for secure Lambda Function URL backend connection"
|
|
origin_access_control_origin_type = "lambda"
|
|
signing_behavior = "always"
|
|
signing_protocol = "sigv4"
|
|
}
|
|
|
|
resource "aws_cloudfront_distribution" "this" {
|
|
enabled = true
|
|
is_ipv6_enabled = true
|
|
price_class = "PriceClass_All"
|
|
|
|
origin {
|
|
domain_name = split("/", aws_lambda_function_url.this.function_url)[2]
|
|
origin_id = "LambdaBackendOrigin"
|
|
origin_access_control_id = aws_cloudfront_origin_access_control.CloudfrontOac.id
|
|
custom_origin_config {
|
|
http_port = 80
|
|
https_port = 443
|
|
origin_protocol_policy = "https-only"
|
|
origin_ssl_protocols = ["TLSv1.2"]
|
|
}
|
|
}
|
|
|
|
default_cache_behavior {
|
|
allowed_methods = ["GET", "HEAD"]
|
|
cached_methods = ["GET", "HEAD"]
|
|
target_origin_id = "LambdaBackendOrigin"
|
|
viewer_protocol_policy = "redirect-to-https"
|
|
|
|
# Use default AWS-managed Cache/Origin Request policies to stay within flat-rate free bounds
|
|
cache_policy_id = data.aws_cloudfront_cache_policy.NoCache.id
|
|
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.AllButHost.id # host header must be dropped for OAC
|
|
}
|
|
|
|
# Flat-rate plans include basic managed WAF rule support automatically
|
|
restrictions {
|
|
geo_restriction {
|
|
restriction_type = "none"
|
|
}
|
|
}
|
|
|
|
viewer_certificate {
|
|
cloudfront_default_certificate = true
|
|
}
|
|
|
|
lifecycle {
|
|
ignore_changes = [
|
|
web_acl_id # after converting to flat-rate plan, a waf acl will be attached and we want to allow it
|
|
]
|
|
}
|
|
}
|
|
|
|
data "aws_cloudfront_cache_policy" "NoCache" {
|
|
name = "Managed-CachingDisabled"
|
|
}
|
|
|
|
data "aws_cloudfront_origin_request_policy" "AllButHost" {
|
|
name = "Managed-AllViewerExceptHostHeader"
|
|
}
|
|
|
|
# Decryption file / client
|
|
resource "local_file" "client" {
|
|
content = templatefile("${path.module}/client.tpl", {
|
|
encryption_pass = random_password.this.result
|
|
cloudfront_url = "https://${aws_cloudfront_distribution.this.domain_name}"
|
|
})
|
|
filename = "${path.module}/client.py"
|
|
} |