hasher-matcher-actioner/terraform/api/main.tf (479 lines of code) (raw):

# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved data "aws_region" "current" {} data "aws_iam_policy_document" "lambda_assume_role" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["lambda.amazonaws.com"] } } } data "aws_caller_identity" "current" {} locals { account_id = data.aws_caller_identity.current.account_id } # HMA Root/Main API Lambda resource "aws_lambda_function" "api_root" { function_name = "${var.prefix}_api_root" package_type = "Image" role = aws_iam_role.api_root.arn image_uri = var.lambda_docker_info.uri image_config { command = [var.lambda_docker_info.commands.api_root] } timeout = 300 memory_size = 512 environment { variables = { DYNAMODB_TABLE = var.datastore.name HMA_CONFIG_TABLE = var.config_table.name BANKS_TABLE = var.banks_datastore.name COUNTS_TABLE_NAME = var.counts_datastore.name IMAGE_BUCKET_NAME = var.image_data_storage.bucket_name IMAGE_PREFIX = var.image_data_storage.image_prefix THREAT_EXCHANGE_DATA_BUCKET_NAME = var.threat_exchange_data.bucket_name THREAT_EXCHANGE_DATA_FOLDER = var.threat_exchange_data.data_folder INDEXES_BUCKET_NAME = var.index_data_storage.bucket_name THREAT_EXCHANGE_API_TOKEN_SECRET_NAME = var.te_api_token_secret.name MEASURE_PERFORMANCE = var.measure_performance ? "True" : "False" WRITEBACKS_QUEUE_URL = var.writebacks_queue.url SUBMISSIONS_QUEUE_URL = var.submissions_queue.url HASHES_QUEUE_URL = var.hashes_queue.url BANKS_MEDIA_BUCKET_NAME = var.banks_media_storage.bucket_name INDEXER_FUNCTION_NAME = var.indexer_function_name } } tags = merge( var.additional_tags, { Name = "RootAPIFunction" } ) } resource "aws_cloudwatch_log_group" "api_root" { name = "/aws/lambda/${aws_lambda_function.api_root.function_name}" retention_in_days = var.log_retention_in_days tags = merge( var.additional_tags, { Name = "RootAPILambdaLogGroup" } ) } resource "aws_iam_role" "api_root" { name_prefix = "${var.prefix}_api_root" assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json tags = merge( var.additional_tags, { Name = "RootAPILambdaRole" } ) } data "aws_iam_policy_document" "api_root" { statement { effect = "Allow" actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:PutItem", "dynamodb:UpdateItem"] resources = ["${var.datastore.arn}*"] } statement { effect = "Allow" actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:PutItem", "dynamodb:UpdateItem"] resources = ["${var.banks_datastore.arn}*"] } statement { effect = "Allow" actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan"] resources = ["${var.counts_datastore.arn}*"] } statement { effect = "Allow" actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:PutItem", "dynamodb:DeleteItem"] resources = [var.config_table.arn] } statement { effect = "Allow" actions = ["s3:GetObject", "s3:PutObject"] resources = [ "arn:aws:s3:::${var.image_data_storage.bucket_name}/${var.image_data_storage.image_prefix}*", "arn:aws:s3:::${var.index_data_storage.bucket_name}/${var.index_data_storage.index_folder_key}*", "arn:aws:s3:::${var.banks_media_storage.bucket_name}/*" ] } statement { effect = "Allow" actions = [ "s3:GetObject", ] resources = concat( [ "arn:aws:s3:::${var.threat_exchange_data.bucket_name}/${var.threat_exchange_data.data_folder}*", ], [for partner_bucket in var.partner_image_buckets : "${partner_bucket.arn}/*"] ) } statement { effect = "Allow" actions = [ "s3:ListBucket" ] resources = [ "arn:aws:s3:::${var.threat_exchange_data.bucket_name}", ] } statement { effect = "Allow" actions = [ "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams" ] resources = ["${aws_cloudwatch_log_group.api_root.arn}:*"] } statement { effect = "Allow" actions = ["cloudwatch:GetMetricStatistics"] resources = ["*"] } statement { effect = "Allow" actions = ["secretsmanager:GetSecretValue", "secretsmanager:UpdateSecret"] resources = [var.te_api_token_secret.arn] } statement { effect = "Allow" actions = ["sqs:SendMessage"] resources = [var.writebacks_queue.arn, var.submissions_queue.arn, var.hashes_queue.arn] } statement { effect = "Allow" actions = ["lambda:GetFunctionConfiguration"] resources = [aws_lambda_function.api_root.arn] } statement { effect = "Allow" actions = ["lambda:InvokeAsync", "lambda:InvokeFunction"] resources = [var.indexer_function_arn] } } resource "aws_iam_policy" "api_root" { name_prefix = "${var.prefix}_api_root_role_policy" description = "Permissions for Root API Lambda" policy = data.aws_iam_policy_document.api_root.json } resource "aws_iam_role_policy_attachment" "api_root" { role = aws_iam_role.api_root.name policy_arn = aws_iam_policy.api_root.arn } # Authorizer API Lambda locals { user_pool_url = "https://cognito-idp.${data.aws_region.current.name}.amazonaws.com/${var.api_and_webapp_user_pool_id}" } resource "aws_lambda_function" "api_auth" { function_name = "${var.prefix}_api_auth" package_type = "Image" role = aws_iam_role.api_auth.arn image_uri = var.lambda_docker_info.uri image_config { command = [var.lambda_docker_info.commands.api_auth] } timeout = 30 memory_size = 128 environment { variables = { HMA_ACCESS_TOKEN_SECRET_NAME = var.hma_api_access_tokens_secret.name USER_POOL_URL = local.user_pool_url CLIENT_ID = var.api_authorizer_audience } } tags = merge( var.additional_tags, { Name = "AuthAPIFunction" } ) } resource "aws_cloudwatch_log_group" "api_auth" { name = "/aws/lambda/${aws_lambda_function.api_auth.function_name}" retention_in_days = var.log_retention_in_days tags = merge( var.additional_tags, { Name = "AuthAPILambdaLogGroup" } ) } resource "aws_iam_role" "api_auth" { name_prefix = "${var.prefix}_api_auth" assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json tags = merge( var.additional_tags, { Name = "AuthAPILambdaRole" } ) } data "aws_iam_policy_document" "api_auth" { statement { effect = "Allow" actions = [ "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams" ] resources = ["${aws_cloudwatch_log_group.api_auth.arn}:*"] } statement { effect = "Allow" actions = ["secretsmanager:GetSecretValue"] resources = [var.hma_api_access_tokens_secret.arn] } } resource "aws_iam_policy" "api_auth" { name_prefix = "${var.prefix}_api_auth_role_policy" description = "Permissions for Auth API Lambda" policy = data.aws_iam_policy_document.api_auth.json } resource "aws_iam_role_policy_attachment" "api_auth" { role = aws_iam_role.api_auth.name policy_arn = aws_iam_policy.api_auth.arn } # API Gateway data "aws_iam_policy_document" "apigateway_assume_role" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["apigateway.amazonaws.com"] } } } resource "aws_api_gateway_rest_api" "hma_api_gw" { name = "${var.prefix}_hma_api_gw" endpoint_configuration { types = var.api_in_vpc ? ["PRIVATE"] : ["REGIONAL"] vpc_endpoint_ids = length(aws_vpc_endpoint.vpce) > 0 ? aws_vpc_endpoint.vpce[*].id : null } policy = length(data.aws_iam_policy_document.hma_api_gw_in_vpc) > 0 ? data.aws_iam_policy_document.hma_api_gw_in_vpc[0].json : null binary_media_types = ["*/*"] } resource "aws_api_gateway_resource" "hma_api_gw" { parent_id = aws_api_gateway_rest_api.hma_api_gw.root_resource_id path_part = "{proxy+}" rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id } resource "aws_api_gateway_method" "hma_api_gw" { authorization = "CUSTOM" http_method = "ANY" authorizer_id = aws_api_gateway_authorizer.hma_api_gw.id resource_id = aws_api_gateway_resource.hma_api_gw.id rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id } resource "aws_api_gateway_authorizer" "hma_api_gw" { rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id type = "REQUEST" authorizer_credentials = aws_iam_role.hma_api_gw.arn authorizer_uri = aws_lambda_function.api_auth.invoke_arn name = "${aws_api_gateway_rest_api.hma_api_gw.name}_authorizer" identity_source = "method.request.header.Authorization" } resource "aws_api_gateway_integration" "hma_api_gw" { http_method = aws_api_gateway_method.hma_api_gw.http_method resource_id = aws_api_gateway_resource.hma_api_gw.id rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.api_root.invoke_arn } resource "aws_api_gateway_deployment" "hma_api_gw" { depends_on = [ aws_vpc_endpoint.vpce ] rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id triggers = { redeployment = sha1(jsonencode([ var.lambda_docker_info.uri, aws_api_gateway_resource.hma_api_gw.id, aws_api_gateway_method.hma_api_gw.id, aws_api_gateway_integration.hma_api_gw.id, aws_lambda_function.api_auth.id, aws_lambda_function.api_root.id, "${sha1(file("${path.module}/main.tf"))}" ])) } lifecycle { create_before_destroy = true } } resource "aws_api_gateway_stage" "hma_api_gw" { deployment_id = aws_api_gateway_deployment.hma_api_gw.id rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id stage_name = "api" } resource "aws_iam_role" "hma_api_gw" { name_prefix = "${var.prefix}_hma_api_gw" assume_role_policy = data.aws_iam_policy_document.apigateway_assume_role.json tags = merge( var.additional_tags, { Name = "HMAAPIGatewayRole" } ) } data "aws_iam_policy_document" "hma_api_gw" { statement { effect = "Allow" actions = [ "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams" ] resources = ["${aws_cloudwatch_log_group.hma_api_gw.arn}:*"] } statement { effect = "Allow" actions = ["lambda:InvokeFunction", ] resources = ["${aws_lambda_function.api_root.arn}:*", aws_lambda_function.api_auth.arn] } } resource "aws_iam_policy" "hma_api_gw" { name_prefix = "${var.prefix}_hma_api_gw_role_policy" description = "Permissions for HMA Rest API Gateway" policy = data.aws_iam_policy_document.hma_api_gw.json } resource "aws_iam_role_policy_attachment" "hma_api_gw" { role = aws_iam_role.hma_api_gw.name policy_arn = aws_iam_policy.hma_api_gw.arn } resource "aws_cloudwatch_log_group" "hma_api_gw" { name = "/aws/apigateway/${aws_api_gateway_rest_api.hma_api_gw.name}" retention_in_days = var.log_retention_in_days tags = merge( var.additional_tags, { Name = "HMAAPIGatewayLogGroup" } ) } resource "aws_lambda_permission" "apigw_lambda" { statement_id = "AllowExecutionFromAPIGateway" action = "lambda:InvokeFunction" function_name = aws_lambda_function.api_root.function_name principal = "apigateway.amazonaws.com" # More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html source_arn = "arn:aws:execute-api:${data.aws_region.current.name}:${local.account_id}:${aws_api_gateway_rest_api.hma_api_gw.id}/*/${aws_api_gateway_method.hma_api_gw.http_method}${aws_api_gateway_resource.hma_api_gw.path}" } resource "aws_api_gateway_method" "cors" { rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id resource_id = aws_api_gateway_resource.hma_api_gw.id http_method = "OPTIONS" authorization = "NONE" } resource "aws_api_gateway_integration" "cors" { rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id resource_id = aws_api_gateway_resource.hma_api_gw.id http_method = aws_api_gateway_method.cors.http_method type = "MOCK" request_templates = { "application/json" : "{\"statusCode\": 200}" } content_handling = "CONVERT_TO_TEXT" } resource "aws_api_gateway_method_response" "cors" { depends_on = [aws_api_gateway_method.cors] rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id resource_id = aws_api_gateway_resource.hma_api_gw.id http_method = aws_api_gateway_method.cors.http_method status_code = "200" response_parameters = { "method.response.header.Access-Control-Allow-Origin" = true, "method.response.header.Access-Control-Allow-Methods" = true, "method.response.header.Access-Control-Allow-Headers" = true } response_models = { "application/json" = "Empty" } } resource "aws_api_gateway_integration_response" "cors" { depends_on = [aws_api_gateway_integration.cors, aws_api_gateway_method_response.cors] rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id resource_id = aws_api_gateway_resource.hma_api_gw.id http_method = aws_api_gateway_method.cors.http_method status_code = "200" response_parameters = { "method.response.header.Access-Control-Allow-Origin" = "'*'", "method.response.header.Access-Control-Allow-Headers" = "'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token, Authorization'", "method.response.header.Access-Control-Allow-Methods" = "'GET, POST, PUT, DELETE, OPTIONS'", # remove or add methods as needed } } # VPC additions data "aws_vpc_endpoint_service" "vpc_service" { service = "execute-api" } resource "aws_vpc_endpoint" "vpce" { count = var.api_in_vpc ? 1 : 0 vpc_id = var.vpc_id service_name = data.aws_vpc_endpoint_service.vpc_service.service_name vpc_endpoint_type = "Interface" private_dns_enabled = true subnet_ids = var.vpc_subnets security_group_ids = var.security_groups } resource "aws_api_gateway_rest_api_policy" "hma_api_gw" { count = var.api_in_vpc ? 1 : 0 rest_api_id = aws_api_gateway_rest_api.hma_api_gw.id policy = data.aws_iam_policy_document.hma_api_gw_in_vpc[0].json } data "aws_iam_policy_document" "hma_api_gw_in_vpc" { count = var.api_in_vpc ? 1 : 0 statement { effect = "Allow" actions = ["execute-api:Invoke"] resources = ["*"] condition { test = "StringEquals" variable = "aws:SourceVpce" values = [aws_vpc_endpoint.vpce[count.index].id] } principals { type = "*" identifiers = ["*"] } } statement { effect = "Deny" actions = ["execute-api:Invoke"] resources = ["*"] condition { test = "StringNotEquals" variable = "aws:SourceVpce" values = [aws_vpc_endpoint.vpce[count.index].id] } principals { type = "*" identifiers = ["*"] } } } # Connect partner s3 buckets to api_root resource "aws_lambda_permission" "allow_bucket" { count = var.enable_partner_upload_notification ? length(var.partner_image_buckets) : 0 statement_id = "AllowExecutionFromS3Bucket" action = "lambda:InvokeFunction" function_name = aws_lambda_function.api_root.arn principal = "s3.amazonaws.com" source_arn = var.partner_image_buckets[count.index].arn } resource "aws_s3_bucket_notification" "bucket_notification" { count = var.enable_partner_upload_notification ? length(var.partner_image_buckets) : 0 bucket = var.partner_image_buckets[count.index].name lambda_function { lambda_function_arn = aws_lambda_function.api_root.arn events = ["s3:ObjectCreated:*"] # Check if a prefix filter (or the aliases folder, path) was specified # Otherwise no prefix constraint filter_prefix = lookup(var.partner_image_buckets[count.index].params, "prefix", lookup(var.partner_image_buckets[count.index].params, "folder", lookup(var.partner_image_buckets[count.index].params, "path", "") ) ) # Check if a suffix filter (or the alias extension) was specified # Otherwise no suffix constraint filter_suffix = lookup(var.partner_image_buckets[count.index].params, "suffix", lookup(var.partner_image_buckets[count.index].params, "extension", "") ) } depends_on = [aws_lambda_permission.allow_bucket] }