diff --git a/infra/csoc_eventforwarder_role.tf b/infra/csoc_eventforwarder_role.tf new file mode 100644 index 000000000..b1252e98f --- /dev/null +++ b/infra/csoc_eventforwarder_role.tf @@ -0,0 +1,34 @@ +resource "aws_iam_role" "eventbridge_forwarder_role" { + name = "imms-${var.environment}-eventbridge-forwarder-role" + assume_role_policy = jsonencode({ + Version : "2012-10-17", + Statement = [{ + Sid = "TrustEventBridgeService", + Effect = "Allow", + Principal = { Service = "events.amazonaws.com" }, + Action = "sts:AssumeRole", + Condition = { + StringEquals = { + "aws:SourceAccount" = var.imms_account_id + } + } + }] + }) +} + +resource "aws_iam_role_policy" "eventbridge_forwarder_policy" { + name = "imms-${var.environment}-eventbridge-forwarder-policy" + role = aws_iam_role.eventbridge_forwarder_role.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Sid = "ActionsForResource", + Effect = "Allow", + Action = ["events:PutEvents"], + Resource = [ + "arn:aws:events:eu-west-2:${var.csoc_account_id}:event-bus/shield-eventbus" + ] + }] + }) +} diff --git a/infra/environments/dev/variables.tfvars b/infra/environments/dev/variables.tfvars index a6fd60afd..9bff9955d 100644 --- a/infra/environments/dev/variables.tfvars +++ b/infra/environments/dev/variables.tfvars @@ -1,6 +1,7 @@ imms_account_id = "345594581768" dspp_account_id = "603871901111" mns_account_id = "631615744739" +csoc_account_id = "693466633220" admin_role = "root" # We shouldn't be using the root account. There should be an Admin role dev_ops_role = "role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_DEV-IMMS-Devops_745af4f208886ecc" dspp_admin_role = "root" diff --git a/infra/environments/preprod/variables.tfvars b/infra/environments/preprod/variables.tfvars index 06df49545..0f8031e58 100644 --- a/infra/environments/preprod/variables.tfvars +++ b/infra/environments/preprod/variables.tfvars @@ -1,6 +1,7 @@ imms_account_id = "084828561157" dspp_account_id = "603871901111" mns_account_id = "631615744739" +csoc_account_id = "693466633220" # admin_role = "role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_PREPROD-IMMS-Admin_acce656dcacf6f4c" admin_role = "root" dev_ops_role = "role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_PREPROD-IMMS-Devops_1d28e4f37b940bcd" diff --git a/infra/environments/prod/variables.tfvars b/infra/environments/prod/variables.tfvars index c4999291e..d5a7a4903 100644 --- a/infra/environments/prod/variables.tfvars +++ b/infra/environments/prod/variables.tfvars @@ -1,6 +1,7 @@ imms_account_id = "664418956997" dspp_account_id = "232116723729" mns_account_id = "758334270304" +csoc_account_id = "693466633220" # admin_role = "role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_PROD-IMMS-Admin_edd6691e4b74064e" admin_role = "root" dev_ops_role = "role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_PROD-IMMS-Devops_8f32c62195d56b76" diff --git a/infra/main.tf b/infra/main.tf index 0ae01c4b0..2b28ba4ae 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -21,3 +21,14 @@ provider "aws" { } } } + +provider "aws" { + alias = "use1" + region = "us-east-1" + default_tags { + tags = { + Project = "immunisation-fhir-api" + Environment = var.environment + } + } +} \ No newline at end of file diff --git a/infra/shield_protection.tf b/infra/shield_protection.tf new file mode 100644 index 000000000..04bd70d6f --- /dev/null +++ b/infra/shield_protection.tf @@ -0,0 +1,130 @@ + +# AWS Dynamic Lookups +data "aws_availability_zones" "available" {} +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} + +# Create all resources to Protect +resource "aws_shield_protection" "nat_eip" { + name = "imms-${var.environment}-fhir-api-eip-shield" + resource_arn = "arn:aws:ec2:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:eip-allocation/${aws_eip.nat.id}" +} + +resource "aws_shield_protection" "parent_dns" { + provider = aws.use1 + name = "imms-${var.environment}-fhir-api-parent-dns-shield" + resource_arn = aws_route53_zone.parent_hosted_zone.arn +} + +resource "aws_shield_protection" "child_dns" { + provider = aws.use1 + name = "imms-${var.environment}-fhir-api-parent-dns-shield" + resource_arn = aws_route53_zone.child_hosted_zone.arn +} + + + +locals { + regional_shield_arn = { + nat_gateway_eip = aws_shield_protection.nat_eip.resource_arn + } + global_shield_arn = { + route53_parent_zone = aws_shield_protection.parent_dns.resource_arn + route53_child_zone = aws_shield_protection.child_dns.resource_arn + } +} + + +# Create Metric Alarms for each of those resources +resource "aws_cloudwatch_metric_alarm" "ddos_protection_regional" { + for_each = local.regional_shield_arn + + alarm_name = "imms-${var.environment}-shield_ddos_${each.key}" + alarm_description = "Alarm when Shield detects DDoS on ${each.key}" + + namespace = "AWS/DDoSProtection" + metric_name = "DDoSDetected" + statistic = "Maximum" + period = 60 + evaluation_periods = 20 + datapoints_to_alarm = 1 + threshold = 0 + comparison_operator = "GreaterThanThreshold" + treat_missing_data = "notBreaching" + + dimensions = { + ResourceArn = each.value + } +} + +# Create Metric Alarms for Global Resources in us-east-1 Region +resource "aws_cloudwatch_metric_alarm" "ddos_protection_global" { + for_each = local.global_shield_arn + + provider = aws.use1 + alarm_name = "imms-${var.environment}-shield_ddos_${each.key}" + alarm_description = "Alarm when Shield detects DDoS on ${each.key}" + + namespace = "AWS/DDoSProtection" + metric_name = "DDoSDetected" + statistic = "Maximum" + period = 60 + evaluation_periods = 20 + datapoints_to_alarm = 1 + threshold = 0 + comparison_operator = "GreaterThanThreshold" + treat_missing_data = "notBreaching" + + dimensions = { + ResourceArn = each.value + } +} + + +# Event Bus Rule for eu-west-2 Region + +resource "aws_cloudwatch_event_rule" "shield_ddos_rule_regional" { + name = "imms-${var.environment}-shield_ddos_rule_${data.aws_region.current.region}" + description = "Forward Shield DDoS CloudWatch alarms to CSOC event bus" + + event_pattern = jsonencode({ + "source" = ["aws.cloudwatch"], + "detail-type" = ["CloudWatch Alarm State Change"], + "resources" = [ + for alarm in aws_cloudwatch_metric_alarm.ddos_protection_regional : alarm.arn + ] + }) +} + + + +resource "aws_cloudwatch_event_target" "shield_ddos_target_regional" { + rule = aws_cloudwatch_event_rule.shield_ddos_rule_regional.name + target_id = "csoc-eventbus" + arn = "arn:aws:events:eu-west-2:${var.csoc_account_id}:event-bus/shield-eventbus" + role_arn = aws_iam_role.eventbridge_forwarder_role.arn +} + +# Event Bus Rule for us-east-1 Region + +resource "aws_cloudwatch_event_rule" "shield_ddos_rule_global" { + provider = aws.use1 + name = "imms-${var.environment}-shield_ddos_rule-us-east-1" + description = "Forward Shield DDoS CloudWatch alarms (global) to CSOC event bus" + + event_pattern = jsonencode({ + "source" = ["aws.cloudwatch"], + "detail-type" = ["CloudWatch Alarm State Change"], + "resources" = [ + for alarm in aws_cloudwatch_metric_alarm.ddos_protection_global : alarm.arn + ] + }) +} + +resource "aws_cloudwatch_event_target" "shield_ddos_target_global" { + provider = aws.use1 + rule = aws_cloudwatch_event_rule.shield_ddos_rule_global.name + target_id = "csoc-eventbus" + arn = "arn:aws:events:us-east-1:${var.csoc_account_id}:event-bus/shield-eventbus" + role_arn = aws_iam_role.eventbridge_forwarder_role.arn +} diff --git a/infra/shield_subscription.tf b/infra/shield_subscription.tf new file mode 100644 index 000000000..9c6c3e197 --- /dev/null +++ b/infra/shield_subscription.tf @@ -0,0 +1,7 @@ +// One-time Shield Advanced subscription for the account. +// This resource is account-level. + +resource "aws_shield_subscription" "shield_subscription" { + auto_renew = "ENABLED" + +} diff --git a/infra/variables.tf b/infra/variables.tf index 717f38b22..6d46a5b01 100644 --- a/infra/variables.tf +++ b/infra/variables.tf @@ -10,6 +10,12 @@ variable "dspp_account_id" { description = "DSPP Core AWS account ID" type = string } +variable "csoc_account_id" { + description = "CSOC Core AWS account ID" + type = string + +} + variable "auto_ops_role" { default = "role/auto-ops" type = string