cds-snc / forms-terraform

Infrastructure as Code for the GC Forms environment
MIT License
15 stars 7 forks source link

feat: add module to manage IPv4 blocklist #844

Closed patheard closed 1 week ago

patheard commented 1 week ago

Summary

Update the IPv4 blocklist to use the module that will create and manage the IP addresses on the blocklist using a Lambda function.

On a schedule, the Lambda function will query the WAF logs and temporarily add any IP address that exceeds a threshold of blocked requests to the blocklist.

Related

github-actions[bot] commented 1 week ago

⚠ Terrform update available

Terraform: 1.9.6 (using 1.9.2)
Terragrunt: 0.67.10 (using 0.63.2)
github-actions[bot] commented 1 week ago

Staging: load_balancer

✅   Terraform Init: success ✅   Terraform Validate: success ✅   Terraform Format: success ✅   Terraform Plan: success ✅   Conftest: success

⚠️   Warning: resources will be destroyed by this change!

Plan: 9 to add, 1 to change, 1 to destroy
Show summary | CHANGE | NAME | |--------|-------------------------------------------------------------------------| | add | `module.waf_ip_blocklist.aws_cloudwatch_event_rule.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_cloudwatch_event_target.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_cloudwatch_log_group.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_iam_policy.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_iam_role.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_iam_role_policy_attachment.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_lambda_function.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_lambda_permission.ipv4_blocklist` | | | `module.waf_ip_blocklist.aws_wafv2_ip_set.ipv4_blocklist` | | delete | `aws_wafv2_ip_set.ipv4_blocklist` | | update | `aws_wafv2_web_acl.forms_acl` |
Show plan ```terraform Resource actions are indicated with the following symbols: + create ~ update in-place - destroy <= read (data resources) Terraform will perform the following actions: # aws_wafv2_ip_set.ipv4_blocklist will be destroyed # (because aws_wafv2_ip_set.ipv4_blocklist is not in configuration) - resource "aws_wafv2_ip_set" "ipv4_blocklist" { - addresses = [ - "101.32.218.31/32", - "135.125.217.54/32", - "135.125.244.48/32", - "135.125.246.110/32", - "135.125.246.189/32", - "157.66.54.50/32", - "165.23.42.204/32", - "200.73.134.6/32", - "34.70.145.43/32", - "35.188.45.35/32", - "46.250.229.125/32", - "47.236.192.208/32", - "47.245.117.221/32", - "47.251.99.88/32", - "78.153.140.151/32", - "78.153.140.177/32", - "8.216.95.92/32", - "8.217.10.15/32", - "91.92.247.121/32", ] -> null - arn = "arn:aws:wafv2:ca-central-1:687401027353:regional/ipset/ipv4_blocklist/2c518f8f-052d-4b2d-914b-d910dba1367d" -> null - id = "2c518f8f-052d-4b2d-914b-d910dba1367d" -> null - ip_address_version = "IPV4" -> null - lock_token = "d88e9eca-d32c-42e7-b09d-d5ff85b1180c" -> null - name = "ipv4_blocklist" -> null - scope = "REGIONAL" -> null - tags = {} -> null - tags_all = { - "CostCentre" = "forms-platform-staging" - "Terraform" = "true" } -> null # (1 unchanged attribute hidden) } # aws_wafv2_web_acl.forms_acl will be updated in-place ~ resource "aws_wafv2_web_acl" "forms_acl" { id = "e8fc1b67-9d4d-4a29-8c94-65b37b30a231" name = "GCForms" tags = {} # (8 unchanged attributes hidden) - rule { - name = "BlockedIPv4" -> null - priority = 80 -> null - action { - block { } } - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ca-central-1:687401027353:regional/ipset/ipv4_blocklist/2c518f8f-052d-4b2d-914b-d910dba1367d" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "BlockedIPv4" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "AWSManagedRulesAmazonIpReputationList" -> null - priority = 1 -> null - override_action { - none {} } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesAmazonIpReputationList" -> null - vendor_name = "AWS" -> null # (1 unchanged attribute hidden) } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "AWSManagedRulesAmazonIpReputationList" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "AWSManagedRulesKnownBadInputsRuleSet" -> null - priority = 40 -> null - override_action { - none {} } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesKnownBadInputsRuleSet" -> null - vendor_name = "AWS" -> null # (1 unchanged attribute hidden) } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "AWSManagedRulesKnownBadInputsRuleSet" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "AWSManagedRulesLinuxRuleSet" -> null - priority = 50 -> null - override_action { - none {} } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesLinuxRuleSet" -> null - vendor_name = "AWS" -> null # (1 unchanged attribute hidden) } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "AWSManagedRulesLinuxRuleSet" -> null - sampled_requests_enabled = true -> null } } + rule { + name = "BlockedIPv4" + priority = 80 + action { + count { } } + statement { + ip_set_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "BlockedIPv4" + sampled_requests_enabled = true } } + rule { + name = "AWSManagedRulesAmazonIpReputationList" + priority = 1 + override_action { + none {} } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAmazonIpReputationList" + vendor_name = "AWS" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true } } + rule { + name = "AWSManagedRulesKnownBadInputsRuleSet" + priority = 40 + override_action { + none {} } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesKnownBadInputsRuleSet" + sampled_requests_enabled = true } } + rule { + name = "AWSManagedRulesLinuxRuleSet" + priority = 50 + override_action { + none {} } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesLinuxRuleSet" + vendor_name = "AWS" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesLinuxRuleSet" + sampled_requests_enabled = true } } # (7 unchanged blocks hidden) } # module.waf_ip_blocklist.data.aws_iam_policy_document.cloudwatch will be read during apply # (config refers to values not yet known) <= data "aws_iam_policy_document" "cloudwatch" { + id = (known after apply) + json = (known after apply) + minified_json = (known after apply) + statement { + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", ] + effect = "Allow" + resources = [ + (known after apply), ] + sid = "CloudWatchWriteAccess" } } # module.waf_ip_blocklist.data.aws_iam_policy_document.combined will be read during apply # (config refers to values not yet known) <= data "aws_iam_policy_document" "combined" { + id = (known after apply) + json = (known after apply) + minified_json = (known after apply) + source_policy_documents = [ + jsonencode( { + Statement = [ + { + Action = [ + "athena:StartQueryExecution", + "athena:GetQueryResults", + "athena:GetQueryExecution", ] + Effect = "Allow" + Resource = "arn:aws:athena:ca-central-1:687401027353:workgroup/primary" + Sid = "AthenaQueryAccess" }, + { + Action = [ + "athena:ListTableMetadata", + "athena:ListDatabases", + "athena:GetTableMetadata", ] + Effect = "Allow" + Resource = [ + "arn:aws:athena:ca-central-1:687401027353:catalog/AwsDataCatalog/database/access_logs/table/waf_logs", + "arn:aws:athena:ca-central-1:687401027353:catalog/AwsDataCatalog/database/access_logs", ] + Sid = "AthenaReadAccess" }, + { + Action = [ + "glue:GetTable", + "glue:GetPartitions", + "glue:GetDatabase", ] + Effect = "Allow" + Resource = [ + "arn:aws:glue:ca-central-1:687401027353:table/access_logs/waf_logs", + "arn:aws:glue:ca-central-1:687401027353:database/access_logs", + "arn:aws:glue:ca-central-1:687401027353:catalog", ] + Sid = "GlueReadAccess" }, ] + Version = "2012-10-17" } ), + (known after apply), + jsonencode( { + Statement = [ + { + Action = [ + "s3:ListMultipartUploadParts", + "s3:ListBucketMultipartUploads", + "s3:ListBucket", + "s3:GetObject", + "s3:GetBucketLocation", + "s3:AbortMultipartUpload", ] + Effect = "Allow" + Resource = [ + "arn:aws:s3:::forms-staging-athena-bucket/*", + "arn:aws:s3:::forms-staging-athena-bucket", + "arn:aws:s3:::cbs-satellite-687401027353/*", + "arn:aws:s3:::cbs-satellite-687401027353", ] + Sid = "S3ReadAccess" }, ] + Version = "2012-10-17" } ), + jsonencode( { + Statement = [ + { + Action = "s3:PutObject" + Effect = "Allow" + Resource = "arn:aws:s3:::forms-staging-athena-bucket/*" + Sid = "S3WriteAccess" }, ] + Version = "2012-10-17" } ), + (known after apply), ] } # module.waf_ip_blocklist.data.aws_iam_policy_document.waf_ip_set will be read during apply # (config refers to values not yet known) <= data "aws_iam_policy_document" "waf_ip_set" { + id = (known after apply) + json = (known after apply) + minified_json = (known after apply) + statement { + actions = [ + "wafv2:GetIPSet", + "wafv2:UpdateIPSet", ] + effect = "Allow" + resources = [ + (known after apply), ] + sid = "WAFIPSetUpdate" } } # module.waf_ip_blocklist.aws_cloudwatch_event_rule.ipv4_blocklist will be created + resource "aws_cloudwatch_event_rule" "ipv4_blocklist" { + arn = (known after apply) + description = "Update the IPv4 blocklist" + event_bus_name = "default" + force_destroy = false + id = (known after apply) + name = "ipv4_blocklist_forms_app" + name_prefix = (known after apply) + schedule_expression = "rate(2 hours)" + tags = { + "CostCentre" = "forms" + "Terraform" = "true" } + tags_all = { + "CostCentre" = "forms" + "Terraform" = "true" } } # module.waf_ip_blocklist.aws_cloudwatch_event_target.ipv4_blocklist will be created + resource "aws_cloudwatch_event_target" "ipv4_blocklist" { + arn = (known after apply) + event_bus_name = "default" + force_destroy = false + id = (known after apply) + rule = (known after apply) + target_id = (known after apply) } # module.waf_ip_blocklist.aws_cloudwatch_log_group.ipv4_blocklist will be created + resource "aws_cloudwatch_log_group" "ipv4_blocklist" { + arn = (known after apply) + id = (known after apply) + log_group_class = (known after apply) + name = "/aws/lambda/ipv4_blocklist_forms_app" + name_prefix = (known after apply) + retention_in_days = 14 + skip_destroy = false + tags = { + "CostCentre" = "forms" + "Terraform" = "true" } + tags_all = { + "CostCentre" = "forms" + "Terraform" = "true" } } # module.waf_ip_blocklist.aws_iam_policy.ipv4_blocklist will be created + resource "aws_iam_policy" "ipv4_blocklist" { + arn = (known after apply) + attachment_count = (known after apply) + id = (known after apply) + name = "ipv4_blocklist_forms_app" + name_prefix = (known after apply) + path = "/" + policy = (known after apply) + policy_id = (known after apply) + tags = { + "CostCentre" = "forms" + "Terraform" = "true" } + tags_all = { + "CostCentre" = "forms" + "Terraform" = "true" } } # module.waf_ip_blocklist.aws_iam_role.ipv4_blocklist will be created + resource "aws_iam_role" "ipv4_blocklist" { + arn = (known after apply) + assume_role_policy = jsonencode( { + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" } }, ] + Version = "2012-10-17" } ) + create_date = (known after apply) + force_detach_policies = false + id = (known after apply) + managed_policy_arns = (known after apply) + max_session_duration = 3600 + name = "ipv4_blocklist_forms_app" + name_prefix = (known after apply) + path = "/" + tags = { + "CostCentre" = "forms" + "Terraform" = "true" } + tags_all = { + "CostCentre" = "forms" + "Terraform" = "true" } + unique_id = (known after apply) + inline_policy (known after apply) } # module.waf_ip_blocklist.aws_iam_role_policy_attachment.ipv4_blocklist will be created + resource "aws_iam_role_policy_attachment" "ipv4_blocklist" { + id = (known after apply) + policy_arn = (known after apply) + role = "ipv4_blocklist_forms_app" } # module.waf_ip_blocklist.aws_lambda_function.ipv4_blocklist will be created + resource "aws_lambda_function" "ipv4_blocklist" { + architectures = (known after apply) + arn = (known after apply) + code_sha256 = (known after apply) + description = "Update the IPv4 blocklist based on the WAF logs" + filename = "/tmp/blocklist.zip" + function_name = "ipv4_blocklist_forms_app" + handler = "blocklist.handler" + id = (known after apply) + invoke_arn = (known after apply) + last_modified = (known after apply) + memory_size = 1024 + package_type = "Zip" + publish = false + qualified_arn = (known after apply) + qualified_invoke_arn = (known after apply) + reserved_concurrent_executions = -1 + role = (known after apply) + runtime = "python3.11" + signing_job_arn = (known after apply) + signing_profile_version_arn = (known after apply) + skip_destroy = false + source_code_hash = "fCJk+rUL/Gi+YyFakKfRGodgqUC5Mq5QYcxBi1huoa0=" + source_code_size = (known after apply) + tags = { + "CostCentre" = "forms" + "Terraform" = "true" } + tags_all = { + "CostCentre" = "forms" + "Terraform" = "true" } + timeout = 300 + version = (known after apply) + environment { + variables = (known after apply) } + ephemeral_storage (known after apply) + logging_config (known after apply) + tracing_config { + mode = "Active" } } # module.waf_ip_blocklist.aws_lambda_permission.ipv4_blocklist will be created + resource "aws_lambda_permission" "ipv4_blocklist" { + action = "lambda:InvokeFunction" + function_name = "ipv4_blocklist_forms_app" + id = (known after apply) + principal = "events.amazonaws.com" + source_arn = (known after apply) + statement_id = "AllowExecutionFromCloudWatch-ipv4_blocklist_forms_app" + statement_id_prefix = (known after apply) } # module.waf_ip_blocklist.aws_wafv2_ip_set.ipv4_blocklist will be created + resource "aws_wafv2_ip_set" "ipv4_blocklist" { + arn = (known after apply) + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "ipv4_blocklist_forms_app" + scope = "REGIONAL" + tags = { + "CostCentre" = "forms" + "Terraform" = "true" } + tags_all = { + "CostCentre" = "forms" + "Terraform" = "true" } } Plan: 9 to add, 1 to change, 1 to destroy. Changes to Outputs: ~ waf_ipv4_blocklist_arn = "arn:aws:wafv2:ca-central-1:687401027353:regional/ipset/ipv4_blocklist/2c518f8f-052d-4b2d-914b-d910dba1367d" -> (known after apply) ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan.tfplan To perform exactly these actions, run the following command to apply: terraform apply "plan.tfplan" ```
Show Conftest results ```sh WARN - plan.json - main - Missing Common Tags: ["aws_acm_certificate.form_viewer"] WARN - plan.json - main - Missing Common Tags: ["aws_acm_certificate.form_viewer_maintenance_mode"] WARN - plan.json - main - Missing Common Tags: ["aws_acm_certificate.forms_api"] WARN - plan.json - main - Missing Common Tags: ["aws_alb_listener_rule.forms_api"] WARN - plan.json - main - Missing Common Tags: ["aws_cloudfront_distribution.maintenance_mode"] WARN - plan.json - main - Missing Common Tags: ["aws_iam_role.firehose_waf_logs"] WARN - plan.json - main - Missing Common Tags: ["aws_kinesis_firehose_delivery_stream.firehose_waf_logs"] WARN - plan.json - main - Missing Common Tags: ["aws_lb.form_viewer"] WARN - plan.json - main - Missing Common Tags: ["aws_lb_listener.form_viewer_http"] WARN - plan.json - main - Missing Common Tags: ["aws_lb_listener.form_viewer_https"] WARN - plan.json - main - Missing Common Tags: ["aws_lb_target_group.form_viewer_1"] WARN - plan.json - main - Missing Common Tags: ["aws_lb_target_group.form_viewer_2"] WARN - plan.json - main - Missing Common Tags: ["aws_lb_target_group.forms_api"] WARN - plan.json - main - Missing Common Tags: ["aws_s3_bucket.maintenance_mode"] WARN - plan.json - main - Missing Common Tags: ["aws_s3_object.maintenance_static_page_css_files[\"style.css\"]"] WARN - plan.json - main - Missing Common Tags: ["aws_s3_object.maintenance_static_page_html_files[\"index-fr.html\"]"] WARN - plan.json - main - Missing Common Tags: ["aws_s3_object.maintenance_static_page_html_files[\"index.html\"]"] WARN - plan.json - main - Missing Common Tags: ["aws_s3_object.maintenance_static_page_ico_files[\"favicon.ico\"]"] WARN - plan.json - main - Missing Common Tags: ["aws_s3_object.maintenance_static_page_svg_files[\"site-unavailable.svg\"]"] WARN - plan.json - main - Missing Common Tags: ["aws_shield_protection.alb"] WARN - plan.json - main - Missing Common Tags: ["aws_shield_protection.route53_hosted_zone[0]"] WARN - plan.json - main - Missing... ```
github-actions[bot] commented 1 week ago

