diff --git a/infrastructure/api_deployment.tf b/infrastructure/api_deployment.tf deleted file mode 100644 index b58d114..0000000 --- a/infrastructure/api_deployment.tf +++ /dev/null @@ -1,65 +0,0 @@ -resource "aws_api_gateway_deployment" "deployment" { - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - # triggers = { - # spec = module.apigw.redeployment_spec - # workspace = terraform.workspace - # } - lifecycle { - create_before_destroy = true - } -} - -resource "aws_api_gateway_stage" "stage" { - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - deployment_id = aws_api_gateway_deployment.deployment.id - stage_name = "v1" - xray_tracing_enabled = true - - tags = { - Environment = terraform.workspace - } - depends_on = [aws_api_gateway_deployment.deployment] - lifecycle { - create_before_destroy = true - } -} - -resource "aws_api_gateway_method_settings" "settings" { - rest_api_id = aws_api_gateway_stage.stage.rest_api_id - stage_name = aws_api_gateway_stage.stage.stage_name - method_path = "*/*" - - settings { - data_trace_enabled = true - logging_level = "INFO" - metrics_enabled = true - } -} - -# resource "aws_api_gateway_base_path_mapping" "main" { -# api_id = aws_api_gateway_stage.stage.rest_api_id -# stage_name = aws_api_gateway_stage.stage.stage_name -# domain_name = local.api_domain -# base_path = aws_api_gateway_stage.stage.stage_name -# depends_on = [aws_api_gateway_stage.stage] -# lifecycle { -# create_before_destroy = true -# } -# } - -resource "aws_cloudwatch_metric_alarm" "api-5XX" { - count = var.enable_cw_alarms ? 1 : 0 - alarm_name = "${terraform.workspace}-API-5XXErrors" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = 2 - threshold = 1 - period = 60 - unit = "Count" - - namespace = "AWS/ApiGateway" - metric_name = "5XXError" - statistic = "Maximum" - dimensions = { - ApiName = terraform.workspace - } -} \ No newline at end of file diff --git a/infrastructure/api_enquiry.tf b/infrastructure/api_enquiry.tf deleted file mode 100644 index 3b976be..0000000 --- a/infrastructure/api_enquiry.tf +++ /dev/null @@ -1,57 +0,0 @@ -resource "aws_api_gateway_resource" "resource" { - for_each = local.api_resources - parent_id = aws_api_gateway_resource.enquiry.id - path_part = each.key - rest_api_id = aws_api_gateway_rest_api.this_gateway.id -} - -resource "aws_api_gateway_method" "resource" { - for_each = local.api_resources - authorization = "NONE" - http_method = "ANY" - resource_id = aws_api_gateway_resource.resource[each.key].id - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - # authorizer_id = aws_api_gateway_authorizer.lambda_auth.id - # api_key_required = true -} - - -resource "aws_api_gateway_method_response" "response_200" { - for_each = local.api_resources - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.resource[each.key].http_method - status_code = "200" - response_parameters = { - "method.response.header.Access-Control-Allow-Methods" : true, - "method.response.header.Access-Control-Allow-Headers" : true, - "method.response.header.Access-Control-Allow-Origin" : true - } - response_models = { - "application/json" = "EmptySchema" - } -} - -resource "aws_api_gateway_integration" "resource" { - for_each = local.api_resources - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.resource[each.key].http_method - integration_http_method = "POST" - type = "AWS_PROXY" - timeout_milliseconds = 29000 - passthrough_behavior = "WHEN_NO_MATCH" - content_handling = "CONVERT_TO_TEXT" - uri = "arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:${var.AWS_ACCOUNT}:function:${module.enquiry_lambda.function_name}/invocations" -} - -resource "aws_api_gateway_integration_response" "resource" { - for_each = local.api_resources - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.resource[each.key].http_method - status_code = aws_api_gateway_method_response.response_200[each.key].status_code - depends_on = [ - aws_api_gateway_integration.resource - ] -} diff --git a/infrastructure/api_gateway.tf b/infrastructure/api_gateway.tf deleted file mode 100644 index 0db9125..0000000 --- a/infrastructure/api_gateway.tf +++ /dev/null @@ -1,43 +0,0 @@ -resource "aws_api_gateway_rest_api" "this_gateway" { - name = "enquiries-${terraform.workspace}" - binary_media_types = ["application/octet-stream"] - tags = local.tags -} - -resource "aws_api_gateway_gateway_response" "default_error" { - for_each = toset(["4XX", "5XX"]) - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - response_type = "DEFAULT_${each.value}" - - response_templates = { - "application/json" = "{\"message\":$context.error.messageString}" - } - - response_parameters = { - "gatewayresponse.header.Access-Control-Allow-Methods" : "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'", - "gatewayresponse.header.Access-Control-Allow-Origin" : "'*'", - "gatewayresponse.header.Access-Control-Allow-Headers" : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" - } -} - -resource "aws_api_gateway_resource" "enquiry" { - parent_id = aws_api_gateway_rest_api.this_gateway.root_resource_id - path_part = "enquiry" - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - depends_on = [aws_api_gateway_model.EmptySchema] -} - -resource "aws_api_gateway_model" "EmptySchema" { - rest_api_id = aws_api_gateway_rest_api.this_gateway.id - name = "EmptySchema" - description = "Empty Schema" - content_type = "application/json" - - schema = jsonencode( - { - title = "Empty Schema" - type = "object" - } - ) -} - diff --git a/infrastructure/cw_alarm.tf b/infrastructure/cw_alarm.tf index bb44d3e..80bac90 100644 --- a/infrastructure/cw_alarm.tf +++ b/infrastructure/cw_alarm.tf @@ -1,15 +1,15 @@ locals { - service_name = "${local.csi}-enquiry-evl-push-lambda" + service_name = "${local.csi}-${var.api_service_name}-evl-push-lambda" } -resource "aws_sqs_queue" "enquiry_evl_push_lambda" { +resource "aws_sqs_queue" "evl_push_lambda" { name = "${local.service_name}-dlq" message_retention_seconds = 1209600 tags = local.default_tags sqs_managed_sse_enabled = true } -resource "aws_cloudwatch_metric_alarm" "deadletter_alarm_enquiry_evl_push_lambda" { +resource "aws_cloudwatch_metric_alarm" "deadletter_alarm_evl_push_lambda" { alarm_name = "${local.service_name}-dlq-not-empty-alarm" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "1" @@ -18,15 +18,15 @@ resource "aws_cloudwatch_metric_alarm" "deadletter_alarm_enquiry_evl_push_lambda period = "120" statistic = "Sum" threshold = "1" - alarm_description = "Items are on the ${aws_sqs_queue.enquiry_evl_push_lambda.name} queue" + alarm_description = "Items are on the ${aws_sqs_queue.evl_push_lambda.name} queue" treat_missing_data = "notBreaching" tags = local.default_tags dimensions = { - "QueueName" = aws_sqs_queue.enquiry_evl_push_lambda.name + "QueueName" = aws_sqs_queue.evl_push_lambda.name } } -resource "aws_cloudwatch_metric_alarm" "enquiry_evl_push_lambda_errors" { +resource "aws_cloudwatch_metric_alarm" "evl_push_lambda_errors" { alarm_name = "${local.service_name}-errors-alarm" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 2 @@ -38,11 +38,11 @@ resource "aws_cloudwatch_metric_alarm" "enquiry_evl_push_lambda_errors" { metric_name = "Errors" statistic = "Maximum" dimensions = { - FunctionName = module.enquiry_sftp_file_push.function_name + FunctionName = module.sftp_file_push.function_name } } -resource "aws_cloudwatch_metric_alarm" "enquiry_evl_push_lambda_timeouts" { +resource "aws_cloudwatch_metric_alarm" "evl_push_lambda_timeouts" { alarm_name = "${local.service_name}-timeouts-alarm" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 2 @@ -55,6 +55,6 @@ resource "aws_cloudwatch_metric_alarm" "enquiry_evl_push_lambda_timeouts" { statistic = "Maximum" dimensions = { Environment = terraform.workspace - Service = "/aws/lambda/${module.enquiry_sftp_file_push.function_name}" + Service = "/aws/lambda/${module.sftp_file_push.function_name}" } } \ No newline at end of file diff --git a/infrastructure/data.tf b/infrastructure/data.tf index 6f09d37..fe6caf8 100644 --- a/infrastructure/data.tf +++ b/infrastructure/data.tf @@ -1,10 +1,7 @@ data "aws_api_gateway_rest_api" "remote_gateway" { - name = "cb2-11925" + name = terraform.workspace } -output "gateway" { - value = data.aws_api_gateway_rest_api.remote_gateway -} data "aws_caller_identity" "current" {} data "aws_region" "current" {} @@ -36,7 +33,7 @@ data "aws_iam_policy_document" "fh_cw_assume" { data "terraform_remote_state" "current_or_dev" { backend = "s3" - workspace = var.remote_state + workspace = terraform.workspace config = { bucket = "cvs-tf-environment" key = "tf_state" @@ -58,12 +55,12 @@ data "aws_iam_role" "firehose_metrics" { } ## Secrets Data -data "aws_secretsmanager_secret" "enquiry-api-key" { - name = "${var.GITHUB_ENVIRONMENT}/enquiry/api-key" +data "aws_secretsmanager_secret" "api-key" { + name = "${var.GITHUB_ENVIRONMENT}/${var.api_service_name}/api-key" } -data "aws_secretsmanager_secret_version" "enquiry-api-key" { - secret_id = data.aws_secretsmanager_secret.enquiry-api-key.id +data "aws_secretsmanager_secret_version" "api-key" { + secret_id = data.aws_secretsmanager_secret.api-key.id } @@ -71,6 +68,21 @@ data "aws_appconfig_environments" "app_config_environments" { application_id = var.app_config_ids["app_config_id"] } + +data "aws_api_gateway_authorizer" "lambda_auth" { + rest_api_id = data.aws_api_gateway_rest_api.remote_gateway.id + authorizer_id = data.terraform_remote_state.current_or_dev.outputs.lambda_authorizer_id +} # output "app_env" { # value = data.aws_appconfig_environments.app_config_environments -# } \ No newline at end of file +# } + +## S3 Access Logging +data "aws_kms_key" "access_logging_s3" { + key_id = "alias/s3-access-logging-${terraform.workspace}" +} + +data "aws_s3_bucket" "access_logging" { + #checkov:skip=CKV_AWS_144:This bucket does not require cross region replication. + bucket = "cvs-s3-access-logs-${terraform.workspace}" +} \ No newline at end of file diff --git a/infrastructure/data/iam_s3.json.tftpl b/infrastructure/data/iam_s3.json.tftpl index 8d779fb..e6a2802 100644 --- a/infrastructure/data/iam_s3.json.tftpl +++ b/infrastructure/data/iam_s3.json.tftpl @@ -2,7 +2,7 @@ "Version": "2012-10-17", "Statement": [ { - "Sid": "CVSS3Bucket${ title(action)}", + "Sid": "CVSS3Bucket${title(action)}", "Effect": "Allow", "Action": [ diff --git a/infrastructure/data/iam_secrets_manager.json.tftpl b/infrastructure/data/iam_secrets_manager.json.tftpl index ac4d1d9..25f4b0d 100644 --- a/infrastructure/data/iam_secrets_manager.json.tftpl +++ b/infrastructure/data/iam_secrets_manager.json.tftpl @@ -6,9 +6,9 @@ "Effect": "Allow", "Action": [ - "secretsmanager:ListSecrets", - "secretsmanager:DescribeSecret", - "secretsmanager:GetSecretValue" + "SecretsManager:ListSecrets", + "SecretsManager:DescribeSecret", + "SecretsManager:GetSecretValue" ], "Resource": ${ jsonencode(resource) } diff --git a/infrastructure/environments/develop.tfvars b/infrastructure/environments/develop.tfvars index 75c5e12..ffa5eb5 100644 --- a/infrastructure/environments/develop.tfvars +++ b/infrastructure/environments/develop.tfvars @@ -1 +1,11 @@ -aws_account = "006106226016" \ No newline at end of file +aws_environment = "nonprod" +sub_domain = "develop" +app_config_ids = { + app_config_id = "j7jocye", + vtx_profile_id = "t5s9wuc", + vtm_profile_id = "4j8oc9c", + vta_profile_id = "mlkqqmj", +} +app_config_environment_id = "42yaqu1" +api_version = "v1" +api_service_name = "enquiry" \ No newline at end of file diff --git a/infrastructure/environments/feature.tfvars b/infrastructure/environments/feature.tfvars index c18ca26..ffa5eb5 100644 --- a/infrastructure/environments/feature.tfvars +++ b/infrastructure/environments/feature.tfvars @@ -1,8 +1,11 @@ aws_environment = "nonprod" sub_domain = "develop" -app_config = { - app_config_id = "j7jocye", - vtx_profile_id = "t5s9wuc", - vtm_profile_id = "4j8oc9c", - vta_profile_id = "mlkqqmj", +app_config_ids = { + app_config_id = "j7jocye", + vtx_profile_id = "t5s9wuc", + vtm_profile_id = "4j8oc9c", + vta_profile_id = "mlkqqmj", } +app_config_environment_id = "42yaqu1" +api_version = "v1" +api_service_name = "enquiry" \ No newline at end of file diff --git a/infrastructure/eventbridge_schedule.tf b/infrastructure/eventbridge_schedule.tf index 4e78b00..19cc3f1 100644 --- a/infrastructure/eventbridge_schedule.tf +++ b/infrastructure/eventbridge_schedule.tf @@ -1,13 +1,13 @@ -resource "aws_cloudwatch_event_rule" "enquiry_lambda_trigger" { - for_each = toset(["evl", "tfl"]) +resource "aws_cloudwatch_event_rule" "lambda_trigger" { + for_each = local.scheduled_tasks name = "${terraform.workspace}-trigger-${each.value}-feed-every-day" description = "${var.schedule_day[each.value]} at ${var.schedule_hour[var.aws_environment]}00hrs" schedule_expression = "cron(0 ${var.schedule_hour[var.aws_environment]} ? * ${var.schedule_day[each.value]} *)" } -resource "aws_cloudwatch_event_target" "enquiry_lambda_trigger" { - for_each = toset(["evl", "tfl"]) - rule = aws_cloudwatch_event_rule.enquiry_lambda_trigger[each.value].name +resource "aws_cloudwatch_event_target" "lambda_trigger" { + for_each = local.scheduled_tasks + rule = aws_cloudwatch_event_rule.lambda_trigger[each.value].name arn = module.enquiry_lambda.arn input = templatefile("./data/enquiry_lambda_trigger.json.tftpl", { client = each.value }) } diff --git a/infrastructure/iam_policies.tf b/infrastructure/iam_policies.tf new file mode 100644 index 0000000..4afa363 --- /dev/null +++ b/infrastructure/iam_policies.tf @@ -0,0 +1,52 @@ +## S3 Policies +resource "aws_iam_policy" "s3" { + for_each = toset(["read", "write"]) + name = "${local.csi}-${each.value}-s3-${var.api_service_name}-document" + description = "${ title(each.value) } S3 ${title(var.api_service_name)} Document" + policy = templatefile("./data/iam_s3.json.tftpl", { action = each.value, arn = aws_s3_bucket.document_feed.arn }) +} + + +resource "aws_iam_role_policy_attachment" "read_bucket" { + role = module.sftp_file_push.role_name + policy_arn = aws_iam_policy.s3["read"].arn +} + +resource "aws_iam_role_policy_attachment" "document_feed" { + role = module.enquiry_lambda.role_name + policy_arn = aws_iam_policy.s3["write"].arn +} + +## Secrets Manager Policies +resource "aws_iam_policy" "secret_manager" { + for_each = { + "${var.api_service_name}" = ["rds-lambda-auth*"] + evl_push_file = ["sftp_poc/evl_config*", "sftp_poc/tfl_config*"] + } + name = "${local.csi}-${each.key}_secret_manager" + description = "Access Secret manager" + policy = templatefile("./data/iam_secrets_manager.json.tftpl", { resource = [ for arn in each.value : "arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:${var.environment == "feature" ? "nonprod" : var.environment}/${arn}" ] }) +} + +resource "aws_iam_role_policy_attachment" "evl_push_file_secret" { + role = module.sftp_file_push.role_name + policy_arn = aws_iam_policy.secret_manager["evl_push_file"].arn +} + +resource "aws_iam_role_policy_attachment" "get_secret" { + role = module.enquiry_lambda.role_name + policy_arn = aws_iam_policy.secret_manager[var.api_service_name].arn +} + +## SQS Policies +resource "aws_iam_policy" "send_evl_push_sqs_message" { + name = "${local.csi}-evl_file_push_send-sqs-message" + description = "${title(var.api_service_name)} EVL File Push Send SQS Message" + policy = templatefile("./data/iam_sqs_send.json.tftpl", { arn = aws_sqs_queue.evl_push_lambda.arn }) +} + +resource "aws_iam_role_policy_attachment" "allow_evl_push_lambda_send_message" { + role = module.sftp_file_push.role_name + policy_arn = aws_iam_policy.send_evl_push_sqs_message.arn +} + diff --git a/infrastructure/iam_policy_s3.tf b/infrastructure/iam_policy_s3.tf deleted file mode 100644 index 13cacc1..0000000 --- a/infrastructure/iam_policy_s3.tf +++ /dev/null @@ -1,17 +0,0 @@ -resource "aws_iam_policy" "s3_enquiry" { - for_each = toset(["read", "write"]) - name = "${local.csi}-${each.value}-s3-enquiry-document" - description = "${ title(each.value) } S3 Enquiry Document" - policy = templatefile("./data/iam_s3.json.tftpl", { action = each.value, arn = aws_s3_bucket.enquiry_document_feed.arn }) -} - - -resource "aws_iam_role_policy_attachment" "read_enquiry_bucket" { - role = module.enquiry_sftp_file_push.role_name - policy_arn = aws_iam_policy.s3_enquiry["read"].arn -} - -resource "aws_iam_role_policy_attachment" "enquiry_document_feed" { - role = module.enquiry_lambda.role_name - policy_arn = aws_iam_policy.s3_enquiry["write"].arn -} diff --git a/infrastructure/iam_policy_secrets_manager.tf b/infrastructure/iam_policy_secrets_manager.tf deleted file mode 100644 index a8ad997..0000000 --- a/infrastructure/iam_policy_secrets_manager.tf +++ /dev/null @@ -1,19 +0,0 @@ -resource "aws_iam_policy" "secret_manager" { - for_each = { - enquiry = ["rds-lambda-auth*"] - enquiry_evl_push_file = ["sftp_poc/evl_config*", "sftp_poc/tfl_config*"] - } - name = "${local.csi}-${each.key}_secret_manager" - description = "Access Secret manager" - policy = templatefile("./data/iam_secrets_manager.json.tftpl", { resource = [ for arn in each.value : "arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${arn}" ] }) -} - -resource "aws_iam_role_policy_attachment" "enquiry_evl_push_file_secret" { - role = module.enquiry_sftp_file_push.role_name - policy_arn = aws_iam_policy.secret_manager["enquiry_evl_push_file"].arn -} - -resource "aws_iam_role_policy_attachment" "get_enquiry_secret" { - role = module.enquiry_lambda.role_name - policy_arn = aws_iam_policy.secret_manager["enquiry"].arn -} diff --git a/infrastructure/iam_policy_sqs_send.tf b/infrastructure/iam_policy_sqs_send.tf deleted file mode 100644 index 968f7e3..0000000 --- a/infrastructure/iam_policy_sqs_send.tf +++ /dev/null @@ -1,11 +0,0 @@ -resource "aws_iam_policy" "send_enquiry_evl_push_sqs_message" { - name = "${local.csi}-enquiry_evl_file_push_send-sqs-message" - description = "Enquiry EVL File Push Send SQS Message" - policy = templatefile("./data/iam_sqs_send.json.tftpl", { arn = aws_sqs_queue.enquiry_evl_push_lambda.arn }) -} - -resource "aws_iam_role_policy_attachment" "allow_enquiry_evl_push_lambda_send_message" { - role = module.enquiry_sftp_file_push.role_name - policy_arn = aws_iam_policy.send_enquiry_evl_push_sqs_message.arn -} - diff --git a/infrastructure/locals.tf b/infrastructure/locals.tf index bbdba9d..cc2bd64 100644 --- a/infrastructure/locals.tf +++ b/infrastructure/locals.tf @@ -29,12 +29,12 @@ locals { tags = { Env = terraform.workspace Project = "cvs" - Service = "cvs-svc-enquiry-tf" + Service = "cvs-svc-${var.api_service_name}-tf" Managed_By = "terraform" } domain = "${var.sub_domain}.${var.domain}" - default_tags = var.default_tags + default_tags = local.tags cfs_buckets = ["csv", "documents"] @@ -58,4 +58,9 @@ locals { "testResults", "vehicle" ]) + + scheduled_tasks = toset([ + "evl", + "tfl" + ]) } \ No newline at end of file diff --git a/infrastructure/main.tf b/infrastructure/main.tf deleted file mode 100644 index 0587937..0000000 --- a/infrastructure/main.tf +++ /dev/null @@ -1,4 +0,0 @@ -# module "app_config" { -# source = "./modules/app-config" -# app_config_id = var.app_config.app_config_id -# } \ No newline at end of file diff --git a/infrastructure/module_api_gateway_enquiry.tf b/infrastructure/module_api_gateway_enquiry.tf new file mode 100644 index 0000000..6d3ebc1 --- /dev/null +++ b/infrastructure/module_api_gateway_enquiry.tf @@ -0,0 +1,11 @@ +module "api_gateway" { + source = "./modules/api-gateway" + parent_api = data.aws_api_gateway_rest_api.remote_gateway.id + service_version = "v1" + default_tags = local.tags + enable_cw_alarms = false + api_service_name = "enquiry" + api_resources = local.api_resources + authorizer_id = data.aws_api_gateway_authorizer.lambda_auth.id + lambda_uri = "arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:${var.AWS_ACCOUNT}:function:${module.enquiry_lambda.function_name}/invocations" +} \ No newline at end of file diff --git a/infrastructure/module_lambda_enquiry.tf b/infrastructure/module_lambda_enquiry.tf index 0662599..e500235 100644 --- a/infrastructure/module_lambda_enquiry.tf +++ b/infrastructure/module_lambda_enquiry.tf @@ -1,15 +1,15 @@ locals { enquiry_ver = terraform.workspace enquiry_service_map = { - name = "enquiry" + name = var.api_service_name version = local.enquiry_ver handler = "src/handler.handler", - description = "Enquiry", + description = "${title(var.api_service_name)} Service", component = "enq", memory = 1024, timeout = 60, } - enquiry_api_key = replace(data.aws_secretsmanager_secret_version.enquiry-api-key.secret_string, "BRANCH", terraform.workspace) + api_key = replace(data.aws_secretsmanager_secret_version.api-key.secret_string, "BRANCH", terraform.workspace) } @@ -19,12 +19,20 @@ module "enquiry_lambda" { service_map = local.enquiry_service_map service_name = local.enquiry_service_map.name - principal_services = ["apigateway", "events"] - invoker_source_arns = [ - data.aws_api_gateway_rest_api.remote_gateway.arn, - aws_cloudwatch_event_rule.enquiry_lambda_trigger["tfl"].arn, - aws_cloudwatch_event_rule.enquiry_lambda_trigger["evl"].arn - ] + lambda_triggers = merge( + { + for service in local.api_resources : service => { + "arn" = "${module.api_gateway.api_gateway_arn}/*/*/${service}" + "principal" = "apigateway.amazonaws.com" + } + }, + { + for task in local.scheduled_tasks : task => { + "arn" = aws_cloudwatch_event_rule.lambda_trigger[task].arn + "principal" = "events.amazonaws.com" + } + } + ) custom_policy_enabled = false project = var.project @@ -38,7 +46,7 @@ module "enquiry_lambda" { lambda_sgs = [data.terraform_remote_state.current_or_dev.outputs["lambda_sg"]] additional_env_vars = { - AWS_S3_BUCKET_NAME = "cvs-enquiry-document-feed-${terraform.workspace}" + AWS_S3_BUCKET_NAME = "cvs-${var.api_service_name}-document-feed-${terraform.workspace}" SECRET = "${var.aws_environment}/rds-lambda-auth-ro/config" SCHEMA_NAME = replace(upper("CVSNOP${terraform.workspace}"), "-", "") } @@ -48,15 +56,7 @@ module "enquiry_lambda" { app_config_environment_id = var.app_config_environment_id } -resource "aws_lambda_permission" "enquiry" { - statement_id = "AllowExecutionFromAPIGateway" - action = "lambda:InvokeFunction" - function_name = module.enquiry_lambda.function_name - principal = "apigateway.amazonaws.com" - source_arn = data.aws_api_gateway_rest_api.remote_gateway.arn -} - -resource "aws_cloudwatch_log_subscription_filter" "enquiry" { +resource "aws_cloudwatch_log_subscription_filter" "filter" { for_each = local.enable_firehose log_group_name = module.enquiry_lambda.log_group_name name = "${local.enquiry_service_map.name}-${terraform.workspace}-metrics" @@ -65,25 +65,3 @@ resource "aws_cloudwatch_log_subscription_filter" "enquiry" { filter_pattern = "" } -resource "aws_api_gateway_api_key" "enquiry-api" { - name = "enquiry-api-${terraform.workspace}" - value = local.enquiry_api_key - tags = { - Environment = terraform.workspace - } -} - -# resource "aws_api_gateway_usage_plan" "enquiry-up" { -# name = "enquiry-up-${terraform.workspace}" -# api_stages { -# api_id = data.aws_api_gateway_rest_api.remote_gateway.id -# stage = aws_api_gateway_stage.stage.stage_name -# } -# } - -# resource "aws_api_gateway_usage_plan_key" "enquiry-upk" { -# key_id = aws_api_gateway_api_key.enquiry-api.id -# key_type = "API_KEY" -# usage_plan_id = aws_api_gateway_usage_plan.enquiry-up.id -# } - diff --git a/infrastructure/module_lambda_enquiry_evl_push.tf b/infrastructure/module_lambda_enquiry_evl_push.tf index 5025cf1..1a59709 100644 --- a/infrastructure/module_lambda_enquiry_evl_push.tf +++ b/infrastructure/module_lambda_enquiry_evl_push.tf @@ -1,7 +1,7 @@ locals { enquiry_sftp_push = { - name = "enquiry-sftp-file-push" + name = "${var.api_service_name}-sftp-file-push" version = terraform.workspace handler = "handler/s3Event.handler", description = "Push S3 data feed to SFTP ${terraform.workspace}", @@ -13,16 +13,20 @@ locals { evl_config_prefix = var.environment } -module "enquiry_sftp_file_push" { +module "sftp_file_push" { source = "./modules/lambda-iam" - s3_prefix = "enquiry-evl-file-push" + s3_prefix = "${var.api_service_name}-evl-file-push" aws_account_id = data.aws_caller_identity.current.account_id service_map = local.enquiry_sftp_push service_name = local.enquiry_sftp_push.name custom_policy_enabled = false - principal_services = ["s3"] - invoker_source_arns = [aws_s3_bucket.enquiry_document_feed.arn] + lambda_triggers = { + bucket = { + arn = aws_s3_bucket.document_feed.arn + principal = "s3.amazonaws.com" + } + } project = var.project environment = terraform.workspace @@ -34,7 +38,7 @@ module "enquiry_sftp_file_push" { subnet_ids = local.subnet_ids lambda_sgs = local.lambda_sgs - dlq_arn = aws_sqs_queue.enquiry_evl_push_lambda.arn + dlq_arn = aws_sqs_queue.evl_push_lambda.arn additional_env_vars = var.sftp_vars diff --git a/infrastructure/modules/api-gateway/api-parent.tf b/infrastructure/modules/api-gateway/api-parent.tf new file mode 100644 index 0000000..f63a873 --- /dev/null +++ b/infrastructure/modules/api-gateway/api-parent.tf @@ -0,0 +1,70 @@ +# Root Path for the service on the parent Gateway (e.g. `/v1/enquiry`) +data "aws_api_gateway_resource" "version" { + rest_api_id = var.parent_api + path = "/${var.service_version}" +} + +resource "aws_api_gateway_resource" "root" { + rest_api_id = var.parent_api + parent_id = data.aws_api_gateway_resource.version.id + path_part = var.api_service_name +} + +# Service Resources on the parent Gateway (e.g. `/v1/enquiry/vehicle`) +resource "aws_api_gateway_resource" "parent" { + for_each = toset(var.api_resources) + rest_api_id = var.parent_api + parent_id = aws_api_gateway_resource.root.id + path_part = each.value +} + +resource "aws_api_gateway_method" "parent" { + for_each = toset(var.api_resources) + authorization = "CUSTOM" + http_method = "ANY" + resource_id = aws_api_gateway_resource.parent[each.key].id + rest_api_id = var.parent_api + authorizer_id = var.authorizer_id + api_key_required = true +} + +resource "aws_api_gateway_integration" "parent" { + for_each = toset(var.api_resources) + rest_api_id = var.parent_api + resource_id = aws_api_gateway_resource.parent[each.key].id + http_method = "ANY" + integration_http_method = "GET" + type = "HTTP" + timeout_milliseconds = 29000 + content_handling = "CONVERT_TO_TEXT" + uri = "${aws_api_gateway_stage.service.invoke_url}/${each.key}" +} + +# Parent API Gateaay Deployment +resource "aws_api_gateway_deployment" "parent" { + rest_api_id = var.parent_api + triggers = { + spec = sha1(jsonencode([aws_api_gateway_method.parent[*]])) + workspace = terraform.workspace + } + lifecycle { + create_before_destroy = true + } +} + +# Create or Update API Gateway Stage +resource "terraform_data" "create_stage" { + input = { + deployment = aws_api_gateway_deployment.parent.id + stage = terraform.workspace + } + + provisioner "local-exec" { + environment = { + STAGE = terraform.workspace + API = var.parent_api + DEPLOYMENT = aws_api_gateway_deployment.parent.id + } + command = "bash ${path.module}/scripts/create_gateway_stage.sh" + } +} \ No newline at end of file diff --git a/infrastructure/modules/api-gateway/api-service.tf b/infrastructure/modules/api-gateway/api-service.tf new file mode 100644 index 0000000..16cdab2 --- /dev/null +++ b/infrastructure/modules/api-gateway/api-service.tf @@ -0,0 +1,145 @@ +resource "aws_api_gateway_rest_api" "service" { + name = "${var.api_service_name}-${terraform.workspace}" + binary_media_types = ["application/octet-stream"] + tags = var.default_tags +} + +resource "aws_api_gateway_gateway_response" "default_error" { + for_each = toset(["4XX", "5XX"]) + rest_api_id = aws_api_gateway_rest_api.service.id + response_type = "DEFAULT_${each.value}" + + response_templates = { + "application/json" = "{\"message\":$context.error.messageString}" + } + + response_parameters = { + "gatewayresponse.header.Access-Control-Allow-Methods" : "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'", + "gatewayresponse.header.Access-Control-Allow-Origin" : "'*'", + "gatewayresponse.header.Access-Control-Allow-Headers" : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" + } +} + +resource "aws_api_gateway_model" "EmptySchema" { + rest_api_id = aws_api_gateway_rest_api.service.id + name = "EmptySchema" + description = "Empty Schema" + content_type = "application/json" + + schema = jsonencode( + { + title = "Empty Schema" + type = "object" + } + ) +} + +resource "aws_api_gateway_deployment" "service" { + rest_api_id = aws_api_gateway_rest_api.service.id + triggers = { + spec = sha1(jsonencode([aws_api_gateway_integration.service])) + workspace = terraform.workspace + } + lifecycle { + create_before_destroy = true + } +} + +resource "aws_api_gateway_stage" "service" { + rest_api_id = aws_api_gateway_rest_api.service.id + deployment_id = aws_api_gateway_deployment.service.id + stage_name = var.service_version + xray_tracing_enabled = true + + tags = { + Environment = terraform.workspace + } + lifecycle { + create_before_destroy = true + } +} + +resource "aws_api_gateway_method_settings" "settings" { + rest_api_id = aws_api_gateway_stage.service.rest_api_id + stage_name = aws_api_gateway_stage.service.stage_name + method_path = "*/*" + + settings { + data_trace_enabled = true + logging_level = "INFO" + metrics_enabled = true + } +} + +resource "aws_api_gateway_resource" "service" { + for_each = toset(var.api_resources) + parent_id = aws_api_gateway_rest_api.service.root_resource_id + path_part = each.key + rest_api_id = aws_api_gateway_rest_api.service.id +} + +resource "aws_api_gateway_method" "service" { + for_each = toset(var.api_resources) + authorization = "NONE" + http_method = "ANY" + resource_id = aws_api_gateway_resource.service[each.key].id + rest_api_id = aws_api_gateway_rest_api.service.id +} + + +resource "aws_api_gateway_method_response" "response_200" { + for_each = toset(var.api_resources) + rest_api_id = aws_api_gateway_rest_api.service.id + resource_id = aws_api_gateway_resource.service[each.key].id + http_method = aws_api_gateway_method.service[each.key].http_method + status_code = "200" + response_parameters = { + "method.response.header.Access-Control-Allow-Methods" : true, + "method.response.header.Access-Control-Allow-Headers" : true, + "method.response.header.Access-Control-Allow-Origin" : true + } + response_models = { + "application/json" = aws_api_gateway_model.EmptySchema.name + } +} + +resource "aws_api_gateway_integration" "service" { + for_each = toset(var.api_resources) + rest_api_id = aws_api_gateway_rest_api.service.id + resource_id = aws_api_gateway_resource.service[each.key].id + http_method = aws_api_gateway_method.service[each.key].http_method + integration_http_method = "POST" + type = "AWS_PROXY" + timeout_milliseconds = 29000 + passthrough_behavior = "WHEN_NO_MATCH" + content_handling = "CONVERT_TO_TEXT" + uri = var.lambda_uri +} + +resource "aws_api_gateway_integration_response" "service" { + for_each = toset(var.api_resources) + rest_api_id = aws_api_gateway_rest_api.service.id + resource_id = aws_api_gateway_resource.service[each.key].id + http_method = aws_api_gateway_method.service[each.key].http_method + status_code = aws_api_gateway_method_response.response_200[each.key].status_code + depends_on = [ + aws_api_gateway_integration.service + ] +} + +resource "aws_cloudwatch_metric_alarm" "api-5XX" { + count = var.enable_cw_alarms ? 1 : 0 + alarm_name = "${terraform.workspace}-API-5XXErrors" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 2 + threshold = 1 + period = 60 + unit = "Count" + + namespace = "AWS/ApiGateway" + metric_name = "5XXError" + statistic = "Maximum" + dimensions = { + ApiName = terraform.workspace + } +} \ No newline at end of file diff --git a/infrastructure/modules/api-gateway/outputs.tf b/infrastructure/modules/api-gateway/outputs.tf new file mode 100644 index 0000000..db9ad41 --- /dev/null +++ b/infrastructure/modules/api-gateway/outputs.tf @@ -0,0 +1,15 @@ +output "api_gateway_id" { + value = aws_api_gateway_rest_api.service.id +} + +output "api_gateway_arn" { + value = aws_api_gateway_rest_api.service.arn +} + +output "api_stage" { + value = terraform_data.create_stage.output.stage +} + +output "api_deployment" { + value = terraform_data.create_stage.output.deployment +} \ No newline at end of file diff --git a/infrastructure/modules/api-gateway/scripts/create_gateway_stage.sh b/infrastructure/modules/api-gateway/scripts/create_gateway_stage.sh new file mode 100644 index 0000000..d152066 --- /dev/null +++ b/infrastructure/modules/api-gateway/scripts/create_gateway_stage.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +if [[ "$(aws apigateway get-stages --rest-api ${API} --query "item[?stageName=='${STAGE}']")" == "[]" ]]; then + aws apigateway create-stage --rest-api-id ${API} --stage-name ${STAGE} --deployment-id ${DEPLOYMENT} +else + aws apigateway update-stage --rest-api-id ${API} --stage-name ${STAGE} --patch-operations "op=replace,path=/deploymentId,value=${DEPLOYMENT}" +fi \ No newline at end of file diff --git a/infrastructure/modules/api-gateway/variables.tf b/infrastructure/modules/api-gateway/variables.tf new file mode 100644 index 0000000..b4280cd --- /dev/null +++ b/infrastructure/modules/api-gateway/variables.tf @@ -0,0 +1,40 @@ +variable "parent_api" { + type = string + description = "API ID for the Parent Gateway" +} + +variable "service_version" { + type = string + description = "API Version for the Service" + default = null +} + +variable "default_tags" { + type = map(string) + description = "Tags to apply to resources" +} + +variable "enable_cw_alarms" { + type = bool + description = "Should CloudWatch Alarms be enabled?" +} + +variable "api_service_name" { + type = string + description = "The name of the API Service" +} + +variable "api_resources" { + type = list(string) + description = "List of API Resources to create" +} + +variable "authorizer_id" { + type = string + description = "Environment specific Lambda Authorizer ID" +} + +variable "lambda_uri" { + type = string + description = "The URI to access the Lambda Function" +} \ No newline at end of file diff --git a/infrastructure/modules/app-config/locals.tf b/infrastructure/modules/app-config/locals.tf deleted file mode 100644 index ba9b0bf..0000000 --- a/infrastructure/modules/app-config/locals.tf +++ /dev/null @@ -1,9 +0,0 @@ -locals { - tags = { - Component = "cvs-app-config" - Project = "cvs" - Name = "cvs-${terraform.workspace}/app-config" - Environment = terraform.workspace - Module = "cvs-tf-service" - } -} diff --git a/infrastructure/modules/app-config/main.tf b/infrastructure/modules/app-config/main.tf deleted file mode 100644 index 3d665ab..0000000 --- a/infrastructure/modules/app-config/main.tf +++ /dev/null @@ -1,12 +0,0 @@ -resource "aws_appconfig_environment" "app_config_environment" { - name = terraform.workspace - description = "Contains all the feature flags for ${terraform.workspace}" - application_id = var.app_config_id - - # monitor { - # alarm_arn = aws_cloudwatch_metric_alarm.example.arn - # alarm_role_arn = aws_iam_role.example.arn - # } - - tags = local.tags -} diff --git a/infrastructure/modules/app-config/output.tf b/infrastructure/modules/app-config/output.tf deleted file mode 100644 index ad42598..0000000 --- a/infrastructure/modules/app-config/output.tf +++ /dev/null @@ -1,3 +0,0 @@ -output "app_config_environment_id" { - value = aws_appconfig_environment.app_config_environment.environment_id -} \ No newline at end of file diff --git a/infrastructure/modules/app-config/variables.tf b/infrastructure/modules/app-config/variables.tf deleted file mode 100644 index 62d7c17..0000000 --- a/infrastructure/modules/app-config/variables.tf +++ /dev/null @@ -1,5 +0,0 @@ -variable "app_config_id" { - type = string - description = "The app config ID to add this environment to" - default = "" -} diff --git a/infrastructure/modules/lambda-iam/lambda_permissions.tf b/infrastructure/modules/lambda-iam/lambda_permissions.tf index 69c073d..bb71302 100644 --- a/infrastructure/modules/lambda-iam/lambda_permissions.tf +++ b/infrastructure/modules/lambda-iam/lambda_permissions.tf @@ -1,8 +1,8 @@ resource "aws_lambda_permission" "allow_invoke" { - count = length(var.principal_services) - statement_id = "Allow${var.principal_services[count.index]}InvokeLambdaFunction${count.index > 0 ? count.index : ""}" + for_each = var.lambda_triggers + statement_id = "AllowInvokeLambdaFunction${each.key}" function_name = aws_lambda_function.service.function_name action = "lambda:InvokeFunction" - principal = "${var.principal_services[count.index]}.amazonaws.com" - source_arn = var.invoker_source_arns[count.index] + principal = each.value["principal"] + source_arn = each.value["arn"] } \ No newline at end of file diff --git a/infrastructure/modules/lambda-iam/variables.tf b/infrastructure/modules/lambda-iam/variables.tf index bddfa8a..570ff6e 100644 --- a/infrastructure/modules/lambda-iam/variables.tf +++ b/infrastructure/modules/lambda-iam/variables.tf @@ -102,16 +102,9 @@ variable "service_name" { } -variable "principal_services" { - type = list(string) - description = "A list of service names allowed to invoke lambda. Accepted values: apigateway, events" - default = [""] -} - -variable "invoker_source_arns" { - type = list(string) - description = "A list of arn of the Principal Service" - default = [""] +variable "lambda_triggers" { + type = map(map(string)) + description = "A collection of Lambda Triggers" } variable "dlq_arn" { diff --git a/infrastructure/s3_access_logging.tf b/infrastructure/s3_access_logging.tf deleted file mode 100644 index 099b29e..0000000 --- a/infrastructure/s3_access_logging.tf +++ /dev/null @@ -1,8 +0,0 @@ -data "aws_kms_key" "access_logging_s3" { - key_id = "alias/s3-access-logging-${terraform.workspace}" -} - -data "aws_s3_bucket" "access_logging" { - #checkov:skip=CKV_AWS_144:This bucket does not require cross region replication. - bucket = "cvs-s3-access-logs-${terraform.workspace}" -} \ No newline at end of file diff --git a/infrastructure/s3_evl_data_feed.tf b/infrastructure/s3_evl_data_feed.tf index 561946f..68e1f04 100644 --- a/infrastructure/s3_evl_data_feed.tf +++ b/infrastructure/s3_evl_data_feed.tf @@ -1,41 +1,43 @@ -resource "aws_s3_bucket" "enquiry_document_feed" { - bucket = "cvs-enquiry-document-feed-${terraform.workspace}" + +## Document Feed +resource "aws_s3_bucket" "document_feed" { + bucket = "cvs-${var.api_service_name}-document-feed-${terraform.workspace}" force_destroy = var.force_destroy tags = { - Name = "cvs-enquiry-document-feed-${terraform.workspace}" + Name = "cvs-${var.api_service_name}-document-feed-${terraform.workspace}" Environment = terraform.workspace } } -resource "aws_s3_bucket_logging" "enquiry_document_feed" { - bucket = aws_s3_bucket.enquiry_document_feed.id +resource "aws_s3_bucket_logging" "document_feed" { + bucket = aws_s3_bucket.document_feed.id target_bucket = data.aws_s3_bucket.access_logging.id - target_prefix = "${aws_s3_bucket.enquiry_document_feed.bucket}/" + target_prefix = "${aws_s3_bucket.document_feed.bucket}/" } -resource "aws_s3_bucket_versioning" "enquiry_document_feed" { - bucket = aws_s3_bucket.enquiry_document_feed.id +resource "aws_s3_bucket_versioning" "document_feed" { + bucket = aws_s3_bucket.document_feed.id versioning_configuration { status = "Suspended" } } -resource "aws_s3_bucket_ownership_controls" "enable_acl_enquiry_document_feed" { - bucket = aws_s3_bucket.enquiry_document_feed.id +resource "aws_s3_bucket_ownership_controls" "enable_acl_document_feed" { + bucket = aws_s3_bucket.document_feed.id rule { object_ownership = "ObjectWriter" } } -resource "aws_s3_bucket_acl" "enquiry_document_feed" { - depends_on = [aws_s3_bucket_ownership_controls.enable_acl_enquiry_document_feed] - bucket = aws_s3_bucket.enquiry_document_feed.id +resource "aws_s3_bucket_acl" "document_feed" { + depends_on = [aws_s3_bucket_ownership_controls.enable_acl_document_feed] + bucket = aws_s3_bucket.document_feed.id acl = "private" } -resource "aws_s3_bucket_public_access_block" "enquiry_document_feed" { +resource "aws_s3_bucket_public_access_block" "document_feed" { bucket = data.aws_s3_bucket.access_logging.id block_public_acls = true block_public_policy = true @@ -43,8 +45,8 @@ resource "aws_s3_bucket_public_access_block" "enquiry_document_feed" { restrict_public_buckets = true } -# resource "aws_s3_bucket_server_side_encryption_configuration" "enquiry_document_feed" { -# bucket = aws_s3_bucket.enquiry_document_feed.bucket +# resource "aws_s3_bucket_server_side_encryption_configuration" "document_feed" { +# bucket = aws_s3_bucket.document_feed.bucket # # rule { # apply_server_side_encryption_by_default { @@ -53,12 +55,12 @@ resource "aws_s3_bucket_public_access_block" "enquiry_document_feed" { # } # } -resource "aws_s3_bucket_policy" "enquiry_document_feed" { - bucket = aws_s3_bucket.enquiry_document_feed.id - policy = data.aws_iam_policy_document.enquiry_document_feed_https.json +resource "aws_s3_bucket_policy" "document_feed" { + bucket = aws_s3_bucket.document_feed.id + policy = data.aws_iam_policy_document.document_feed_https.json } -data "aws_iam_policy_document" "enquiry_document_feed_https" { +data "aws_iam_policy_document" "document_feed_https" { statement { effect = "Deny" principals { @@ -70,8 +72,8 @@ data "aws_iam_policy_document" "enquiry_document_feed_https" { ] resources = [ - "${aws_s3_bucket.enquiry_document_feed.arn}/*", - aws_s3_bucket.enquiry_document_feed.arn + "${aws_s3_bucket.document_feed.arn}/*", + aws_s3_bucket.document_feed.arn ] condition { @@ -83,10 +85,10 @@ data "aws_iam_policy_document" "enquiry_document_feed_https" { } resource "aws_s3_bucket_notification" "feed_bucket_notification" { - bucket = aws_s3_bucket.enquiry_document_feed.id + bucket = aws_s3_bucket.document_feed.id lambda_function { - lambda_function_arn = module.enquiry_sftp_file_push.arn + lambda_function_arn = module.sftp_file_push.arn events = ["s3:ObjectCreated:*"] filter_prefix = "EVL_GVT_" filter_suffix = ".csv" diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index 686c023..4adca76 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -1,4 +1,4 @@ -## Environment Variables +## Environment Variables expected as TV_VAR_XXX values from pipeline variable "AWS_ACCOUNT" { type = string description = "The AWS Account ID to deploy to" @@ -73,8 +73,6 @@ variable "app_config" { } } - - variable "schedule_day" { type = map(string) description = "Days on which to run Lambda on Schedule" @@ -166,4 +164,14 @@ variable "include_option" { variable "include_verb" { type = bool default = true +} + +variable "api_version" { + type = string + description = "DVSA API Version to deploy Service Into" +} + +variable "api_service_name" { + type = string + description = "Name of the API Service (e.g. `defects`, `enquiry`)" } \ No newline at end of file