diff --git a/.github/workflows/terraform.yaml b/.github/workflows/terraform.yaml index 3dea7ea7..f85dd0ef 100644 --- a/.github/workflows/terraform.yaml +++ b/.github/workflows/terraform.yaml @@ -4,6 +4,7 @@ on: pull_request: paths: - terraform/environment/wildsea/** + - terraform/modules/wildsea/** push: branches: - main diff --git a/Makefile b/Makefile index 7eddd28f..3e2707be 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ default: all TERRAFORM_ENVIRONMENTS := aws github wildsea aws-dev wildsea-dev TERRAFOM_VALIDATE := $(addsuffix /.validate,$(addprefix terraform/environment/, $(TERRAFORM_ENVIRONMENTS))) +TERRAFORM_MODULES := iac-roles oidc state-bucket wildsea ACCOUNT_ID := $(shell aws sts get-caller-identity --query 'Account' --output text) AWS_REGION ?= "ap-southeast-2" RO_ROLE = arn:aws:iam::$(ACCOUNT_ID):role/GitHubAction-Wildsea-ro-dev @@ -9,13 +10,25 @@ RW_ROLE = arn:aws:iam::$(ACCOUNT_ID):role/GitHubAction-Wildsea-rw-dev all: $(TERRAFOM_VALIDATE) -terraform/environment/%/.validate: terraform/environment/%/*.tf +.PHONY: terraform-format +terraform-format: $(addprefix terraform-format-environment-,$(TERRAFORM_ENVIRONMENTS)) $(addprefix terraform-format-module-,$(TERRAFORM_MODULES)) + @true + +.PHONY: terraform-format-environment-% +terraform-format-environment-%: + cd terraform/environment/$*; terraform fmt + +.PHONY: terraform-format-module-% +terraform-format-module-%: + cd terraform/module/$*; terraform fmt + +terraform/environment/%/.validate: terraform/environment/%/*.tf terraform-format cd terraform/environment/$* ; terraform fmt cd terraform/environment/$* ; terraform validate touch $@ .PHONY: dev -dev: terraform/environment/aws-dev/.apply terraform/environment/wildsea-dev/.apply +dev: terraform-format terraform/environment/aws-dev/.apply terraform/environment/wildsea-dev/.apply @true terraform/environment/aws-dev/.apply: terraform/environment/aws-dev/*.tf terraform/module/iac-roles/*.tf diff --git a/terraform/environment/aws-dev/main.tf b/terraform/environment/aws-dev/main.tf index a32f67c2..af062a9e 100644 --- a/terraform/environment/aws-dev/main.tf +++ b/terraform/environment/aws-dev/main.tf @@ -46,13 +46,13 @@ provider "aws" { } module "iac-roles" { - source = "../../module/iac-roles" - app_name = var.app_name - environment = var.environment - action_prefix = var.action_prefix - workspace = "none" - repo = var.repo + source = "../../module/iac-roles" + app_name = var.app_name + environment = var.environment + action_prefix = var.action_prefix + workspace = "none" + repo = var.repo state_bucket_arn = "arn:${data.aws_partition.current.id}:s3:::${var.state_bucket}" - oidc_type = "AWS" - oidc_arn = data.aws_caller_identity.current.account_id + oidc_type = "AWS" + oidc_arn = data.aws_caller_identity.current.account_id } diff --git a/terraform/environment/wildsea-dev/main.tf b/terraform/environment/wildsea-dev/main.tf index 0fb7a6d3..6399df3f 100644 --- a/terraform/environment/wildsea-dev/main.tf +++ b/terraform/environment/wildsea-dev/main.tf @@ -28,5 +28,5 @@ module "wildsea" { source = "../../module/wildsea" saml_metadata_url = var.saml_metadata_url - prefix = local.prefix + prefix = local.prefix } diff --git a/terraform/environment/wildsea-dev/plan b/terraform/environment/wildsea-dev/plan index ff4b49b6..b31203bf 100644 Binary files a/terraform/environment/wildsea-dev/plan and b/terraform/environment/wildsea-dev/plan differ diff --git a/terraform/environment/wildsea/main.tf b/terraform/environment/wildsea/main.tf index b9254771..2c99957b 100644 --- a/terraform/environment/wildsea/main.tf +++ b/terraform/environment/wildsea/main.tf @@ -33,5 +33,5 @@ module "wildsea" { source = "../../module/wildsea" saml_metadata_url = var.saml_metadata_url - prefix = local.prefix + prefix = local.prefix } diff --git a/terraform/module/iac-roles/policy.tf b/terraform/module/iac-roles/policy.tf index 927b0eda..babc7eef 100644 --- a/terraform/module/iac-roles/policy.tf +++ b/terraform/module/iac-roles/policy.tf @@ -46,6 +46,9 @@ data "aws_iam_policy_document" "ro" { sid = "CognitoIdpGlobal" actions = [ "cognito-idp:DescribeUserPoolDomain", + "appsync:SetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:GetWebAcl", ] resources = [ "*" @@ -107,6 +110,19 @@ data "aws_iam_policy_document" "ro" { "arn:${data.aws_partition.current.id}:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:*" ] } + + statement { + actions = [ + "wafv2:ListWebACLs", + "wafv2:ListTagsForResource", + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "aws:ResourceTag/Name" + values = [local.prefix] + } + } } data "aws_iam_policy_document" "rw" { @@ -254,7 +270,7 @@ data "aws_iam_policy_document" "rw" { } statement { - actions = ["iam:CreateServiceLinkedRole"] + actions = ["iam:CreateServiceLinkedRole"] resources = ["*"] condition { test = "StringEquals" @@ -262,6 +278,44 @@ data "aws_iam_policy_document" "rw" { values = ["appsync.${data.aws_partition.current.dns_suffix}"] } } + + statement { + actions = [ + "wafv2:CreateWebAcl", + "wafv2:TagResource", + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "aws:RequestTag/Name" + values = [local.prefix] + } + } + + statement { + actions = [ + "wafv2:CreateWebACL", + ] + resources = [ + "arn:aws:wafv2:ap-southeast-2:021891603679:regional/managedruleset/*/*" + ] + } + + statement { + actions = [ + "wafv2:UpdateWebACL", + "wafv2:DeleteWebACL", + "wafv2:ListTagsForResource", + "wafv2:AssociateWebACL", + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "aws:ResourceTag/Name" + values = [local.prefix] + } + } + } data "aws_iam_policy_document" "rw_boundary" { @@ -326,9 +380,11 @@ data "aws_iam_policy_document" "rw_boundary" { } statement { - sid = "CognitoIdpGlobal" actions = [ "cognito-idp:DescribeUserPoolDomain", + "appsync:SetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:GetWebAcl", ] resources = [ "*" @@ -441,7 +497,7 @@ data "aws_iam_policy_document" "rw_boundary" { } statement { - actions = ["iam:CreateServiceLinkedRole"] + actions = ["iam:CreateServiceLinkedRole"] resources = ["*"] condition { test = "StringEquals" @@ -449,4 +505,55 @@ data "aws_iam_policy_document" "rw_boundary" { values = ["appsync.${data.aws_partition.current.dns_suffix}"] } } + + statement { + actions = [ + "wafv2:CreateWebAcl", + "wafv2:TagResource", + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "aws:RequestTag/Name" + values = [local.prefix] + } + } + + statement { + actions = [ + "wafv2:CreateWebACL", + ] + resources = [ + "arn:aws:wafv2:ap-southeast-2:021891603679:regional/managedruleset/*/*" + ] + } + + statement { + actions = [ + "wafv2:UpdateWebACL", + "wafv2:DeleteWebACL", + "wafv2:UpdatebACL", + "wafv2:ListTagsForResource", + "wafv2:AssociateWebACL", + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "aws:ResourceTag/Name" + values = [local.prefix] + } + } + + statement { + actions = [ + "wafv2:ListWebACLs", + "wafv2:ListTagsForResource", + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "aws:ResourceTag/Name" + values = [local.prefix] + } + } } diff --git a/terraform/module/iac-roles/roles.tf b/terraform/module/iac-roles/roles.tf index cbcfdbcd..9bf09ec6 100644 --- a/terraform/module/iac-roles/roles.tf +++ b/terraform/module/iac-roles/roles.tf @@ -9,7 +9,7 @@ resource "aws_iam_role" "ro" { data "aws_iam_policy_document" "ro_assume" { statement { - actions = [ var.oidc_type == "Federated" ? "sts:AssumeRoleWithWebIdentity" : "sts:AssumeRole" ] + actions = [var.oidc_type == "Federated" ? "sts:AssumeRoleWithWebIdentity" : "sts:AssumeRole"] principals { type = var.oidc_type identifiers = [var.oidc_arn] @@ -59,7 +59,7 @@ resource "aws_iam_role" "rw" { data "aws_iam_policy_document" "rw_assume" { statement { - actions = [ var.oidc_type == "Federated" ? "sts:AssumeRoleWithWebIdentity" : "sts:AssumeRole" ] + actions = [var.oidc_type == "Federated" ? "sts:AssumeRoleWithWebIdentity" : "sts:AssumeRole"] principals { type = var.oidc_type identifiers = [var.oidc_arn] diff --git a/terraform/module/oidc/main.tf b/terraform/module/oidc/main.tf index d3e0c5f2..b7b501d0 100644 --- a/terraform/module/oidc/main.tf +++ b/terraform/module/oidc/main.tf @@ -11,5 +11,5 @@ resource "aws_iam_openid_connect_provider" "oidc" { } output "oidc_arn" { - value = aws_iam_openid_connect_provider.oidc.arn + value = aws_iam_openid_connect_provider.oidc.arn } diff --git a/terraform/module/state-bucket/s3.tf b/terraform/module/state-bucket/s3.tf index 3e6f0de6..ebcb36bc 100644 --- a/terraform/module/state-bucket/s3.tf +++ b/terraform/module/state-bucket/s3.tf @@ -57,5 +57,5 @@ resource "aws_s3_bucket_versioning" "state" { output "arn" { description = "ARN of the state bucket" - value = aws_s3_bucket.state.arn + value = aws_s3_bucket.state.arn } diff --git a/terraform/module/wildsea/cognito.tf b/terraform/module/wildsea/cognito.tf index 7f6f5932..bc16d35f 100644 --- a/terraform/module/wildsea/cognito.tf +++ b/terraform/module/wildsea/cognito.tf @@ -7,7 +7,7 @@ resource "aws_cognito_user_pool" "cognito" { } resource "aws_cognito_identity_provider" "idp" { - for_each = var.saml_metadata_url == "" ? toset([]) : toset([1]) + for_each = var.saml_metadata_url == "" ? toset([]) : toset([1]) user_pool_id = aws_cognito_user_pool.cognito.id provider_name = "SAML" provider_type = "SAML" @@ -34,7 +34,7 @@ resource "aws_cognito_user_pool_client" "cognito" { logout_urls = ["https://TODO"] allowed_oauth_flows = ["code", "implicit"] allowed_oauth_scopes = ["openid"] - supported_identity_providers = [ var.saml_metadata_url == "" ? "COGNITO" : aws_cognito_identity_provider.idp[0].provider_name ] + supported_identity_providers = [var.saml_metadata_url == "" ? "COGNITO" : aws_cognito_identity_provider.idp[0].provider_name] } resource "aws_cognito_identity_pool" "cognito" { @@ -86,7 +86,7 @@ resource "aws_iam_policy" "cognito" { name = "${var.prefix}-user" policy = data.aws_iam_policy_document.cognito.json } - + data "aws_iam_policy_document" "cognito" { statement { actions = [ diff --git a/terraform/module/wildsea/graphql.tf b/terraform/module/wildsea/graphql.tf index ea4b03d4..26dd77ef 100644 --- a/terraform/module/wildsea/graphql.tf +++ b/terraform/module/wildsea/graphql.tf @@ -1,29 +1,32 @@ resource "aws_appsync_graphql_api" "graphql" { - name = var.prefix - schema = file("../../../graphql/schema.graphql") - authentication_type = "AWS_IAM" - xray_enabled = true + name = var.prefix + schema = file("../../../graphql/schema.graphql") + authentication_type = "AWS_IAM" + xray_enabled = true - log_config { - cloudwatch_logs_role_arn = aws_iam_role.graphql_log.arn - field_log_level = "ERROR" - } + log_config { + cloudwatch_logs_role_arn = aws_iam_role.graphql_log.arn + field_log_level = "ERROR" + } - additional_authentication_provider { - authentication_type = "AMAZON_COGNITO_USER_POOLS" - user_pool_config { - user_pool_id = aws_cognito_user_pool.cognito.id - aws_region = data.aws_region.current.name - } + additional_authentication_provider { + authentication_type = "AMAZON_COGNITO_USER_POOLS" + user_pool_config { + user_pool_id = aws_cognito_user_pool.cognito.id + aws_region = data.aws_region.current.name } + } tags = { Name = var.prefix } } +# nosemgrep: aws-cloudwatch-log-group-unencrypted // AWS Key is fine resource "aws_cloudwatch_log_group" "graphql_log" { - name = "/aws/appsync/${var.prefix}" + # checkov:skip=CKV_AWS_338:Two weeks is enough, we don't need a year + # checkov:skip=CKV_AWS_158:AWS Key is fine + name = "/aws/appsync/${var.prefix}" retention_in_days = 14 tags = { @@ -32,25 +35,115 @@ resource "aws_cloudwatch_log_group" "graphql_log" { } resource "aws_iam_role" "graphql_log" { - name = "${var.prefix}-graphql-log" - assume_role_policy = data.aws_iam_policy_document.graphql_log_assume.json + name = "${var.prefix}-graphql-log" + assume_role_policy = data.aws_iam_policy_document.graphql_log_assume.json - tags = { - Name = var.prefix - } + tags = { + Name = var.prefix + } } data "aws_iam_policy_document" "graphql_log_assume" { statement { actions = ["sts:AssumeRole"] principals { - type = "Service" + type = "Service" identifiers = ["appsync.${data.aws_partition.current.dns_suffix}"] } } } resource "aws_iam_role_policy_attachment" "grahql_log" { - role = aws_iam_role.graphql_log.name - policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs" + role = aws_iam_role.graphql_log.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs" +} + +resource "aws_wafv2_web_acl_association" "graphql" { + resource_arn = aws_appsync_graphql_api.graphql.arn + web_acl_arn = aws_wafv2_web_acl.graphql.arn +} + +resource "aws_wafv2_web_acl" "graphql" { + name = "${var.prefix}-graphql-waf" + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "Ratelimit" + priority = 10 + + action { + block {} + } + + statement { + rate_based_statement { + limit = 1000 + aggregate_key_type = "IP" + + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "Ratelimit" + sampled_requests_enabled = false + } + } + + + rule { + name = "AWSManagedRulesCommonRuleSet" + priority = 20 + statement { + managed_rule_group_statement { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + } + } + override_action { + count { + + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesCommonRuleSet" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesAmazonIpReputationList" + priority = 30 + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAmazonIpReputationList" + vendor_name = "AWS" + } + } + override_action { + count { + + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "GraphQLWAF" + sampled_requests_enabled = true + } + + tags = { + Name = var.prefix + } } diff --git a/terraform/module/wildsea/main.tf b/terraform/module/wildsea/main.tf index a8e354f4..61c12747 100644 --- a/terraform/module/wildsea/main.tf +++ b/terraform/module/wildsea/main.tf @@ -3,11 +3,11 @@ data "aws_partition" "current" {} data "aws_caller_identity" "current" {} variable "prefix" { - description = "Resource name prefix" - type = string + description = "Resource name prefix" + type = string } variable "saml_metadata_url" { - description = "SAML metadata URL" - type = string + description = "SAML metadata URL" + type = string }