Staging: idp

✅   Terraform Init: success ✅   Terraform Validate: success ✅   Terraform Format: success ✅   Terraform Plan: success ✅   Conftest: success

Plan: 0 to add, 1 to change, 0 to destroy
Show summary | CHANGE | NAME | |--------|-------------------------| | update | `aws_wafv2_web_acl.idp` |
Show plan ```terraform Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # aws_wafv2_web_acl.idp will be updated in-place ~ resource "aws_wafv2_web_acl" "idp" { id = "bab5476e-86d5-4718-aa69-55b379a8673d" name = "idp" tags = { "CostCentre" = "forms-platform-staging" "Terraform" = "true" } # (8 unchanged attributes hidden) - rule { - name = "BlockedIPv4" -> null - priority = 70 -> null - action { - block { } } - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ca-central-1:687401027353:regional/ipset/ipv4_blocklist/2c518f8f-052d-4b2d-914b-d910dba1367d" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "BlockedIPv4" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "AWSManagedRulesAmazonIpReputationList" -> null - priority = 10 -> null - override_action { - none {} } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesAmazonIpReputationList" -> null - vendor_name = "AWS" -> null # (1 unchanged attribute hidden) } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "AWSManagedRulesAmazonIpReputationList" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "AWSManagedRulesKnownBadInputsRuleSet" -> null - priority = 30 -> null - override_action { - none {} } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesKnownBadInputsRuleSet" -> null - vendor_name = "AWS" -> null # (1 unchanged attribute hidden) } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "AWSManagedRulesKnownBadInputsRuleSet" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "AWSManagedRulesLinuxRuleSet" -> null - priority = 40 -> null - override_action { - none {} } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesLinuxRuleSet" -> null - vendor_name = "AWS" -> null # (1 unchanged attribute hidden) } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "AWSManagedRulesLinuxRuleSet" -> null - sampled_requests_enabled = true -> null } } + rule { + name = "BlockedIPv4" + priority = 70 + action { + count { } } + statement { + ip_set_reference_statement { + arn = "arn:aws:wafv2:ca-central-1:687401027353:regional/ipset/ipv4_blocklist/2c518f8f-052d-4b2d-914b-d910dba1367d" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "BlockedIPv4" + sampled_requests_enabled = true } } + rule { + name = "AWSManagedRulesAmazonIpReputationList" + priority = 10 + override_action { + none {} } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAmazonIpReputationList" + vendor_name = "AWS" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true } } + rule { + name = "AWSManagedRulesKnownBadInputsRuleSet" + priority = 30 + override_action { + none {} } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesKnownBadInputsRuleSet" + sampled_requests_enabled = true } } + rule { + name = "AWSManagedRulesLinuxRuleSet" + priority = 40 + override_action { + none {} } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesLinuxRuleSet" + vendor_name = "AWS" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesLinuxRuleSet" + sampled_requests_enabled = true } } # (7 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan.tfplan To perform exactly these actions, run the following command to apply: terraform apply "plan.tfplan" ```
Show Conftest results ```sh WARN - plan.json - main - Missing Common Tags: ["aws_alb_listener_rule.idp_protocol_version"] WARN - plan.json - main - Missing Common Tags: ["aws_iam_policy.idp_send_email"] WARN - plan.json - main - Missing Common Tags: ["aws_iam_user.idp_send_email"] WARN - plan.json - main - Missing Common Tags: ["aws_shield_protection.idp"] 23 tests, 19 passed, 4 warnings, 0 failures, 0 exceptions ```