diff --git a/terraform/main.tf b/terraform/main.tf index 09a6eb7..6e84074 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -3,8 +3,7 @@ locals { vpce_services = compact([ - "s3", - var.deploy_api ? "execute-api" : null + "s3" ]) bucket_name = try(var.domain_name, "s3-private-vpce-${data.aws_caller_identity.current.account_id}") @@ -57,26 +56,12 @@ module "s3" { bucket_name = local.bucket_name s3_vpce_id = module.vpce["s3"].id s3_access_allowed_roles = concat( - var.s3_access_allowed_roles, - var.deploy_api ? [module.api[0].lambda_role_arn] : [] + var.s3_access_allowed_roles ) s3_access_allow_aws_services = var.s3_access_allow_aws_services s3_access_allowed_service_principals = var.s3_access_allowed_service_principals } -module "api" { - count = var.deploy_api ? 1 : 0 - source = "./modules/api" - - vpc_id = var.vpc_id - subnet_ids = var.private_subnet_ids - - domain_name = var.domain_name - s3_bucket_name = module.s3.bucket_name - s3_kms_key_arn = module.s3.kms_key_arn - s3_endpoint_url = var.domain_name != null ? var.domain_name : replace(module.vpce["s3"].dns_name, "*", "bucket") - execute_api_vpce_id = module.vpce["execute-api"].id -} module "alb" { count = local.deploy_alb ? 1 : 0 @@ -85,7 +70,9 @@ module "alb" { vpc_id = var.vpc_id subnet_ids = var.alb_subnet_ids target_security_group_ids = [aws_security_group.vpce_sg.id] - + s3_bucket_name = module.s3.bucket_name + s3_kms_key_arn = module.s3.kms_key_arn + s3_endpoint_url = var.domain_name != null ? var.domain_name : replace(module.vpce["s3"].dns_name, "*", "bucket") s3_vpce_id = module.vpce["s3"].id s3_vpce_nr_ips = length(var.private_subnet_ids) execute_api_vpce_id = try(module.vpce["execute-api"].id, null) diff --git a/terraform/modules/alb/data.tf b/terraform/modules/alb/data.tf index 4ddd250..7114a14 100644 --- a/terraform/modules/alb/data.tf +++ b/terraform/modules/alb/data.tf @@ -5,6 +5,8 @@ data "aws_vpc" "vpc" { id = var.vpc_id } +data "aws_region" "current" {} + # Certificate data "aws_acm_certificate" "cert" { domain = var.domain_name diff --git a/terraform/modules/api/dependencies/lambda_get_url/app.py b/terraform/modules/alb/dependencies/lambda_get_url/app.py similarity index 100% rename from terraform/modules/api/dependencies/lambda_get_url/app.py rename to terraform/modules/alb/dependencies/lambda_get_url/app.py diff --git a/terraform/modules/api/lambda.tf b/terraform/modules/alb/lambda.tf similarity index 69% rename from terraform/modules/api/lambda.tf rename to terraform/modules/alb/lambda.tf index 7970d6c..99c21c1 100644 --- a/terraform/modules/api/lambda.tf +++ b/terraform/modules/alb/lambda.tf @@ -20,18 +20,21 @@ data "aws_iam_policy_document" "assume_role" { } resource "aws_iam_role" "get_url_role" { + count = var.attach_api ? 1 : 0 name = "GetS3VpceUrlRole" assume_role_policy = data.aws_iam_policy_document.assume_role.json } resource "aws_iam_role_policy_attachment" "lambda_basic_execution_role_attachment" { + count = var.attach_api ? 1 : 0 policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - role = aws_iam_role.get_url_role.id + role = aws_iam_role.get_url_role[0].id } resource "aws_iam_role_policy_attachment" "lambda_eni_management_role_attachment" { + count = var.attach_api ? 1 : 0 policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaENIManagementAccess" - role = aws_iam_role.get_url_role.id + role = aws_iam_role.get_url_role[0].id } data "aws_iam_policy_document" "allow_s3_access" { @@ -49,8 +52,9 @@ data "aws_iam_policy_document" "allow_s3_access" { } resource "aws_iam_role_policy" "s3_policy" { + count = var.attach_api ? 1 : 0 name = "GetS3VpceUrlRoleS3Policy" - role = aws_iam_role.get_url_role.id + role = aws_iam_role.get_url_role[0].id policy = data.aws_iam_policy_document.allow_s3_access.json } @@ -65,12 +69,14 @@ data "aws_iam_policy_document" "allow_kms_decrypt" { } resource "aws_iam_role_policy" "kms_policy" { + count = var.attach_api ? 1 : 0 name = "GetS3VpceUrlRoleKmsDecryptPolicy" - role = aws_iam_role.get_url_role.id + role = aws_iam_role.get_url_role[0].id policy = data.aws_iam_policy_document.allow_kms_decrypt.json } resource "aws_security_group" "lambda_sg" { + count = var.attach_api ? 1 : 0 name = "GetS3VpceLambdaSecurityGroup" description = "Allow TLS outbound traffic" vpc_id = var.vpc_id @@ -85,12 +91,13 @@ resource "aws_security_group" "lambda_sg" { } resource "aws_lambda_function" "get_url" { + count = var.attach_api ? 1 : 0 function_name = "GetS3VpceUrlLambdaFunction" filename = data.archive_file.lambda_zip.output_path source_code_hash = data.archive_file.lambda_zip.output_base64sha256 runtime = "python3.11" handler = "app.lambda_handler" - role = aws_iam_role.get_url_role.arn + role = aws_iam_role.get_url_role[0].arn memory_size = 128 timeout = 15 @@ -106,17 +113,28 @@ resource "aws_lambda_function" "get_url" { vpc_config { subnet_ids = var.subnet_ids - security_group_ids = [aws_security_group.lambda_sg.id] + security_group_ids = [aws_security_group.lambda_sg[0].id] } } -resource "aws_lambda_permission" "permission" { - statement_id = "AllowAPIGatewayInvoke" +resource "aws_lambda_permission" "with_lambda" { + count = var.attach_api ? 1 : 0 + statement_id = "AllowExecutionFromlambda" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.get_url.function_name - principal = "apigateway.amazonaws.com" + function_name = aws_lambda_function.get_url[0].function_name + principal = "elasticloadbalancing.amazonaws.com" + source_arn = aws_lb_target_group.lambda[0].arn +} + +resource "aws_lb_target_group" "lambda" { + count = var.attach_api ? 1 : 0 + name = "AlbLambdaTargetGroup" + target_type = "lambda" +} - # The /*/* portion grants access from any method on any resource - # within the API Gateway "REST API". - source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*" +resource "aws_lb_target_group_attachment" "lambda" { + count = var.attach_api ? 1 : 0 + target_group_arn = aws_lb_target_group.lambda[0].arn + target_id = aws_lambda_function.get_url[0].arn + depends_on = [aws_lambda_permission.with_lambda] } diff --git a/terraform/modules/alb/main.tf b/terraform/modules/alb/main.tf index d9c884f..d953e68 100644 --- a/terraform/modules/alb/main.tf +++ b/terraform/modules/alb/main.tf @@ -56,37 +56,6 @@ resource "aws_lb_target_group_attachment" "s3" { target_id = flatten(data.aws_network_interface.s3_vpce_enis[*].private_ips)[count.index] } -# Execute API -resource "aws_lb_target_group" "execute_api" { - count = var.attach_api ? 1 : 0 - name = "AlbExecuteApiTargetGroup" - target_type = "ip" - port = 443 - protocol = "HTTPS" - vpc_id = var.vpc_id - - health_check { - matcher = "200,403" - protocol = "HTTPS" - } -} - -data "aws_vpc_endpoint" "execute_api_vpce" { - count = var.attach_api ? 1 : 0 - id = var.execute_api_vpce_id -} - -data "aws_network_interface" "execute_api_vpce_enis" { - count = var.attach_api ? var.execute_api_vpce_nr_ips : 0 - id = flatten(data.aws_vpc_endpoint.execute_api_vpce[0].network_interface_ids)[count.index] -} - -resource "aws_lb_target_group_attachment" "execute_api" { - count = var.attach_api ? var.execute_api_vpce_nr_ips : 0 - target_group_arn = aws_lb_target_group.execute_api[0].arn - target_id = flatten(data.aws_network_interface.execute_api_vpce_enis[*].private_ips)[count.index] -} - # APPLICATION LOAD BALANCER resource "aws_lb" "alb" { name = "AlbS3Vpce" @@ -124,7 +93,7 @@ resource "aws_lb_listener_rule" "api_rule" { action { type = "forward" - target_group_arn = aws_lb_target_group.execute_api[0].arn + target_group_arn = aws_lb_target_group.lambda[0].arn } condition { diff --git a/terraform/modules/alb/variables.tf b/terraform/modules/alb/variables.tf index ed29b39..06e55e8 100644 --- a/terraform/modules/alb/variables.tf +++ b/terraform/modules/alb/variables.tf @@ -16,6 +16,21 @@ variable "target_security_group_ids" { description = "IDs of security groups which the ALB should be allowed to access on port 443" } +variable "s3_bucket_name" { + type = string + description = "Name of the S3 bucket" +} + +variable "s3_endpoint_url" { + type = string + description = "Endpoint URL to be used for the S3 bucket" +} + +variable "s3_kms_key_arn" { + type = string + description = "ARN of the KMS key that is used for encrypting S3 bucket contents" +} + variable "s3_vpce_id" { type = string description = "ID of the S3 VPC endpoint" diff --git a/terraform/modules/api/data.tf b/terraform/modules/api/data.tf deleted file mode 100644 index bd8e476..0000000 --- a/terraform/modules/api/data.tf +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -data "aws_region" "current" {} - -data "aws_vpc" "vpc" { - id = var.vpc_id -} - -# Certificate -data "aws_acm_certificate" "cert" { - domain = var.domain_name - statuses = ["ISSUED"] - most_recent = true -} diff --git a/terraform/modules/api/main.tf b/terraform/modules/api/main.tf deleted file mode 100644 index c99b582..0000000 --- a/terraform/modules/api/main.tf +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -data "aws_iam_policy_document" "allow_api_gw_via_vpce" { - statement { - effect = "Allow" - principals { - type = "*" - identifiers = ["*"] - } - actions = ["execute-api:Invoke"] - resources = ["*"] - } - statement { - effect = "Deny" - principals { - type = "*" - identifiers = ["*"] - } - actions = ["execute-api:Invoke"] - resources = ["*"] - condition { - test = "StringNotEquals" - variable = "aws:SourceVpce" - values = [var.execute_api_vpce_id] - } - } -} - -resource "aws_api_gateway_rest_api" "api" { - name = "S3PrivateVpceApi" - - disable_execute_api_endpoint = true - endpoint_configuration { - types = ["PRIVATE"] - vpc_endpoint_ids = [var.execute_api_vpce_id] - } - - policy = data.aws_iam_policy_document.allow_api_gw_via_vpce.json -} - -resource "aws_api_gateway_method_settings" "api" { - rest_api_id = aws_api_gateway_rest_api.api.id - stage_name = aws_api_gateway_stage.prod.stage_name - method_path = "*/*" - - settings { - metrics_enabled = true - logging_level = "ERROR" - } -} - -resource "aws_api_gateway_domain_name" "api" { - domain_name = var.domain_name - regional_certificate_arn = data.aws_acm_certificate.cert.arn - security_policy = "TLS_1_2" - - endpoint_configuration { - types = ["REGIONAL"] - } -} - -resource "aws_api_gateway_base_path_mapping" "api" { - api_id = aws_api_gateway_rest_api.api.id - stage_name = aws_api_gateway_stage.prod.stage_name - domain_name = aws_api_gateway_domain_name.api.domain_name -} - -resource "aws_api_gateway_deployment" "api_deployment" { - rest_api_id = aws_api_gateway_rest_api.api.id - - triggers = { - redeployment = filemd5("main.tf") - } - - lifecycle { - create_before_destroy = true - } - - depends_on = [aws_api_gateway_integration.lambda] - -} - -resource "aws_api_gateway_stage" "prod" { - #checkov:skip=CKV2_AWS_51: Do not use client certificate authentication in this example - deployment_id = aws_api_gateway_deployment.api_deployment.id - rest_api_id = aws_api_gateway_rest_api.api.id - stage_name = "prod" -} - -resource "aws_api_gateway_request_validator" "prod" { - name = "validator" - rest_api_id = aws_api_gateway_rest_api.api.id - validate_request_parameters = true -} - -resource "aws_api_gateway_resource" "api_path" { - rest_api_id = aws_api_gateway_rest_api.api.id - parent_id = aws_api_gateway_rest_api.api.root_resource_id - path_part = "api" -} - -resource "aws_api_gateway_resource" "api_get_url" { - rest_api_id = aws_api_gateway_rest_api.api.id - parent_id = aws_api_gateway_resource.api_path.id - path_part = "get_url" -} - -resource "aws_api_gateway_method" "api_get_url_method" { - rest_api_id = aws_api_gateway_rest_api.api.id - resource_id = aws_api_gateway_resource.api_get_url.id - http_method = "GET" - authorization = "AWS_IAM" - request_validator_id = aws_api_gateway_request_validator.prod.id - - request_parameters = { - "method.request.querystring.key" = true - } -} - -resource "aws_api_gateway_integration" "lambda" { - rest_api_id = aws_api_gateway_rest_api.api.id - resource_id = aws_api_gateway_method.api_get_url_method.resource_id - http_method = aws_api_gateway_method.api_get_url_method.http_method - - integration_http_method = "POST" - type = "AWS_PROXY" - uri = aws_lambda_function.get_url.invoke_arn -} diff --git a/terraform/modules/api/outputs.tf b/terraform/modules/api/outputs.tf deleted file mode 100644 index eb1a3d9..0000000 --- a/terraform/modules/api/outputs.tf +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -output "lambda_role_arn" { - value = aws_iam_role.get_url_role.arn -} diff --git a/terraform/modules/api/variables.tf b/terraform/modules/api/variables.tf deleted file mode 100644 index c847851..0000000 --- a/terraform/modules/api/variables.tf +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -variable "vpc_id" { - type = string - description = "ID of the VPC the API Gateway and Lambda function should be deployed into" -} - -variable "subnet_ids" { - type = list(string) - description = "List of subnet IDs the API Gateway should be deployed into" -} - - -variable "execute_api_vpce_id" { - type = string - description = "ID of the execute-api VPC endpoint that should be allowed access to the API Gateway" -} - -variable "s3_bucket_name" { - type = string - description = "Name of the S3 bucket" -} - -variable "s3_endpoint_url" { - type = string - description = "Endpoint URL to be used for the S3 bucket" -} - -variable "s3_kms_key_arn" { - type = string - description = "ARN of the KMS key that is used for encrypting S3 bucket contents" -} - -variable "domain_name" { - type = string - description = "Domain name that should be associated with the ALB" -} diff --git a/terraform/variables.tf b/terraform/variables.tf index ca9e56e..d6abf58 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -49,7 +49,7 @@ variable "domain_name" { variable "deploy_api" { type = bool default = true - description = "Deploys an API GW with a Lambda route that generates a pre-signed URL for an object in the bucket. Will be integrated in the ALB in /api/* routes." + description = "Deploys an API with a Lambda target that generates a pre-signed URL for an object in the bucket. Will be integrated in the ALB in /api/* routes." } variable "hosted_zone_id" {