From 46293bbf66a15c113c9c0fd649758c80b0c5de52 Mon Sep 17 00:00:00 2001 From: Milan Simonovic Date: Wed, 15 Jun 2022 13:09:01 +0200 Subject: [PATCH] WAF: add api instance (#27) * refactor: switch to waf module * api firewall: allow programmatic access --- main.tf | 79 ++++++++++++---- modules/waf/main.tf | 203 ---------------------------------------- modules/waf/vars.tf | 17 ---- modules/waf/versions.tf | 6 -- vars.tf | 13 +++ 5 files changed, 73 insertions(+), 245 deletions(-) delete mode 100644 modules/waf/main.tf delete mode 100644 modules/waf/vars.tf delete mode 100644 modules/waf/versions.tf diff --git a/main.tf b/main.tf index 03b939d..57a22bb 100644 --- a/main.tf +++ b/main.tf @@ -8,7 +8,6 @@ provider "aws" { alias = "us-east-1" } - ### # Find a certificate for our domain that has status ISSUED # NOTE that for now, this infra depends on managing certs INSIDE AWS/ACM @@ -65,29 +64,73 @@ module "cdn_static" { } # export s3 arn so serverless can pick it up to configure iam policies -resource "aws_ssm_parameter" "cdn_static_param" { - name = "/${var.eb_env_name}/${var.eb_env_stage}/s3_static_arn" - description = "S3 static bucket ARN" +resource "aws_ssm_parameter" "cdn_content_param" { + name = "/${var.eb_env_name}/${var.eb_env_stage}/s3_content_arn" + description = "S3 content (videos, images) bucket ARN" type = "SecureString" value = module.cdn_static.s3_bucket_arn } +# TODO remove +resource "aws_ssm_parameter" "cdn_content_param_deprecated" { + name = "/${var.eb_env_name}/${var.eb_env_stage}/s3_static_arn" + description = "S3 content (videos, images) bucket ARN" + type = "SecureString" + value = module.cdn_static.s3_bucket_arn +} ##### # Firewall # ##### +module "cdn_firewall" { + source = "git::https://github.com/mentorpal/terraform-modules//modules/api-waf?ref=tags/v1.4.1" + name = "${var.eb_env_name}-cdn-${var.eb_env_stage}" + scope = "CLOUDFRONT" + rate_limit = 1000 -module "firewall" { - source = "./modules/waf" - aws_region = var.aws_region - environment = var.eb_env_stage - rate_limit = 1000 - tags = var.eb_env_tags + excluded_bot_rules = [ + "CategorySocialMedia", # slack + "CategorySearchEngine" # google bot + ] + excluded_common_rules = [ + "SizeRestrictions_BODY", # 8kb is not enough + "CrossSiteScripting_BODY" # flags legit image upload attempts + ] + enable_logging = var.enable_cdn_firewall_logging + aws_region = var.aws_region + tags = var.eb_env_tags +} + +module "api_firewall" { + source = "git::https://github.com/mentorpal/terraform-modules//modules/api-waf?ref=tags/v1.4.1" + name = "${var.eb_env_name}-api-${var.eb_env_stage}" + scope = "REGIONAL" + rate_limit = 100 + + excluded_bot_rules = [ + "CategoryMonitoring", + # classifier & uploader calling graphql: + "CategoryHttpLibrary", + "SignalNonBrowserUserAgent", + ] + excluded_common_rules = [ + "SizeRestrictions_BODY", # 8kb is not enough + "CrossSiteScripting_BODY" # flags legit image upload attempts + ] + enable_logging = var.enable_api_firewall_logging + aws_region = var.aws_region + tags = var.eb_env_tags +} + +resource "aws_ssm_parameter" "api_firewall_ssm" { + name = "/${var.eb_env_name}/${var.eb_env_stage}/api_firewall_arn" + type = "String" + value = module.api_firewall.wafv2_webacl_arn } ###### -# CloudFront distro in front of Beanstalk +# CloudFront distro in front of s3 # # the default policy does not include query strings as cache keys @@ -134,7 +177,8 @@ resource "aws_cloudfront_function" "cf_fn_origin_root" { code = file("${path.module}/scripts/mentorpal-rewrite-default-index-s3-origin.js") } -module "cdn_beanstalk" { +# fronts just an s3 bucket with static assets (javascript, css, ...) for frontend apps hosting +module "cdn_static_assets" { source = "git::https://github.com/cloudposse/terraform-aws-cloudfront-s3-cdn.git?ref=tags/0.82.4" acm_certificate_arn = data.aws_acm_certificate.localregion.arn aliases = [var.site_domain_name] @@ -151,9 +195,6 @@ module "cdn_beanstalk" { dns_alias_enabled = true environment = var.aws_region - # TODO cicd pipeline - # deployment_principal_arns = {} - # cookies are used in graphql right? but seems to work with "none": forward_cookies = "none" @@ -207,7 +248,7 @@ module "cdn_beanstalk" { # this are artifacts generated from github code, no need to version them: versioning_enabled = false viewer_protocol_policy = "redirect-to-https" - web_acl_id = module.firewall.wafv2_webacl_arn + web_acl_id = module.cdn_firewall.wafv2_webacl_arn } # export to SSM so cicd can be configured for deployment @@ -215,19 +256,19 @@ module "cdn_beanstalk" { resource "aws_ssm_parameter" "cdn_id" { name = "/${var.eb_env_name}/${var.eb_env_stage}/CLOUDFRONT_DISTRIBUTION_ID" type = "String" - value = module.cdn_beanstalk.cf_id + value = module.cdn_static_assets.cf_id } resource "aws_ssm_parameter" "cdn_s3_websites_arn" { name = "/${var.eb_env_name}/${var.eb_env_stage}/s3-websites/ARN" description = "Bucket that stores frontend apps" type = "String" - value = module.cdn_beanstalk.s3_bucket_arn + value = module.cdn_static_assets.s3_bucket_arn } resource "aws_ssm_parameter" "cdn_s3_websites_name" { name = "/${var.eb_env_name}/${var.eb_env_stage}/s3-websites/NAME" description = "Bucket that stores frontend apps" type = "String" - value = module.cdn_beanstalk.s3_bucket + value = module.cdn_static_assets.s3_bucket } diff --git a/modules/waf/main.tf b/modules/waf/main.tf deleted file mode 100644 index 96b4e64..0000000 --- a/modules/waf/main.tf +++ /dev/null @@ -1,203 +0,0 @@ -resource "aws_wafv2_web_acl" "wafv2_webacl" { - name = "mentorpal-${var.environment}-wafv2-webacl" - scope = "CLOUDFRONT" - tags = var.tags - - default_action { - allow {} - } - - rule { - name = "ip-rate-limit-rule" - priority = 1 - - action { - block {} - } - - statement { - rate_based_statement { - aggregate_key_type = "IP" - limit = var.rate_limit - } - } - - visibility_config { - cloudwatch_metrics_enabled = false - metric_name = "${var.rate_limit}-ip-rate-limit-rule" - sampled_requests_enabled = false - } - } - - rule { - name = "common-control" - priority = 2 - - override_action { - none {} - } - statement { - managed_rule_group_statement { - # see https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html#aws-managed-rule-groups-baseline-crs - name = "AWSManagedRulesCommonRuleSet" - vendor_name = "AWS" - excluded_rule { - # 8kb is not enough to post videos - name = "SizeRestrictions_BODY" - } - excluded_rule { - # flags legit thumbnail upload attemts - name = "CrossSiteScripting_BODY" - } - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWS-Common-rule" - sampled_requests_enabled = true - } - } - - rule { - name = "bot-control" - priority = 3 - - override_action { - none {} - } - statement { - managed_rule_group_statement { - # see https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-bot.html - name = "AWSManagedRulesBotControlRuleSet" - vendor_name = "AWS" - - excluded_rule { - name = "CategorySocialMedia" # slack - } - excluded_rule { - name = "CategorySearchEngine" # google bot - } - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWS-BotControl-rule" - sampled_requests_enabled = true - } - } - - rule { - name = "AWSManagedRulesLinuxRuleSet" - priority = 4 - override_action { - none {} - } - statement { - managed_rule_group_statement { - name = "AWSManagedRulesLinuxRuleSet" - vendor_name = "AWS" - } - } - visibility_config { - metric_name = "AWS-Linux-rule" - cloudwatch_metrics_enabled = true - sampled_requests_enabled = true - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "mentorpal-${var.environment}-wafv2-webacl" - sampled_requests_enabled = true - } -} - -resource "aws_s3_bucket" "s3_logs" { - bucket = "mentorpal-aws-waf-logs-${var.aws_region}-${var.environment}" - acl = "private" - tags = var.tags -} - -data "aws_iam_policy_document" "policy_assume_kinesis" { - statement { - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["firehose.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "firehose_role" { - name = "mentorpal-firehose-aws-waf-logs-${var.aws_region}-${var.environment}" - assume_role_policy = data.aws_iam_policy_document.policy_assume_kinesis.json - tags = var.tags -} - -# https://docs.aws.amazon.com/firehose/latest/dev/controlling-access.html#using-iam-s3 -data "aws_iam_policy_document" "s3_policy_document" { - statement { - sid = "1" - actions = [ - "s3:GetBucketLocation", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - ] - - resources = [ - aws_s3_bucket.s3_logs.arn, - ] - } - - statement { - sid = "2" - actions = [ - "s3:AbortMultipartUpload", - "s3:GetObject", - "s3:PutObject", - ] - - resources = [ - "${aws_s3_bucket.s3_logs.arn}/*", - ] - } -} - -resource "aws_iam_policy" "s3_policy" { - name = "mentorpal-kinesis-s3-write-policy-${var.environment}" - policy = data.aws_iam_policy_document.s3_policy_document.json -} - -resource "aws_iam_role_policy_attachment" "firehose_s3_policy_attachment" { - role = aws_iam_role.firehose_role.name - policy_arn = aws_iam_policy.s3_policy.arn -} - -resource "aws_kinesis_firehose_delivery_stream" "waf_logs_kinesis_stream" { - # the name must begin with aws-waf-logs- - name = "aws-waf-logs-kinesis-stream-mentorpal-${var.environment}" - destination = "s3" - s3_configuration { - role_arn = aws_iam_role.firehose_role.arn - bucket_arn = aws_s3_bucket.s3_logs.arn - compression_format = "GZIP" - } - tags = var.tags -} - -resource "aws_wafv2_web_acl_logging_configuration" "waf_logging_conf_staging" { - log_destination_configs = [aws_kinesis_firehose_delivery_stream.waf_logs_kinesis_stream.arn] - resource_arn = aws_wafv2_web_acl.wafv2_webacl.arn - redacted_fields { - single_header { - name = "authorization" - } - } -} - -output "wafv2_webacl_arn" { - value = aws_wafv2_web_acl.wafv2_webacl.arn -} diff --git a/modules/waf/vars.tf b/modules/waf/vars.tf deleted file mode 100644 index 744ab0b..0000000 --- a/modules/waf/vars.tf +++ /dev/null @@ -1,17 +0,0 @@ -variable "aws_region" { - type = string - description = "AWS region" -} - -variable "environment" { - type = string -} - -variable "tags" { - type = map(string) -} - -variable "rate_limit" { - type = number - default = 100 # minimum -} diff --git a/modules/waf/versions.tf b/modules/waf/versions.tf deleted file mode 100644 index 3b292ca..0000000 --- a/modules/waf/versions.tf +++ /dev/null @@ -1,6 +0,0 @@ -terraform { - required_version = ">= 0.15.0" - required_providers { - aws = ">= 3.1" - } -} diff --git a/vars.tf b/vars.tf index 4f14734..8e58265 100644 --- a/vars.tf +++ b/vars.tf @@ -53,6 +53,19 @@ variable "static_cors_allowed_origins" { default = [] } + +variable "enable_cdn_firewall_logging" { + type = bool + default = false + description = "enable cdn firewall logging (s3 bucket for storage, and a kinesis stream for delivery)" +} + +variable "enable_api_firewall_logging" { + type = bool + default = false + description = "enable api firewall logging (s3 bucket for storage, and a kinesis stream for delivery)" +} + variable "enable_alarms" { type = bool description = "Not used atm, reserved for future alerts"