1
0
Files
terraform.examples/modules/security_identity_compliance/LambdaAccessKey/main.tf
T
2026-06-14 16:05:47 +08:00

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"
}