From aa10a8ab23945eb11ca689a6da83b3ea0300063d Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Thu, 21 Aug 2025 17:56:55 -0600 Subject: [PATCH 01/70] initial checkin --- terraform/services/cost-anomaly/main.tf | 41 +++++++++++++++++++++++ terraform/services/cost-anomaly/terraform | 11 ++++++ 2 files changed, 52 insertions(+) create mode 100644 terraform/services/cost-anomaly/main.tf create mode 100644 terraform/services/cost-anomaly/terraform diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf new file mode 100644 index 00000000..cd79b10a --- /dev/null +++ b/terraform/services/cost-anomaly/main.tf @@ -0,0 +1,41 @@ +resource "aws_sns_topic" "cost_anomaly_alerts"{ + name = "cost-anomaly-alerts-topic" +} + +resource "aws_sns_topic_policy" "cost_anomaly_topic_policy"{ + arn = aws_sns_topic.cost_anomaly_alerts.arn + policy = data.aws_iam_policy_document.cost_anomaly_topic_access.json } + +data "aws_iam_policy_document" "cost_anomaly_topic_access" { + statement { + effect = "Allow" + principals + + { type = "Service" identifiers = ["ce.amazonaws.com"] } + actions = ["sns:Publish"] + resources = [aws_sns_topic.cost_anomaly_alerts.arn] + } +} + +resource "aws_ce_anomaly_monitor" "all_services_monitor"{ + name = "all-services-cost-monitor" + monitor_type = "DIMENSIONAL" + monitor_dimension = "SERVICE" +} + +resource "aws_ce_anomaly_subscription" "cost_anomaly_subscription" { + name = "cost-anomaly-alerts-subscription" + frequency = "DAILY" + monitor_arn_list = [aws_ce_anomaly_monitor.all_services_monitor.arn] + subscriber{ + type = "SNS" + address = aws_sns_topic.cost_anomaly_alerts.arn + } + threshold_expression { + dimension { + key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" + match_options = ["GREATER_THAN_OR_EQUAL"] + values = ["150"] + } + } +} \ No newline at end of file diff --git a/terraform/services/cost-anomaly/terraform b/terraform/services/cost-anomaly/terraform new file mode 100644 index 00000000..7b7e43ad --- /dev/null +++ b/terraform/services/cost-anomaly/terraform @@ -0,0 +1,11 @@ +terraform { + backend "s3" { + key = "cost-anomaly/terraform.tfstate" + } +} + +provider "aws" { + default_tags { + tags = module.standards.default_tags + } +} \ No newline at end of file From 148001251b1c918723dfaac07ef1db8ce9e7a056 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Thu, 21 Aug 2025 17:59:35 -0600 Subject: [PATCH 02/70] add workflow check --- .github/workflows/tf-cost-anomaly.yml | 54 ++++++++++++++++++++ terraform/services/cost-anomaly/variables.tf | 17 ++++++ 2 files changed, 71 insertions(+) create mode 100644 .github/workflows/tf-cost-anomaly.yml create mode 100644 terraform/services/cost-anomaly/variables.tf diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml new file mode 100644 index 00000000..77951237 --- /dev/null +++ b/.github/workflows/tf-cost-anomaly.yml @@ -0,0 +1,54 @@ +name: tf-cost-anomaly +run-name: tf-cost-anomaly ${{ (inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main')) && 'apply' || 'plan' }} + +on: + push: + paths: + - .github/workflows/tf-cost-anomaly.yml + - terraform/services/cost-anomaly/** + workflow_dispatch: + inputs: + apply: + required: false + type: boolean + description: "Apply the terraform?" + +jobs: + check-fmt: + runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} + steps: + - uses: actions/checkout@v4 + - uses: ./actions/setup-tfenv-terraform + - run: terraform fmt -check -diff -recursive terraform/services/cost-anomaly + + plan-apply: + needs: check-fmt + permissions: + contents: read + id-token: write + runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} + defaults: + run: + working-directory: ./terraform/services/cost-anomaly + strategy: + fail-fast: false + matrix: + app: [ab2d, bcda, dpc] + env: [dev, test, sandbox, prod] + include: + - app: cdap + env: mgmt + steps: + - uses: actions/checkout@v4 + - uses: ./actions/setup-tfenv-terraform + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions + aws-region: ${{ vars.AWS_REGION }} + - run: terraform init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend + - run: terraform plan -out=tf.plan + env: + TF_VAR_app: ${{ matrix.app }} + TF_VAR_env: ${{ matrix.env }} + - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') + run: terraform apply -auto-approve tf.plan diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf new file mode 100644 index 00000000..1e98ef7a --- /dev/null +++ b/terraform/services/cost-anomaly/variables.tf @@ -0,0 +1,17 @@ +variable "app" { + description = "The application name (ab2d, bcda, dpc, cdap)" + type = string + validation { + condition = contains(["ab2d", "bcda", "dpc", "cdap"], var.app) + error_message = "Valid value for app is ab2d, bcda, dpc, or cdap." + } +} + +variable "env" { + description = "The application environment (dev, test, mgmt, sbx, sandbox, prod)" + type = string + validation { + condition = contains(["dev", "test", "mgmt", "sandbox", "prod"], var.env) + error_message = "Valid value for env is dev, test, mgmt, sandbox, or prod." + } +} From 348f93336a231f1b5fb98469459068fe73df7a28 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Thu, 21 Aug 2025 18:02:40 -0600 Subject: [PATCH 03/70] fmt --- terraform/services/cost-anomaly/main.tf | 32 +++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index cd79b10a..3319552d 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -1,34 +1,36 @@ -resource "aws_sns_topic" "cost_anomaly_alerts"{ +resource "aws_sns_topic" "cost_anomaly_alerts" { name = "cost-anomaly-alerts-topic" } -resource "aws_sns_topic_policy" "cost_anomaly_topic_policy"{ - arn = aws_sns_topic.cost_anomaly_alerts.arn - policy = data.aws_iam_policy_document.cost_anomaly_topic_access.json } +resource "aws_sns_topic_policy" "cost_anomaly_topic_policy" { + arn = aws_sns_topic.cost_anomaly_alerts.arn + policy = data.aws_iam_policy_document.cost_anomaly_topic_access.json +} data "aws_iam_policy_document" "cost_anomaly_topic_access" { statement { effect = "Allow" - principals - - { type = "Service" identifiers = ["ce.amazonaws.com"] } - actions = ["sns:Publish"] + principals { + type = "Service" + identifiers = ["ce.amazonaws.com"] + } + actions = ["sns:Publish"] resources = [aws_sns_topic.cost_anomaly_alerts.arn] } } -resource "aws_ce_anomaly_monitor" "all_services_monitor"{ - name = "all-services-cost-monitor" - monitor_type = "DIMENSIONAL" +resource "aws_ce_anomaly_monitor" "all_services_monitor" { + name = "all-services-cost-monitor" + monitor_type = "DIMENSIONAL" monitor_dimension = "SERVICE" } resource "aws_ce_anomaly_subscription" "cost_anomaly_subscription" { - name = "cost-anomaly-alerts-subscription" - frequency = "DAILY" + name = "cost-anomaly-alerts-subscription" + frequency = "DAILY" monitor_arn_list = [aws_ce_anomaly_monitor.all_services_monitor.arn] - subscriber{ - type = "SNS" + subscriber { + type = "SNS" address = aws_sns_topic.cost_anomaly_alerts.arn } threshold_expression { From be551af04d4df191f6431c7c61af9267712bc349 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 12:17:30 -0600 Subject: [PATCH 04/70] cost monitor and sns configuration --- .github/workflows/tf-cost-anomaly.yml | 18 +-- terraform/services/cost-anomaly/main.tf | 115 +++++++++++++++---- terraform/services/cost-anomaly/terraform | 11 -- terraform/services/cost-anomaly/terraform.tf | 17 +++ terraform/services/cost-anomaly/variables.tf | 11 +- 5 files changed, 119 insertions(+), 53 deletions(-) delete mode 100644 terraform/services/cost-anomaly/terraform create mode 100644 terraform/services/cost-anomaly/terraform.tf diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 77951237..120b27de 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -5,21 +5,21 @@ on: push: paths: - .github/workflows/tf-cost-anomaly.yml - - terraform/services/cost-anomaly/** + - terraform.tf/services/cost-anomaly/** workflow_dispatch: inputs: apply: required: false type: boolean - description: "Apply the terraform?" + description: "Apply the terraform.tf?" jobs: check-fmt: runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} steps: - uses: actions/checkout@v4 - - uses: ./actions/setup-tfenv-terraform - - run: terraform fmt -check -diff -recursive terraform/services/cost-anomaly + - uses: ./actions/setup-tfenv-terraform.tf + - run: terraform.tf fmt -check -diff -recursive terraform.tf/services/cost-anomaly plan-apply: needs: check-fmt @@ -29,7 +29,7 @@ jobs: runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} defaults: run: - working-directory: ./terraform/services/cost-anomaly + working-directory: ./terraform.tf/services/cost-anomaly strategy: fail-fast: false matrix: @@ -40,15 +40,15 @@ jobs: env: mgmt steps: - uses: actions/checkout@v4 - - uses: ./actions/setup-tfenv-terraform + - uses: ./actions/setup-tfenv-terraform.tf - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} - - run: terraform init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend - - run: terraform plan -out=tf.plan + - run: terraform.tf init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend + - run: terraform.tf plan -out=tf.plan env: TF_VAR_app: ${{ matrix.app }} TF_VAR_env: ${{ matrix.env }} - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') - run: terraform apply -auto-approve tf.plan + run: terraform.tf apply -auto-approve tf.plan diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 3319552d..8f007615 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -1,43 +1,112 @@ -resource "aws_sns_topic" "cost_anomaly_alerts" { - name = "cost-anomaly-alerts-topic" +data "aws_caller_identity" "current" {} + +resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { + name = "BCDA Account Monitor" + monitor_type = "DIMENSIONAL" + monitor_dimension = "SERVICE" } -resource "aws_sns_topic_policy" "cost_anomaly_topic_policy" { - arn = aws_sns_topic.cost_anomaly_alerts.arn - policy = data.aws_iam_policy_document.cost_anomaly_topic_access.json +resource "aws_sns_topic" "cost_anomaly_updates" { + name = "cost-anomaly-updates" + kms_master_key_id = "alias/bcda-${var.env}" } -data "aws_iam_policy_document" "cost_anomaly_topic_access" { +data "aws_iam_policy_document" "sns_topic_policy" { + policy_id = "__cost_anomaly_sns_topic_policy" + statement { + sid = "AWSAnomalyDetectionSNSPublishingPermissions" + + actions = [ + "SNS:Publish" + ] + effect = "Allow" + principals { type = "Service" - identifiers = ["ce.amazonaws.com"] + identifiers = ["costalerts.amazonaws.com"] + } + + resources = [ + aws_sns_topic.cost_anomaly_updates.arn + ] + } + + statement { + sid = "__cost_anomaly_sns" + + actions = [ + "SNS:Subscribe", + "SNS:SetTopicAttributes", + "SNS:RemovePermission", + "SNS:Receive", + "SNS:Publish", + "SNS:ListSubscriptionsByTopic", + "SNS:GetTopicAttributes", + "SNS:DeleteTopic", + "SNS:AddPermission" + ] + + condition { + test = "StringEquals" + variable = "AWS:SourceOwner" + + values = [ + data.aws_caller_identity.current.account_id + ] } - actions = ["sns:Publish"] - resources = [aws_sns_topic.cost_anomaly_alerts.arn] + + effect = "Allow" + + principals { + type = "AWS" + identifiers = ["*"] + } + + resources = [ + aws_sns_topic.cost_anomaly_updates.arn + ] } } -resource "aws_ce_anomaly_monitor" "all_services_monitor" { - name = "all-services-cost-monitor" - monitor_type = "DIMENSIONAL" - monitor_dimension = "SERVICE" +resource "aws_sns_topic_policy" "default" { + arn = aws_sns_topic.cost_anomaly_updates.arn + + policy = data.aws_iam_policy_document.sns_topic_policy.json } -resource "aws_ce_anomaly_subscription" "cost_anomaly_subscription" { - name = "cost-anomaly-alerts-subscription" - frequency = "DAILY" - monitor_arn_list = [aws_ce_anomaly_monitor.all_services_monitor.arn] +resource "aws_ce_anomaly_subscription" "realtime_subscription" { + name = "realtime_cost_anomaly_subscription" + frequency = "IMMEDIATE" + + monitor_arn_list = [ + aws_ce_anomaly_monitor.BCDA_Account_Monitor.arn + ] + subscriber { type = "SNS" - address = aws_sns_topic.cost_anomaly_alerts.arn + address = aws_sns_topic.cost_anomaly_updates.arn } + + depends_on = [ + aws_sns_topic_policy.default + ] + threshold_expression { - dimension { - key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" - match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["150"] + or { + dimension { + key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" + match_options = ["GREATER_THAN_OR_EQUAL"] + values = ["150"] + } + } + or { + dimension { + key = "ANOMALY_TOTAL_IMPACT_PERCENTAGE" + match_options = ["GREATER_THAN_OR_EQUAL"] + values = ["30"] + } } } -} \ No newline at end of file +} diff --git a/terraform/services/cost-anomaly/terraform b/terraform/services/cost-anomaly/terraform deleted file mode 100644 index 7b7e43ad..00000000 --- a/terraform/services/cost-anomaly/terraform +++ /dev/null @@ -1,11 +0,0 @@ -terraform { - backend "s3" { - key = "cost-anomaly/terraform.tfstate" - } -} - -provider "aws" { - default_tags { - tags = module.standards.default_tags - } -} \ No newline at end of file diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf new file mode 100644 index 00000000..1706ed22 --- /dev/null +++ b/terraform/services/cost-anomaly/terraform.tf @@ -0,0 +1,17 @@ +terraform { + backend "s3" { + key = "cost-anomaly/terraform.tfstate" + } +} + +provider "aws" { + default_tags { + tags = { + business = "oeda" + code = "https://github.com/CMSgov/cdap/tree/main/terraform/services/cost-anomaly" + component = "cost-anomaly" + environment = var.env + terraform = true + } + } +} \ No newline at end of file diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index 1e98ef7a..a672aa5c 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -1,14 +1,5 @@ -variable "app" { - description = "The application name (ab2d, bcda, dpc, cdap)" - type = string - validation { - condition = contains(["ab2d", "bcda", "dpc", "cdap"], var.app) - error_message = "Valid value for app is ab2d, bcda, dpc, or cdap." - } -} - variable "env" { - description = "The application environment (dev, test, mgmt, sbx, sandbox, prod)" + description = "The application environment (dev, test, mgmt, sandbox, prod)" type = string validation { condition = contains(["dev", "test", "mgmt", "sandbox", "prod"], var.env) From 12e67cb0a65fe63e550b5cb2b960ab03537dadf2 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 12:22:19 -0600 Subject: [PATCH 05/70] formatting --- .github/workflows/tf-cost-anomaly.yml | 12 ++++++------ terraform/services/cost-anomaly/main.tf | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 120b27de..473db8c6 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -18,7 +18,7 @@ jobs: runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} steps: - uses: actions/checkout@v4 - - uses: ./actions/setup-tfenv-terraform.tf + - uses: ./actions/setup-tfenv-terraform - run: terraform.tf fmt -check -diff -recursive terraform.tf/services/cost-anomaly plan-apply: @@ -29,7 +29,7 @@ jobs: runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} defaults: run: - working-directory: ./terraform.tf/services/cost-anomaly + working-directory: ./terraform/services/cost-anomaly strategy: fail-fast: false matrix: @@ -40,15 +40,15 @@ jobs: env: mgmt steps: - uses: actions/checkout@v4 - - uses: ./actions/setup-tfenv-terraform.tf + - uses: ./actions/setup-tfenv-terraform - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} - - run: terraform.tf init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend - - run: terraform.tf plan -out=tf.plan + - run: terraform init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend + - run: terraform plan -out=tf.plan env: TF_VAR_app: ${{ matrix.app }} TF_VAR_env: ${{ matrix.env }} - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') - run: terraform.tf apply -auto-approve tf.plan + run: terraform apply -auto-approve tf.plan diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 8f007615..95e0b10c 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -1,13 +1,13 @@ data "aws_caller_identity" "current" {} resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { - name = "BCDA Account Monitor" + name = "BCDA Account Monitor" monitor_type = "DIMENSIONAL" monitor_dimension = "SERVICE" } resource "aws_sns_topic" "cost_anomaly_updates" { - name = "cost-anomaly-updates" + name = "cost-anomaly-updates" kms_master_key_id = "alias/bcda-${var.env}" } From 08a73fe4b025109fac57c756597689e3b0578c88 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 12:25:44 -0600 Subject: [PATCH 06/70] formatting --- .github/workflows/tf-cost-anomaly.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 473db8c6..77951237 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -5,13 +5,13 @@ on: push: paths: - .github/workflows/tf-cost-anomaly.yml - - terraform.tf/services/cost-anomaly/** + - terraform/services/cost-anomaly/** workflow_dispatch: inputs: apply: required: false type: boolean - description: "Apply the terraform.tf?" + description: "Apply the terraform?" jobs: check-fmt: @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./actions/setup-tfenv-terraform - - run: terraform.tf fmt -check -diff -recursive terraform.tf/services/cost-anomaly + - run: terraform fmt -check -diff -recursive terraform/services/cost-anomaly plan-apply: needs: check-fmt From 0ef1ac32671ddcfd57a83f4d8dd200142549ba81 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 15:09:21 -0600 Subject: [PATCH 07/70] implementing alarm-to-slack --- terraform/services/cost-anomaly/main.tf | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 95e0b10c..a0b1f7c5 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -110,3 +110,31 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { } } } + + +module "sns_to_slack_function" { + source = "../../modules/function" + + app = "cdap" + env = var.env + + name = "Cost Anomaly Alert" + description = "Listens for Cost Anomaly Alerts and forwards to Slack" + + handler = "lambda_function.lambda_handler" + runtime = "python3.13" + + environment_variables = { + + IGNORE_OK = true + } +} + +module "sns_to_slack_queue" { + source = "../../modules/queue" + + name = "cost-anomaly-alert-queue" + sns_topic_arn = "aws_sns_topic.cost_anomaly_updates.arn" + + function_name = module.sns_to_slack_function.name +} From 945a6bd8e6a4021b07c6da13809cb066e8ce6fa3 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 15:30:39 -0600 Subject: [PATCH 08/70] formatting --- terraform/services/cost-anomaly/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index a0b1f7c5..26fa2aa9 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -133,7 +133,7 @@ module "sns_to_slack_function" { module "sns_to_slack_queue" { source = "../../modules/queue" - name = "cost-anomaly-alert-queue" + name = "cost-anomaly-alert-queue" sns_topic_arn = "aws_sns_topic.cost_anomaly_updates.arn" function_name = module.sns_to_slack_function.name From e44bc9dd2e8163f233e0b16845d0316f8e450368 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 15:37:51 -0600 Subject: [PATCH 09/70] added cdap to function apps --- terraform/modules/function/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/modules/function/variables.tf b/terraform/modules/function/variables.tf index 728ea005..5f4951af 100644 --- a/terraform/modules/function/variables.tf +++ b/terraform/modules/function/variables.tf @@ -2,8 +2,8 @@ variable "app" { description = "The application name (ab2d, bcda, dpc)" type = string validation { - condition = contains(["ab2d", "bcda", "dpc"], var.app) - error_message = "Valid value for app is ab2d, bcda, or dpc." + condition = contains(["ab2d", "bcda", "dpc" ,"cdap"], var.app) + error_message = "Valid value for app is ab2d, bcda, dpc or cdap." } } From a4e330c3029fddfd17d25584d2244d9457c3f80e Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 25 Aug 2025 21:07:59 -0600 Subject: [PATCH 10/70] Changed threshold settings per DF --- terraform/services/cost-anomaly/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 26fa2aa9..a997e170 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -98,14 +98,14 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { dimension { key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["150"] + values = ["100"] } } or { dimension { key = "ANOMALY_TOTAL_IMPACT_PERCENTAGE" match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["30"] + values = ["20"] } } } From e269c583d0a3e57943c8857139966d0a7af7ed20 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 2 Sep 2025 07:22:03 -0600 Subject: [PATCH 11/70] Update terraform/services/cost-anomaly/main.tf Co-authored-by: jdettmannnava <145699825+jdettmannnava@users.noreply.github.com> --- terraform/services/cost-anomaly/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index a997e170..286e123a 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -8,7 +8,7 @@ resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { resource "aws_sns_topic" "cost_anomaly_updates" { name = "cost-anomaly-updates" - kms_master_key_id = "alias/bcda-${var.env}" + kms_master_key_id = "alias/${var.app}-${var.env}" } data "aws_iam_policy_document" "sns_topic_policy" { From 5a76f504dd55707ed18c38a398eb834857d7a6e3 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Wed, 3 Sep 2025 08:43:05 -0600 Subject: [PATCH 12/70] adding cdap repo --- terraform/modules/function/main.tf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/terraform/modules/function/main.tf b/terraform/modules/function/main.tf index c76c7cd8..2c2205d2 100644 --- a/terraform/modules/function/main.tf +++ b/terraform/modules/function/main.tf @@ -7,6 +7,9 @@ locals { dpc = [ "repo:CMSgov/dpc-app:*", ] + cdap = [ + "repo:CMSgov/cdap:*", + ] } } From 4dd8ca24b2de586d988400d18e7f3018c29d7bb6 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Wed, 3 Sep 2025 11:55:59 -0600 Subject: [PATCH 13/70] implementing the topic module in cost anomaly alert # Conflicts: # terraform/modules/bucket/main.tf --- terraform/modules/topic/main.tf | 8 +- terraform/modules/topic/variables.tf | 10 ++- terraform/services/cost-anomaly/main.tf | 79 ++------------------ terraform/services/cost-anomaly/variables.tf | 2 + 4 files changed, 20 insertions(+), 79 deletions(-) diff --git a/terraform/modules/topic/main.tf b/terraform/modules/topic/main.tf index 8333708b..5862c845 100644 --- a/terraform/modules/topic/main.tf +++ b/terraform/modules/topic/main.tf @@ -2,7 +2,7 @@ module "topic_key" { source = "../key" name = "${var.name}-topic" description = "For ${var.name} SNS topic" - buckets = var.buckets + buckets = var.publisher_arns } resource "aws_sns_topic" "this" { @@ -12,11 +12,11 @@ resource "aws_sns_topic" "this" { } data "aws_iam_policy_document" "topic" { - count = length(var.buckets) > 0 ? 1 : 0 + count = length(var.publisher_arns) > 0 ? 1 : 0 statement { principals { type = "Service" - identifiers = ["s3.amazonaws.com"] + identifiers = var.policy_service_identifiers } actions = ["SNS:Publish"] @@ -25,7 +25,7 @@ data "aws_iam_policy_document" "topic" { condition { test = "ArnLike" variable = "aws:SourceArn" - values = var.buckets + values = var.publisher_arns } } } diff --git a/terraform/modules/topic/variables.tf b/terraform/modules/topic/variables.tf index ae21fa24..4f921ce0 100644 --- a/terraform/modules/topic/variables.tf +++ b/terraform/modules/topic/variables.tf @@ -3,8 +3,14 @@ variable "name" { type = string } -variable "buckets" { - description = "ARNs for S3 buckets that need to publish event notifications to the SNS topic" +variable "publisher_arns" { + description = "ARNs for S3 buckets or services that need to publish event notifications to the SNS topic" type = list(any) default = [] } + +variable "policy_service_identifiers" { + description = "Identifier(s) for service principals for use in policy. Example: s3.amazonaws.com" + type = list(any) + default = ["s3.amazonaws.com"] +} diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 286e123a..ef6baec7 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -6,75 +6,12 @@ resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { monitor_dimension = "SERVICE" } -resource "aws_sns_topic" "cost_anomaly_updates" { - name = "cost-anomaly-updates" - kms_master_key_id = "alias/${var.app}-${var.env}" +module "cost_anomaly_sns" { + source = "../../modules/topic" + name = "cost-anomaly-updates" + policy_service_identifiers = ["costalerts.amazonaws.com"] } -data "aws_iam_policy_document" "sns_topic_policy" { - policy_id = "__cost_anomaly_sns_topic_policy" - - statement { - sid = "AWSAnomalyDetectionSNSPublishingPermissions" - - actions = [ - "SNS:Publish" - ] - - effect = "Allow" - - principals { - type = "Service" - identifiers = ["costalerts.amazonaws.com"] - } - - resources = [ - aws_sns_topic.cost_anomaly_updates.arn - ] - } - - statement { - sid = "__cost_anomaly_sns" - - actions = [ - "SNS:Subscribe", - "SNS:SetTopicAttributes", - "SNS:RemovePermission", - "SNS:Receive", - "SNS:Publish", - "SNS:ListSubscriptionsByTopic", - "SNS:GetTopicAttributes", - "SNS:DeleteTopic", - "SNS:AddPermission" - ] - - condition { - test = "StringEquals" - variable = "AWS:SourceOwner" - - values = [ - data.aws_caller_identity.current.account_id - ] - } - - effect = "Allow" - - principals { - type = "AWS" - identifiers = ["*"] - } - - resources = [ - aws_sns_topic.cost_anomaly_updates.arn - ] - } -} - -resource "aws_sns_topic_policy" "default" { - arn = aws_sns_topic.cost_anomaly_updates.arn - - policy = data.aws_iam_policy_document.sns_topic_policy.json -} resource "aws_ce_anomaly_subscription" "realtime_subscription" { name = "realtime_cost_anomaly_subscription" @@ -86,13 +23,9 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { subscriber { type = "SNS" - address = aws_sns_topic.cost_anomaly_updates.arn + address = module.cost_anomaly_sns.arn } - depends_on = [ - aws_sns_topic_policy.default - ] - threshold_expression { or { dimension { @@ -134,7 +67,7 @@ module "sns_to_slack_queue" { source = "../../modules/queue" name = "cost-anomaly-alert-queue" - sns_topic_arn = "aws_sns_topic.cost_anomaly_updates.arn" + sns_topic_arn = module.cost_anomaly_sns.arn function_name = module.sns_to_slack_function.name } diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index a672aa5c..a471dafd 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -6,3 +6,5 @@ variable "env" { error_message = "Valid value for env is dev, test, mgmt, sandbox, or prod." } } + + From a1907bd5a67f081794022b8a1b7a55901c34d993 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 19 Sep 2025 14:01:45 -0600 Subject: [PATCH 14/70] Added lambda code --- .../lambda_src/lambda_function.py | 125 ++++++++++++++++++ terraform/services/cost-anomaly/main.tf | 47 ++++--- 2 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 terraform/services/cost-anomaly/lambda_src/lambda_function.py diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py new file mode 100644 index 00000000..8417db0e --- /dev/null +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -0,0 +1,125 @@ +""" +Receives messages from Cost Anomaly alarms via SQS subscription to SNS. +Forwards the alarms to Slack, with an emoji that says good or bad. +""" + +from datetime import datetime, timezone +import json +import os +from urllib import request +from urllib.error import URLError +import boto3 +from botocore.exceptions import ClientError + +class Field: + def __init__(self, type, text, emoji): + #type: plain_text + self.type = type + #text: text to be displayed + self.text = text + #emoji: boolean + self.emoji = emoji + +class Block: + #def __init__(self, type, text=None, fields=None): + def __init__(self, type, **kwargs): + #type: section + self.type = type + #fields: an array of fields in the section + if kwargs.get("fields"): + self.fields = kwargs.get("fields") + if kwargs.get("text"): + self.text = kwargs.get("text") + +class Text: + #def __init__(self, type, text, emoji): + def __init__(self, type, text, **kwargs): + #type: plain_text + self.type = type + #text: text to be displayed + self.text = text + #emoji: boolean + if kwargs.get("emoji"): + self.emoji = kwargs.get("emoji") + +def is_ignore_ok(): + """ + Returns the current value of the IGNORE_OK environment variable. + This allows tests to patch the environment dynamically. + """ + return os.environ.get('IGNORE_OK', 'false').lower() == 'true' + +def lambda_handler(event, context): + + anomalyEvent = json.loads(event["Records"][0]["Sns"]["Message"]) + message_id = anomalyEvent["messageId"] + + #Total Cost of the Anomaly + totalcostImpact = anomalyEvent["impact"]["totalImpact"] + + #Anomaly Detection Interval + anomalyStartDate = anomalyEvent["anomalyStartDate"] + anomalyEndDate = anomalyEvent["anomalyEndDate"] + + #anomalyDetailsLink + anomalyDetailsLink = anomalyEvent["anomalyDetailsLink"] + + #Blocks is the main array that holds the full message. + blocks = [] + + headerText = Text("plain_text", ":warning: Cost Anomaly Detected ", emoji = True) + totalAnomalyCostText = Text("mrkdwn", "*Total Anomaly Cost*: $" + str(totalcostImpact)) + rootCausesHeaderText = Text("mrkdwn", "*Root Causes* :mag:") + anomalyStartDateText = Text("mrkdwn", "*Anomaly Start Date*: " + str(anomalyStartDate)) + anomalyEndDateText = Text("mrkdwn", "*Anomaly End Date*: " + str(anomalyEndDate)) + anomalyDetailsLinkText = Text("mrkdwn", "*Anomaly Details Link*: " + str(anomalyDetailsLink)) + + blocks.append(Block("header", text=headerText.__dict__)) + blocks.append(Block("section", text=totalAnomalyCostText.__dict__)) + blocks.append(Block("section", text=anomalyStartDateText.__dict__)) + blocks.append(Block("section", text=anomalyEndDateText.__dict__)) + blocks.append(Block("section", text=anomalyDetailsLinkText.__dict__)) + blocks.append(Block("section", text=rootCausesHeaderText.__dict__)) + + for rootCause in anomalyEvent["rootCauses"]: + fields = [] + for rootCauseAttribute in rootCause: + if feature_flag_displayAccountName == True: + if rootCauseAttribute == "linkedAccount": + accountName = get_aws_account_name(rootCause[rootCauseAttribute]) + fields.append(Field("plain_text", "accountName" + " : " + accountName, False)) + fields.append(Field("plain_text", rootCauseAttribute + " : " + rootCause[rootCauseAttribute], False)) + blocks.append(Block("section", fields = [ob.__dict__ for ob in fields])) + + message_json = blocks= json.dumps([ob.__dict__ for ob in blocks]) + + webhook = 'https://hooks.slack.com/services/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' + + send_message_to_slack(webhook, message_json, message_id) + +def send_message_to_slack(webhook, message_json, message_id): + """ + Calls the Slack webhook with the formatted message. Returns success status. + """ + if not webhook: + log({'msg': 'Unable to send to Slack as webhook URL is not set', + 'messageId': message_id}) + return False + jsondata = message_json + jsondataasbytes = jsondata.encode('utf-8') + req = request.Request(webhook) + req.add_header('Content-Type', 'application/json; charset=utf-8') + req.add_header('Content-Length', str(len(jsondataasbytes))) + try: + with request.urlopen(req, jsondataasbytes) as resp: + if resp.status == 200: + log({'msg': 'Successfully sent message to Slack', + 'messageId': message_id}) + return True + log({'msg': f'Unsuccessful attempt to send message to Slack ({resp.status})', + 'messageId': message_id}) + return False + except URLError as e: + log({'msg': f'Unsuccessful attempt to send message to Slack ({e.reason})', + 'messageId': message_id}) + return False diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index ef6baec7..b1c9e118 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -1,20 +1,20 @@ data "aws_caller_identity" "current" {} - +locals { + function_name = "cost_anomaly_alert" +} resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { name = "BCDA Account Monitor" monitor_type = "DIMENSIONAL" monitor_dimension = "SERVICE" } -module "cost_anomaly_sns" { - source = "../../modules/topic" - name = "cost-anomaly-updates" - policy_service_identifiers = ["costalerts.amazonaws.com"] +resource "aws_sns_topic" "cost_anomaly_sns" { + name = "cost-anomaly-topic" + kms_master_key_id = "alias/bcda-${var.env}" } - resource "aws_ce_anomaly_subscription" "realtime_subscription" { - name = "realtime_cost_anomaly_subscription" + name = "cost_anomaly_subscription" frequency = "IMMEDIATE" monitor_arn_list = [ @@ -23,7 +23,7 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { subscriber { type = "SNS" - address = module.cost_anomaly_sns.arn + address = aws_sns_topic.cost_anomaly_sns.arn } threshold_expression { @@ -44,30 +44,33 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { } } +module "sns_to_slack_queue" { + source = "../../modules/queue" + + name = "cost-anomaly-alert-queue" + sns_topic_arn = aws_sns_topic.cost_anomaly_sns.arn + + app = "bcda" + env = var.env + function_name = local.function_name +} -module "sns_to_slack_function" { +module "cost_anomaly_function" { source = "../../modules/function" - app = "cdap" + app = "bcda" env = var.env - name = "Cost Anomaly Alert" - description = "Listens for Cost Anomaly Alerts and forwards to Slack" + name = local.function_name + description = "Forwards cost anomaly alerts to Slack channel #dasg-metrics-and-insights." handler = "lambda_function.lambda_handler" runtime = "python3.13" - environment_variables = { + memory_size = 2048 + environment_variables = { + ENV = var.env IGNORE_OK = true } } - -module "sns_to_slack_queue" { - source = "../../modules/queue" - - name = "cost-anomaly-alert-queue" - sns_topic_arn = module.cost_anomaly_sns.arn - - function_name = module.sns_to_slack_function.name -} From 4be6eaa8cd1d8146a9cc0766300c81498e383a37 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 19 Sep 2025 15:26:25 -0600 Subject: [PATCH 15/70] Added lambda code --- terraform/services/cost-anomaly/main.tf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index b1c9e118..130fdcf2 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -1,6 +1,6 @@ data "aws_caller_identity" "current" {} locals { - function_name = "cost_anomaly_alert" + function_name = "cost-anomaly-alert" } resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { name = "BCDA Account Monitor" @@ -53,6 +53,8 @@ module "sns_to_slack_queue" { app = "bcda" env = var.env function_name = local.function_name + + depends_on = ["module.cost_anomaly_function"] } module "cost_anomaly_function" { From 6002b24651b95a5c583cb1be4000de81a7abf747 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 22 Sep 2025 10:09:52 -0600 Subject: [PATCH 16/70] testing feedback --- .../lambda_src/lambda_function.py | 20 ++++++- terraform/services/cost-anomaly/main.tf | 53 ++++++++++++++----- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py index 8417db0e..07720849 100644 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -52,7 +52,9 @@ def is_ignore_ok(): def lambda_handler(event, context): anomalyEvent = json.loads(event["Records"][0]["Sns"]["Message"]) - message_id = anomalyEvent["messageId"] + print(anomalyEvent) + + message_id = 4 #Total Cost of the Anomaly totalcostImpact = anomalyEvent["impact"]["totalImpact"] @@ -84,7 +86,6 @@ def lambda_handler(event, context): for rootCause in anomalyEvent["rootCauses"]: fields = [] for rootCauseAttribute in rootCause: - if feature_flag_displayAccountName == True: if rootCauseAttribute == "linkedAccount": accountName = get_aws_account_name(rootCause[rootCauseAttribute]) fields.append(Field("plain_text", "accountName" + " : " + accountName, False)) @@ -123,3 +124,18 @@ def send_message_to_slack(webhook, message_json, message_id): log({'msg': f'Unsuccessful attempt to send message to Slack ({e.reason})', 'messageId': message_id}) return False +def get_aws_account_name(account_id): + #Function is used to fetch account name corresponding to an account number. The account name is used to display a meaningful name in the Slack notification. For this function to operate, proper IAM permission should be granted to the Lambda function role. + print("Fetching Account Name corresponding to accountId:" + account_id) + + #Initialise Organisations + client = boto3.client('organizations') + + #Call describe_account in order to return the account_id corresponding to the account_number. + response = client.describe_account(AccountId=account_id) + + accountName = response["Account"]["Name"] + print("Fetching Account Name complete. Account Name:" + accountName) + + #Return the Account Name corresponding the Input Account ID. + return response["Account"]["Name"] \ No newline at end of file diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 130fdcf2..878a3e4a 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -54,25 +54,54 @@ module "sns_to_slack_queue" { env = var.env function_name = local.function_name - depends_on = ["module.cost_anomaly_function"] } -module "cost_anomaly_function" { - source = "../../modules/function" +# IAM role for Lambda execution +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" - app = "bcda" - env = var.env + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} - name = local.function_name - description = "Forwards cost anomaly alerts to Slack channel #dasg-metrics-and-insights." +resource "aws_iam_role" "cost_anomaly_alert" { + name = "lambda_execution_role" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +# Package the Lambda function code +data "archive_file" "cost_anomaly_alert" { + type = "zip" + source_file = "lambda_src/lambda_function.py" + output_path = "lambda/cost_anomaly_function.zip" +} + +# Lambda function +resource "aws_lambda_function" "cost_anomaly_alert" { + filename = data.archive_file.cost_anomaly_alert.output_path + function_name = "cost_anomaly_alert_lambda_function" + role = aws_iam_role.cost_anomaly_alert.arn + handler = "lambda_function.lambda_handler" + source_code_hash = data.archive_file.cost_anomaly_alert.output_base64sha256 - handler = "lambda_function.lambda_handler" runtime = "python3.13" - memory_size = 2048 + environment { + variables = { + ENVIRONMENT = var.env + IGNORE_OK = "false" + + } + } - environment_variables = { - ENV = var.env - IGNORE_OK = true + tags = { + Environment = var.env + Application = "cost_anomaly_alert" } } From 3951b1c41636e2cd6930a0c0011590c7539ceb31 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 22 Sep 2025 10:17:20 -0600 Subject: [PATCH 17/70] correct indent --- .../lambda_src/lambda_function.py | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py index 07720849..73d3dc17 100644 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -12,35 +12,35 @@ from botocore.exceptions import ClientError class Field: - def __init__(self, type, text, emoji): - #type: plain_text - self.type = type - #text: text to be displayed - self.text = text - #emoji: boolean - self.emoji = emoji + def __init__(self, type, text, emoji): + #type: plain_text + self.type = type + #text: text to be displayed + self.text = text + #emoji: boolean + self.emoji = emoji class Block: - #def __init__(self, type, text=None, fields=None): - def __init__(self, type, **kwargs): - #type: section - self.type = type - #fields: an array of fields in the section - if kwargs.get("fields"): - self.fields = kwargs.get("fields") - if kwargs.get("text"): - self.text = kwargs.get("text") + #def __init__(self, type, text=None, fields=None): + def __init__(self, type, **kwargs): + #type: section + self.type = type + #fields: an array of fields in the section + if kwargs.get("fields"): + self.fields = kwargs.get("fields") + if kwargs.get("text"): + self.text = kwargs.get("text") class Text: - #def __init__(self, type, text, emoji): - def __init__(self, type, text, **kwargs): - #type: plain_text - self.type = type - #text: text to be displayed - self.text = text - #emoji: boolean - if kwargs.get("emoji"): - self.emoji = kwargs.get("emoji") + #def __init__(self, type, text, emoji): + def __init__(self, type, text, **kwargs): + #type: plain_text + self.type = type + #text: text to be displayed + self.text = text + #emoji: boolean + if kwargs.get("emoji"): + self.emoji = kwargs.get("emoji") def is_ignore_ok(): """ @@ -84,13 +84,13 @@ def lambda_handler(event, context): blocks.append(Block("section", text=rootCausesHeaderText.__dict__)) for rootCause in anomalyEvent["rootCauses"]: - fields = [] - for rootCauseAttribute in rootCause: - if rootCauseAttribute == "linkedAccount": - accountName = get_aws_account_name(rootCause[rootCauseAttribute]) - fields.append(Field("plain_text", "accountName" + " : " + accountName, False)) - fields.append(Field("plain_text", rootCauseAttribute + " : " + rootCause[rootCauseAttribute], False)) - blocks.append(Block("section", fields = [ob.__dict__ for ob in fields])) + fields = [] + for rootCauseAttribute in rootCause: + if rootCauseAttribute == "linkedAccount": + accountName = get_aws_account_name(rootCause[rootCauseAttribute]) + fields.append(Field("plain_text", "accountName" + " : " + accountName, False)) + fields.append(Field("plain_text", rootCauseAttribute + " : " + rootCause[rootCauseAttribute], False)) + blocks.append(Block("section", fields = [ob.__dict__ for ob in fields])) message_json = blocks= json.dumps([ob.__dict__ for ob in blocks]) From 8682d57b54fa832e1bfbdfdddc6146d3a2c63540 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 22 Sep 2025 10:43:20 -0600 Subject: [PATCH 18/70] test --- terraform/services/cost-anomaly/lambda_src/lambda_function.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py index 73d3dc17..9b1162d3 100644 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -51,6 +51,7 @@ def is_ignore_ok(): def lambda_handler(event, context): + print("event:" + event) anomalyEvent = json.loads(event["Records"][0]["Sns"]["Message"]) print(anomalyEvent) From 66f5156a58c77b67840725e12cdde8825b7d1252 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 22 Sep 2025 10:48:36 -0600 Subject: [PATCH 19/70] test --- .../cost-anomaly/lambda_src/lambda_function.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py index 9b1162d3..f73ea897 100644 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -51,9 +51,13 @@ def is_ignore_ok(): def lambda_handler(event, context): - print("event:" + event) - anomalyEvent = json.loads(event["Records"][0]["Sns"]["Message"]) - print(anomalyEvent) + print(f"Received event: {json.dumps(event)}") + print(f"Function name: {context.function_name}") + print(f"Remaining time: {context.get_remaining_time_in_millis()} ms") + # print("event:" + event) + # anomalyEvent = json.loads(event["Records"][0]["Sns"]["Message"]) + anomalyEvent = json.dumps(event) + # print(anomalyEvent) message_id = 4 From 74616f90eff3ae72f38d0d2c8a096cbf9ad1bd51 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 23 Sep 2025 08:32:34 -0600 Subject: [PATCH 20/70] parameterized webhook --- .../lambda_src/lambda_function.py | 35 +++++++++++++++++-- terraform/services/cost-anomaly/main.tf | 10 ++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py index f73ea897..58d7c8b3 100644 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -11,6 +11,8 @@ import boto3 from botocore.exceptions import ClientError +ssm_parameter_cache = {} + class Field: def __init__(self, type, text, emoji): #type: plain_text @@ -41,6 +43,29 @@ def __init__(self, type, text, **kwargs): #emoji: boolean if kwargs.get("emoji"): self.emoji = kwargs.get("emoji") +def get_ssm_client(): + """ + Lazy initialization of boto3 SSM client. + Prevents global instantiation to avoid NoRegionError during tests. + """ + return boto3.client('ssm') + +def get_ssm_parameter(name): + """ + Retrieves an SSM parameter and caches the value to prevent duplicate API calls. + Caches None if the parameter is not found or an error occurs. + """ + if name not in ssm_parameter_cache: + try: + ssm_client = get_ssm_client() + response = ssm_client.get_parameter(Name=name, WithDecryption=True) + value = response['Parameter']['Value'] + ssm_parameter_cache[name] = value + except ClientError as e: + log({'msg': f'Error getting SSM parameter {name}: {e}'}) + ssm_parameter_cache[name] = None + + return ssm_parameter_cache[name] def is_ignore_ok(): """ @@ -99,7 +124,7 @@ def lambda_handler(event, context): message_json = blocks= json.dumps([ob.__dict__ for ob in blocks]) - webhook = 'https://hooks.slack.com/services/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' + webhook = get_ssm_parameter(f'/cost_anomaly/lambda/slack_webhook_url') send_message_to_slack(webhook, message_json, message_id) @@ -143,4 +168,10 @@ def get_aws_account_name(account_id): print("Fetching Account Name complete. Account Name:" + accountName) #Return the Account Name corresponding the Input Account ID. - return response["Account"]["Name"] \ No newline at end of file + return response["Account"]["Name"] +def log(data): + """ + Enriches the log message with the current time and prints it to standard out. + """ + data['time'] = datetime.now().astimezone(tz=timezone.utc).isoformat() + print(json.dumps(data)) \ No newline at end of file diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 878a3e4a..12dc1f51 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -1,6 +1,7 @@ data "aws_caller_identity" "current" {} locals { function_name = "cost-anomaly-alert" + ssm_parameter = "/cost_anomaly/lambda/slack_webhook_url" } resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { name = "BCDA Account Monitor" @@ -8,6 +9,11 @@ resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { monitor_dimension = "SERVICE" } +resource "aws_ssm_parameter" "webhook" { + name = local.ssm_parameter + type = "String" +} + resource "aws_sns_topic" "cost_anomaly_sns" { name = "cost-anomaly-topic" kms_master_key_id = "alias/bcda-${var.env}" @@ -31,14 +37,14 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { dimension { key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["100"] + values = ["20"] } } or { dimension { key = "ANOMALY_TOTAL_IMPACT_PERCENTAGE" match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["20"] + values = ["5"] } } } From 711e343ff0c9ab9c636369613b146827244038d1 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 23 Sep 2025 09:29:16 -0600 Subject: [PATCH 21/70] inject param name as env variable to lambda --- terraform/services/cost-anomaly/lambda_src/lambda_function.py | 4 ++-- terraform/services/cost-anomaly/main.tf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py index 58d7c8b3..637ae55f 100644 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ b/terraform/services/cost-anomaly/lambda_src/lambda_function.py @@ -122,9 +122,9 @@ def lambda_handler(event, context): fields.append(Field("plain_text", rootCauseAttribute + " : " + rootCause[rootCauseAttribute], False)) blocks.append(Block("section", fields = [ob.__dict__ for ob in fields])) - message_json = blocks= json.dumps([ob.__dict__ for ob in blocks]) + message_json = json.dumps([ob.__dict__ for ob in blocks]) - webhook = get_ssm_parameter(f'/cost_anomaly/lambda/slack_webhook_url') + webhook = get_ssm_parameter(os.environ.get('WEBHOOK_PARAM')) send_message_to_slack(webhook, message_json, message_id) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 12dc1f51..121f12e7 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -102,7 +102,7 @@ resource "aws_lambda_function" "cost_anomaly_alert" { variables = { ENVIRONMENT = var.env IGNORE_OK = "false" - + WEBHOOK_PARAM = local.ssm_parameter } } From 4bb9dab5d47bb9ef7e0482c863191fd264c1f028 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 23 Sep 2025 09:36:48 -0600 Subject: [PATCH 22/70] correct workflow for tofu --- .github/workflows/tf-cost-anomaly.yml | 12 ++++++------ terraform/services/cost-anomaly/main.tf | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 77951237..45197358 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -18,8 +18,9 @@ jobs: runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} steps: - uses: actions/checkout@v4 - - uses: ./actions/setup-tfenv-terraform - - run: terraform fmt -check -diff -recursive terraform/services/cost-anomaly + - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 + - uses: cmsgov/cdap/actions/setup-tenv@8343fb96563ce4b74c4dececee9b268f42bd4a40 + - run: tofu fmt -check -diff -recursive . plan-apply: needs: check-fmt @@ -40,15 +41,14 @@ jobs: env: mgmt steps: - uses: actions/checkout@v4 - - uses: ./actions/setup-tfenv-terraform - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} - - run: terraform init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend - - run: terraform plan -out=tf.plan + - run: tofu init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend + - run: tofu plan -out=tf.plan env: TF_VAR_app: ${{ matrix.app }} TF_VAR_env: ${{ matrix.env }} - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') - run: terraform apply -auto-approve tf.plan + run: tofu apply -auto-approve tf.plan diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 121f12e7..4968f0d2 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -10,8 +10,8 @@ resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { } resource "aws_ssm_parameter" "webhook" { - name = local.ssm_parameter - type = "String" + name = local.ssm_parameter + type = "String" } resource "aws_sns_topic" "cost_anomaly_sns" { @@ -100,8 +100,8 @@ resource "aws_lambda_function" "cost_anomaly_alert" { environment { variables = { - ENVIRONMENT = var.env - IGNORE_OK = "false" + ENVIRONMENT = var.env + IGNORE_OK = "false" WEBHOOK_PARAM = local.ssm_parameter } } From 402fc1a824de419cd6d3d0eb1273ea3b183d5f1a Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Wed, 24 Sep 2025 13:44:55 -0600 Subject: [PATCH 23/70] remove lambda > work will be in plt-1361 --- .../lambda_src/lambda_function.py | 177 ------------------ terraform/services/cost-anomaly/main.tf | 113 ----------- 2 files changed, 290 deletions(-) delete mode 100644 terraform/services/cost-anomaly/lambda_src/lambda_function.py delete mode 100644 terraform/services/cost-anomaly/main.tf diff --git a/terraform/services/cost-anomaly/lambda_src/lambda_function.py b/terraform/services/cost-anomaly/lambda_src/lambda_function.py deleted file mode 100644 index 637ae55f..00000000 --- a/terraform/services/cost-anomaly/lambda_src/lambda_function.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Receives messages from Cost Anomaly alarms via SQS subscription to SNS. -Forwards the alarms to Slack, with an emoji that says good or bad. -""" - -from datetime import datetime, timezone -import json -import os -from urllib import request -from urllib.error import URLError -import boto3 -from botocore.exceptions import ClientError - -ssm_parameter_cache = {} - -class Field: - def __init__(self, type, text, emoji): - #type: plain_text - self.type = type - #text: text to be displayed - self.text = text - #emoji: boolean - self.emoji = emoji - -class Block: - #def __init__(self, type, text=None, fields=None): - def __init__(self, type, **kwargs): - #type: section - self.type = type - #fields: an array of fields in the section - if kwargs.get("fields"): - self.fields = kwargs.get("fields") - if kwargs.get("text"): - self.text = kwargs.get("text") - -class Text: - #def __init__(self, type, text, emoji): - def __init__(self, type, text, **kwargs): - #type: plain_text - self.type = type - #text: text to be displayed - self.text = text - #emoji: boolean - if kwargs.get("emoji"): - self.emoji = kwargs.get("emoji") -def get_ssm_client(): - """ - Lazy initialization of boto3 SSM client. - Prevents global instantiation to avoid NoRegionError during tests. - """ - return boto3.client('ssm') - -def get_ssm_parameter(name): - """ - Retrieves an SSM parameter and caches the value to prevent duplicate API calls. - Caches None if the parameter is not found or an error occurs. - """ - if name not in ssm_parameter_cache: - try: - ssm_client = get_ssm_client() - response = ssm_client.get_parameter(Name=name, WithDecryption=True) - value = response['Parameter']['Value'] - ssm_parameter_cache[name] = value - except ClientError as e: - log({'msg': f'Error getting SSM parameter {name}: {e}'}) - ssm_parameter_cache[name] = None - - return ssm_parameter_cache[name] - -def is_ignore_ok(): - """ - Returns the current value of the IGNORE_OK environment variable. - This allows tests to patch the environment dynamically. - """ - return os.environ.get('IGNORE_OK', 'false').lower() == 'true' - -def lambda_handler(event, context): - - print(f"Received event: {json.dumps(event)}") - print(f"Function name: {context.function_name}") - print(f"Remaining time: {context.get_remaining_time_in_millis()} ms") - # print("event:" + event) - # anomalyEvent = json.loads(event["Records"][0]["Sns"]["Message"]) - anomalyEvent = json.dumps(event) - # print(anomalyEvent) - - message_id = 4 - - #Total Cost of the Anomaly - totalcostImpact = anomalyEvent["impact"]["totalImpact"] - - #Anomaly Detection Interval - anomalyStartDate = anomalyEvent["anomalyStartDate"] - anomalyEndDate = anomalyEvent["anomalyEndDate"] - - #anomalyDetailsLink - anomalyDetailsLink = anomalyEvent["anomalyDetailsLink"] - - #Blocks is the main array that holds the full message. - blocks = [] - - headerText = Text("plain_text", ":warning: Cost Anomaly Detected ", emoji = True) - totalAnomalyCostText = Text("mrkdwn", "*Total Anomaly Cost*: $" + str(totalcostImpact)) - rootCausesHeaderText = Text("mrkdwn", "*Root Causes* :mag:") - anomalyStartDateText = Text("mrkdwn", "*Anomaly Start Date*: " + str(anomalyStartDate)) - anomalyEndDateText = Text("mrkdwn", "*Anomaly End Date*: " + str(anomalyEndDate)) - anomalyDetailsLinkText = Text("mrkdwn", "*Anomaly Details Link*: " + str(anomalyDetailsLink)) - - blocks.append(Block("header", text=headerText.__dict__)) - blocks.append(Block("section", text=totalAnomalyCostText.__dict__)) - blocks.append(Block("section", text=anomalyStartDateText.__dict__)) - blocks.append(Block("section", text=anomalyEndDateText.__dict__)) - blocks.append(Block("section", text=anomalyDetailsLinkText.__dict__)) - blocks.append(Block("section", text=rootCausesHeaderText.__dict__)) - - for rootCause in anomalyEvent["rootCauses"]: - fields = [] - for rootCauseAttribute in rootCause: - if rootCauseAttribute == "linkedAccount": - accountName = get_aws_account_name(rootCause[rootCauseAttribute]) - fields.append(Field("plain_text", "accountName" + " : " + accountName, False)) - fields.append(Field("plain_text", rootCauseAttribute + " : " + rootCause[rootCauseAttribute], False)) - blocks.append(Block("section", fields = [ob.__dict__ for ob in fields])) - - message_json = json.dumps([ob.__dict__ for ob in blocks]) - - webhook = get_ssm_parameter(os.environ.get('WEBHOOK_PARAM')) - - send_message_to_slack(webhook, message_json, message_id) - -def send_message_to_slack(webhook, message_json, message_id): - """ - Calls the Slack webhook with the formatted message. Returns success status. - """ - if not webhook: - log({'msg': 'Unable to send to Slack as webhook URL is not set', - 'messageId': message_id}) - return False - jsondata = message_json - jsondataasbytes = jsondata.encode('utf-8') - req = request.Request(webhook) - req.add_header('Content-Type', 'application/json; charset=utf-8') - req.add_header('Content-Length', str(len(jsondataasbytes))) - try: - with request.urlopen(req, jsondataasbytes) as resp: - if resp.status == 200: - log({'msg': 'Successfully sent message to Slack', - 'messageId': message_id}) - return True - log({'msg': f'Unsuccessful attempt to send message to Slack ({resp.status})', - 'messageId': message_id}) - return False - except URLError as e: - log({'msg': f'Unsuccessful attempt to send message to Slack ({e.reason})', - 'messageId': message_id}) - return False -def get_aws_account_name(account_id): - #Function is used to fetch account name corresponding to an account number. The account name is used to display a meaningful name in the Slack notification. For this function to operate, proper IAM permission should be granted to the Lambda function role. - print("Fetching Account Name corresponding to accountId:" + account_id) - - #Initialise Organisations - client = boto3.client('organizations') - - #Call describe_account in order to return the account_id corresponding to the account_number. - response = client.describe_account(AccountId=account_id) - - accountName = response["Account"]["Name"] - print("Fetching Account Name complete. Account Name:" + accountName) - - #Return the Account Name corresponding the Input Account ID. - return response["Account"]["Name"] -def log(data): - """ - Enriches the log message with the current time and prints it to standard out. - """ - data['time'] = datetime.now().astimezone(tz=timezone.utc).isoformat() - print(json.dumps(data)) \ No newline at end of file diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf deleted file mode 100644 index 4968f0d2..00000000 --- a/terraform/services/cost-anomaly/main.tf +++ /dev/null @@ -1,113 +0,0 @@ -data "aws_caller_identity" "current" {} -locals { - function_name = "cost-anomaly-alert" - ssm_parameter = "/cost_anomaly/lambda/slack_webhook_url" -} -resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { - name = "BCDA Account Monitor" - monitor_type = "DIMENSIONAL" - monitor_dimension = "SERVICE" -} - -resource "aws_ssm_parameter" "webhook" { - name = local.ssm_parameter - type = "String" -} - -resource "aws_sns_topic" "cost_anomaly_sns" { - name = "cost-anomaly-topic" - kms_master_key_id = "alias/bcda-${var.env}" -} - -resource "aws_ce_anomaly_subscription" "realtime_subscription" { - name = "cost_anomaly_subscription" - frequency = "IMMEDIATE" - - monitor_arn_list = [ - aws_ce_anomaly_monitor.BCDA_Account_Monitor.arn - ] - - subscriber { - type = "SNS" - address = aws_sns_topic.cost_anomaly_sns.arn - } - - threshold_expression { - or { - dimension { - key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" - match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["20"] - } - } - or { - dimension { - key = "ANOMALY_TOTAL_IMPACT_PERCENTAGE" - match_options = ["GREATER_THAN_OR_EQUAL"] - values = ["5"] - } - } - } -} - -module "sns_to_slack_queue" { - source = "../../modules/queue" - - name = "cost-anomaly-alert-queue" - sns_topic_arn = aws_sns_topic.cost_anomaly_sns.arn - - app = "bcda" - env = var.env - function_name = local.function_name - -} - -# IAM role for Lambda execution -data "aws_iam_policy_document" "assume_role" { - statement { - effect = "Allow" - - principals { - type = "Service" - identifiers = ["lambda.amazonaws.com"] - } - - actions = ["sts:AssumeRole"] - } -} - -resource "aws_iam_role" "cost_anomaly_alert" { - name = "lambda_execution_role" - assume_role_policy = data.aws_iam_policy_document.assume_role.json -} - -# Package the Lambda function code -data "archive_file" "cost_anomaly_alert" { - type = "zip" - source_file = "lambda_src/lambda_function.py" - output_path = "lambda/cost_anomaly_function.zip" -} - -# Lambda function -resource "aws_lambda_function" "cost_anomaly_alert" { - filename = data.archive_file.cost_anomaly_alert.output_path - function_name = "cost_anomaly_alert_lambda_function" - role = aws_iam_role.cost_anomaly_alert.arn - handler = "lambda_function.lambda_handler" - source_code_hash = data.archive_file.cost_anomaly_alert.output_base64sha256 - - runtime = "python3.13" - - environment { - variables = { - ENVIRONMENT = var.env - IGNORE_OK = "false" - WEBHOOK_PARAM = local.ssm_parameter - } - } - - tags = { - Environment = var.env - Application = "cost_anomaly_alert" - } -} From 57c2e149f7388077cb9d2c7fbeaa2664d6757cc0 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Wed, 24 Sep 2025 13:51:50 -0600 Subject: [PATCH 24/70] remove lambda > work will be in plt-1361 --- terraform/services/cost-anomaly/main.tf | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 terraform/services/cost-anomaly/main.tf diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf new file mode 100644 index 00000000..561f1327 --- /dev/null +++ b/terraform/services/cost-anomaly/main.tf @@ -0,0 +1,59 @@ +data "aws_caller_identity" "current" {} + +locals { + function_name = "cost-anomaly-alert" +} + +resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { + name = "BCDA Account Monitor" + monitor_type = "DIMENSIONAL" + monitor_dimension = "SERVICE" +} + +resource "aws_sns_topic" "cost_anomaly_sns" { + name = "cost-anomaly-topic" + kms_master_key_id = "alias/bcda-${var.env}" +} + +resource "aws_ce_anomaly_subscription" "realtime_subscription" { + name = "cost_anomaly_subscription" + frequency = "IMMEDIATE" + + monitor_arn_list = [ + aws_ce_anomaly_monitor.BCDA_Account_Monitor.arn + ] + + subscriber { + type = "SNS" + address = aws_sns_topic.cost_anomaly_sns.arn + } + + threshold_expression { + or { + dimension { + key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE" + match_options = ["GREATER_THAN_OR_EQUAL"] + values = ["20"] + } + } + or { + dimension { + key = "ANOMALY_TOTAL_IMPACT_PERCENTAGE" + match_options = ["GREATER_THAN_OR_EQUAL"] + values = ["5"] + } + } + } +} + +module "sns_to_slack_queue" { + source = "../../modules/queue" + + name = "cost-anomaly-alert-queue" + sns_topic_arn = aws_sns_topic.cost_anomaly_sns.arn + + app = "bcda" + env = var.env + function_name = local.function_name + +} From a19ffce133741761736f7c2cda4328fe341d4fed Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Wed, 24 Sep 2025 14:25:13 -0600 Subject: [PATCH 25/70] revert topic module changes --- terraform/modules/topic/main.tf | 8 ++++---- terraform/modules/topic/variables.tf | 10 ++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/terraform/modules/topic/main.tf b/terraform/modules/topic/main.tf index 5862c845..8333708b 100644 --- a/terraform/modules/topic/main.tf +++ b/terraform/modules/topic/main.tf @@ -2,7 +2,7 @@ module "topic_key" { source = "../key" name = "${var.name}-topic" description = "For ${var.name} SNS topic" - buckets = var.publisher_arns + buckets = var.buckets } resource "aws_sns_topic" "this" { @@ -12,11 +12,11 @@ resource "aws_sns_topic" "this" { } data "aws_iam_policy_document" "topic" { - count = length(var.publisher_arns) > 0 ? 1 : 0 + count = length(var.buckets) > 0 ? 1 : 0 statement { principals { type = "Service" - identifiers = var.policy_service_identifiers + identifiers = ["s3.amazonaws.com"] } actions = ["SNS:Publish"] @@ -25,7 +25,7 @@ data "aws_iam_policy_document" "topic" { condition { test = "ArnLike" variable = "aws:SourceArn" - values = var.publisher_arns + values = var.buckets } } } diff --git a/terraform/modules/topic/variables.tf b/terraform/modules/topic/variables.tf index 4f921ce0..ae21fa24 100644 --- a/terraform/modules/topic/variables.tf +++ b/terraform/modules/topic/variables.tf @@ -3,14 +3,8 @@ variable "name" { type = string } -variable "publisher_arns" { - description = "ARNs for S3 buckets or services that need to publish event notifications to the SNS topic" +variable "buckets" { + description = "ARNs for S3 buckets that need to publish event notifications to the SNS topic" type = list(any) default = [] } - -variable "policy_service_identifiers" { - description = "Identifier(s) for service principals for use in policy. Example: s3.amazonaws.com" - type = list(any) - default = ["s3.amazonaws.com"] -} From 2e16165028b62e1c76d0bd1d14d34fc9032acb0f Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Wed, 24 Sep 2025 14:28:41 -0600 Subject: [PATCH 26/70] revert function module changes --- terraform/modules/function/main.tf | 3 --- terraform/modules/function/variables.tf | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/terraform/modules/function/main.tf b/terraform/modules/function/main.tf index 2c2205d2..c76c7cd8 100644 --- a/terraform/modules/function/main.tf +++ b/terraform/modules/function/main.tf @@ -7,9 +7,6 @@ locals { dpc = [ "repo:CMSgov/dpc-app:*", ] - cdap = [ - "repo:CMSgov/cdap:*", - ] } } diff --git a/terraform/modules/function/variables.tf b/terraform/modules/function/variables.tf index 5f4951af..728ea005 100644 --- a/terraform/modules/function/variables.tf +++ b/terraform/modules/function/variables.tf @@ -2,8 +2,8 @@ variable "app" { description = "The application name (ab2d, bcda, dpc)" type = string validation { - condition = contains(["ab2d", "bcda", "dpc" ,"cdap"], var.app) - error_message = "Valid value for app is ab2d, bcda, dpc or cdap." + condition = contains(["ab2d", "bcda", "dpc"], var.app) + error_message = "Valid value for app is ab2d, bcda, or dpc." } } From dda9eeb2b9bcf8431261240c4cdb81a024ade02b Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 08:30:38 -0600 Subject: [PATCH 27/70] set working directory at top of workflow --- .github/workflows/tf-cost-anomaly.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 45197358..7fcd48fe 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -13,6 +13,10 @@ on: type: boolean description: "Apply the terraform?" +defaults: + run: + working-directory: ./terraform/services/cost-anomaly + jobs: check-fmt: runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} @@ -28,9 +32,6 @@ jobs: contents: read id-token: write runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} - defaults: - run: - working-directory: ./terraform/services/cost-anomaly strategy: fail-fast: false matrix: From a5c570bf685dc03ed8d99357b52477133a1ed9f0 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 08:41:31 -0600 Subject: [PATCH 28/70] set working directory at top of workflow --- .github/workflows/tf-cost-anomaly.yml | 3 +++ terraform/modules/cluster/variables.tf | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 7fcd48fe..4d93e58b 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -13,6 +13,9 @@ on: type: boolean description: "Apply the terraform?" +env: + TENV_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + defaults: run: working-directory: ./terraform/services/cost-anomaly diff --git a/terraform/modules/cluster/variables.tf b/terraform/modules/cluster/variables.tf index 4a9f0a6a..5273e53f 100644 --- a/terraform/modules/cluster/variables.tf +++ b/terraform/modules/cluster/variables.tf @@ -1,6 +1,14 @@ variable "platform" { description = "Object that describes standardized platform values." - type = any + type = object({ + app = string, + env = string, + kms_alias_primary = object({ + target_key_arn = string + }), + service = string, + is_ephemeral_env = string + }) } variable "cluster_name_override" { From 81e5026f5ca2c2fa0d92c54f1ae2dffb3d3fb673 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 08:46:28 -0600 Subject: [PATCH 29/70] set working directory at top of workflow --- .github/workflows/tf-cost-anomaly.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 4d93e58b..1d76c66b 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -35,6 +35,9 @@ jobs: contents: read id-token: write runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} + defaults: + run: + working-directory: ./terraform/services/cost-anomaly strategy: fail-fast: false matrix: From d305754f53f3d43bc456d80368572e3321b5ead6 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 08:55:27 -0600 Subject: [PATCH 30/70] set working directory at top of workflow --- .github/workflows/tf-cost-anomaly.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 1d76c66b..608b44d9 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -48,6 +48,8 @@ jobs: env: mgmt steps: - uses: actions/checkout@v4 + - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 + - uses: cmsgov/cdap/actions/setup-tenv@8343fb96563ce4b74c4dececee9b268f42bd4a40 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions From 5960f0da6e611aae44140e7b6b03da5cfc3e34f4 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 09:06:09 -0600 Subject: [PATCH 31/70] run tofu apply with exclusions first --- .github/workflows/tf-cost-anomaly.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 608b44d9..ea5fa83f 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -55,9 +55,10 @@ jobs: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} - run: tofu init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend - - run: tofu plan -out=tf.plan + - run: tofu plan -exclude=module.sns_to_slack_queue.data.aws_iam_policy_document.sns_send_message -exclude=module.sns_to_slack_queue.aws_sns_topic_subscription.this -out=tf.plan env: TF_VAR_app: ${{ matrix.app }} TF_VAR_env: ${{ matrix.env }} - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') - run: tofu apply -auto-approve tf.plan + run: tofu apply -exclude=module.sns_to_slack_queue.data.aws_iam_policy_document.sns_send_message -exclude=module.sns_to_slack_queue.aws_sns_topic_subscription.this -auto-approve tf.plan + - run: tofu apply -auto-approve tf.plan From 7e38dfd9a1b3908eed2de62f7384beb3b3991282 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 09:11:59 -0600 Subject: [PATCH 32/70] run tofu apply with exclusions first --- .github/workflows/tf-cost-anomaly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index ea5fa83f..27cf8731 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -61,4 +61,5 @@ jobs: TF_VAR_env: ${{ matrix.env }} - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') run: tofu apply -exclude=module.sns_to_slack_queue.data.aws_iam_policy_document.sns_send_message -exclude=module.sns_to_slack_queue.aws_sns_topic_subscription.this -auto-approve tf.plan - - run: tofu apply -auto-approve tf.plan + - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') + run: tofu apply -auto-approve tf.plan From 6af040806c945fc364956ff88843fbf5b771625a Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 09:18:42 -0600 Subject: [PATCH 33/70] run tofu apply with exclusions first --- .github/workflows/tf-cost-anomaly.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 27cf8731..8c8225a5 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -43,9 +43,6 @@ jobs: matrix: app: [ab2d, bcda, dpc] env: [dev, test, sandbox, prod] - include: - - app: cdap - env: mgmt steps: - uses: actions/checkout@v4 - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 From 28d47f6b65e06075837a35851b6601055f818183 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 29 Sep 2025 13:07:26 -0600 Subject: [PATCH 34/70] pr feedback --- .github/workflows/tf-cost-anomaly.yml | 7 ++----- terraform/services/cost-anomaly/main.tf | 6 +++--- terraform/services/cost-anomaly/terraform.tf | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 8c8225a5..3c1ce7d8 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -35,14 +35,11 @@ jobs: contents: read id-token: write runs-on: codebuild-cdap-${{github.run_id}}-${{github.run_attempt}} - defaults: - run: - working-directory: ./terraform/services/cost-anomaly strategy: fail-fast: false matrix: - app: [ab2d, bcda, dpc] - env: [dev, test, sandbox, prod] + app: [bcda] + env: [test, prod] steps: - uses: actions/checkout@v4 - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 561f1327..0f84d071 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -4,8 +4,8 @@ locals { function_name = "cost-anomaly-alert" } -resource "aws_ce_anomaly_monitor" "BCDA_Account_Monitor" { - name = "BCDA Account Monitor" +resource "aws_ce_anomaly_monitor" "account_alerts" { + name = "AccountAlerts" monitor_type = "DIMENSIONAL" monitor_dimension = "SERVICE" } @@ -20,7 +20,7 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { frequency = "IMMEDIATE" monitor_arn_list = [ - aws_ce_anomaly_monitor.BCDA_Account_Monitor.arn + aws_ce_anomaly_monitor.account_alerts.arn ] subscriber { diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index 1706ed22..beda7b54 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -14,4 +14,4 @@ provider "aws" { terraform = true } } -} \ No newline at end of file +} From a8efedc68422f6d6fa3b8d74eeb1cf38649f33c3 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 29 Sep 2025 13:10:47 -0600 Subject: [PATCH 35/70] pr feedback --- terraform/modules/cluster/variables.tf | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/terraform/modules/cluster/variables.tf b/terraform/modules/cluster/variables.tf index 5273e53f..4a9f0a6a 100644 --- a/terraform/modules/cluster/variables.tf +++ b/terraform/modules/cluster/variables.tf @@ -1,14 +1,6 @@ variable "platform" { description = "Object that describes standardized platform values." - type = object({ - app = string, - env = string, - kms_alias_primary = object({ - target_key_arn = string - }), - service = string, - is_ephemeral_env = string - }) + type = any } variable "cluster_name_override" { From a230bf7c4289cc8e4c229e8b8259a41484936ff6 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 30 Sep 2025 09:37:59 -0600 Subject: [PATCH 36/70] pr feedback --- terraform/services/cost-anomaly/variables.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index a471dafd..475d8e2c 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -2,9 +2,7 @@ variable "env" { description = "The application environment (dev, test, mgmt, sandbox, prod)" type = string validation { - condition = contains(["dev", "test", "mgmt", "sandbox", "prod"], var.env) + condition = contains(["test", "prod"], var.env) error_message = "Valid value for env is dev, test, mgmt, sandbox, or prod." } } - - From a7c4c76dc3c59b6632cfd746faccac763614b32f Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 30 Sep 2025 09:53:15 -0600 Subject: [PATCH 37/70] Add standards module. --- terraform/services/cost-anomaly/main.tf | 8 ++++++++ terraform/services/cost-anomaly/terraform.tf | 17 ++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 0f84d071..327678fd 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -4,6 +4,14 @@ locals { function_name = "cost-anomaly-alert" } +module "standards" { + source = "github.com/CMSgov/cdap//terraform/modules/standards" + app = "cdap" + env = var.env + root_module = "https://github.com/CMSgov/cdap/tree/main/terraform/services/cost-anomaly" + service = "cost-anomaly" +} + resource "aws_ce_anomaly_monitor" "account_alerts" { name = "AccountAlerts" monitor_type = "DIMENSIONAL" diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index beda7b54..d6bfafd1 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -5,13 +5,16 @@ terraform { } provider "aws" { + region = "us-east-1" default_tags { - tags = { - business = "oeda" - code = "https://github.com/CMSgov/cdap/tree/main/terraform/services/cost-anomaly" - component = "cost-anomaly" - environment = var.env - terraform = true - } + tags = module.standards.default_tags + } +} + +provider "aws" { + alias = "secondary" + region = "us-west-2" + default_tags { + tags = module.standards.default_tags } } From f34d2640fb3959122de710a54e1ca97183eb72b1 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 30 Sep 2025 09:59:41 -0600 Subject: [PATCH 38/70] Add standards module. --- terraform/services/cost-anomaly/main.tf | 1 + terraform/services/cost-anomaly/terraform.tf | 1 + 2 files changed, 2 insertions(+) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 327678fd..c5c09a8d 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -8,6 +8,7 @@ module "standards" { source = "github.com/CMSgov/cdap//terraform/modules/standards" app = "cdap" env = var.env + providers = { aws = aws, aws.secondary = aws.secondary } root_module = "https://github.com/CMSgov/cdap/tree/main/terraform/services/cost-anomaly" service = "cost-anomaly" } diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index d6bfafd1..f6003c08 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -5,6 +5,7 @@ terraform { } provider "aws" { + alias = primary region = "us-east-1" default_tags { tags = module.standards.default_tags From f72b6771740e2fdf230362afb56aef175f18bceb Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 30 Sep 2025 10:04:04 -0600 Subject: [PATCH 39/70] tofu fmt --- terraform/services/cost-anomaly/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index c5c09a8d..bf343dca 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -8,7 +8,7 @@ module "standards" { source = "github.com/CMSgov/cdap//terraform/modules/standards" app = "cdap" env = var.env - providers = { aws = aws, aws.secondary = aws.secondary } + providers = { aws = aws, aws.secondary = aws.secondary } root_module = "https://github.com/CMSgov/cdap/tree/main/terraform/services/cost-anomaly" service = "cost-anomaly" } From c7cacd80bcb33f5f751496135e9ee8b5b2db27fb Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 30 Sep 2025 10:07:32 -0600 Subject: [PATCH 40/70] tofu fmt --- terraform/services/cost-anomaly/terraform.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index f6003c08..ebb0478e 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -5,7 +5,7 @@ terraform { } provider "aws" { - alias = primary + alias = "primary" region = "us-east-1" default_tags { tags = module.standards.default_tags From efa17aec8fa5a5bb37d9646d77ee121e25348e23 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 30 Sep 2025 10:24:19 -0600 Subject: [PATCH 41/70] tofu fmt --- terraform/services/cost-anomaly/terraform.tf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index ebb0478e..29f4e18c 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -2,6 +2,13 @@ terraform { backend "s3" { key = "cost-anomaly/terraform.tfstate" } + required_providers { + aws = { + source = "hashicorp/aws" + version = "~>5" + } + } + required_version = "1.10.5" } provider "aws" { From 36b5d50dd12fcd5a453d570b381835aa4f6d12eb Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 3 Oct 2025 07:28:42 -0600 Subject: [PATCH 42/70] Update .github/workflows/tf-cost-anomaly.yml Co-authored-by: Sean Fern --- .github/workflows/tf-cost-anomaly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 3c1ce7d8..827cab38 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v4 - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 - uses: cmsgov/cdap/actions/setup-tenv@8343fb96563ce4b74c4dececee9b268f42bd4a40 - - uses: aws-actions/configure-aws-credentials@v4 + - uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v5.0.0 with: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} From 531e15d8b38affdd55283c099f96d7c0d1ce9ec9 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 3 Oct 2025 07:28:52 -0600 Subject: [PATCH 43/70] Update terraform/services/cost-anomaly/main.tf Co-authored-by: Sean Fern --- terraform/services/cost-anomaly/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index bf343dca..1d4f26c9 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -64,5 +64,4 @@ module "sns_to_slack_queue" { app = "bcda" env = var.env function_name = local.function_name - } From a61fa99c4a2bb4e6ea7663e1df3884475b948483 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 3 Oct 2025 07:34:33 -0600 Subject: [PATCH 44/70] fix env description. --- terraform/services/cost-anomaly/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index 475d8e2c..dba1f873 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -1,8 +1,8 @@ variable "env" { - description = "The application environment (dev, test, mgmt, sandbox, prod)" + description = "The application environment (test, prod)" type = string validation { condition = contains(["test", "prod"], var.env) - error_message = "Valid value for env is dev, test, mgmt, sandbox, or prod." + error_message = "Valid value for env is test or prod." } } From 70c3ec572119934cd7ddb3f557f43fbd4c99ba53 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 6 Oct 2025 08:11:41 -0600 Subject: [PATCH 45/70] added cron schedule to workflow --- .github/workflows/tf-cost-anomaly.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index 827cab38..e1165092 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -1,11 +1,13 @@ name: tf-cost-anomaly -run-name: tf-cost-anomaly ${{ (inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main')) && 'apply' || 'plan' }} +run-name: tf-cost-anomaly ${{ (inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'schedule') && 'apply' || 'plan' }} on: push: paths: - .github/workflows/tf-cost-anomaly.yml - terraform/services/cost-anomaly/** + schedule: + - cron: "12 14 * * 1-5" workflow_dispatch: inputs: apply: @@ -53,7 +55,7 @@ jobs: env: TF_VAR_app: ${{ matrix.app }} TF_VAR_env: ${{ matrix.env }} - - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') - run: tofu apply -exclude=module.sns_to_slack_queue.data.aws_iam_policy_document.sns_send_message -exclude=module.sns_to_slack_queue.aws_sns_topic_subscription.this -auto-approve tf.plan + - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'schedule' + run: tofu apply -auto-approve tf.plan - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') run: tofu apply -auto-approve tf.plan From 70990f9355c8a55ee582c1421a1cfcbb450793b3 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 27 Oct 2025 08:24:24 -0600 Subject: [PATCH 46/70] remove test plan vars --- .github/workflows/tf-cost-anomaly.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tf-cost-anomaly.yml b/.github/workflows/tf-cost-anomaly.yml index e1165092..b7243ee6 100644 --- a/.github/workflows/tf-cost-anomaly.yml +++ b/.github/workflows/tf-cost-anomaly.yml @@ -51,11 +51,9 @@ jobs: role-to-assume: arn:aws:iam::${{ contains(fromJSON('["dev", "test"]'), matrix.env) && secrets.NON_PROD_ACCOUNT || secrets.PROD_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} - run: tofu init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend - - run: tofu plan -exclude=module.sns_to_slack_queue.data.aws_iam_policy_document.sns_send_message -exclude=module.sns_to_slack_queue.aws_sns_topic_subscription.this -out=tf.plan + - run: tofu plan -out=tf.plan env: TF_VAR_app: ${{ matrix.app }} TF_VAR_env: ${{ matrix.env }} - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'schedule' run: tofu apply -auto-approve tf.plan - - if: inputs.apply || (github.event_name == 'push' && github.ref == 'refs/heads/main') - run: tofu apply -auto-approve tf.plan From 83c85164475d961456e5c11e360e723e0a1116f6 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 27 Oct 2025 14:44:09 -0600 Subject: [PATCH 47/70] pr feedback changes to queue module --- terraform/modules/queue/main.tf | 11 +++-------- terraform/modules/queue/variables.tf | 12 ++++++++++++ terraform/services/cost-anomaly/main.tf | 11 ++++++++++- terraform/services/cost-anomaly/variables.tf | 12 ++++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 8bb249e1..d77f76cc 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -29,7 +29,9 @@ resource "aws_sqs_queue_redrive_allow_policy" "this" { } data "aws_iam_policy_document" "sns_send_message" { - count = var.sns_topic_arn != "None" ? 1 : 0 + + source_policy_documents = var.source_policy_documents + override_policy_documents = var.override_policy_documents statement { actions = ["sqs:SendMessage"] @@ -50,19 +52,12 @@ data "aws_iam_policy_document" "sns_send_message" { } resource "aws_sqs_queue_policy" "sns_send_message" { - count = var.sns_topic_arn != "None" ? 1 : 0 queue_url = aws_sqs_queue.this.id policy = data.aws_iam_policy_document.sns_send_message[0].json } -resource "aws_sns_topic_subscription" "this" { - count = var.sns_topic_arn != "None" ? 1 : 0 - endpoint = aws_sqs_queue.this.arn - protocol = "sqs" - topic_arn = var.sns_topic_arn -} resource "aws_lambda_event_source_mapping" "this" { event_source_arn = aws_sqs_queue.this.arn diff --git a/terraform/modules/queue/variables.tf b/terraform/modules/queue/variables.tf index fbee2b3e..500f6b0b 100644 --- a/terraform/modules/queue/variables.tf +++ b/terraform/modules/queue/variables.tf @@ -40,3 +40,15 @@ variable "env" { error_message = "Valid value for env is dev, test, sandbox, or prod." } } + +variable "source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 1d4f26c9..1d16aec1 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -58,10 +58,19 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { module "sns_to_slack_queue" { source = "../../modules/queue" + source_policy_documents = var + override_policy_documents = var.override_policy_documents + name = "cost-anomaly-alert-queue" - sns_topic_arn = aws_sns_topic.cost_anomaly_sns.arn app = "bcda" env = var.env function_name = local.function_name + +} + +resource "aws_sns_topic_subscription" "this" { + endpoint = sns_to_slack_queue + protocol = "sqs" + topic_arn = aws_sns_topic.cost_anomaly_sns.arn } diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index dba1f873..063be9d3 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -6,3 +6,15 @@ variable "env" { error_message = "Valid value for env is test or prod." } } + +variable "source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} From 0da0b2fc985359212f927af7e53bc4db9577ac45 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 27 Oct 2025 15:06:56 -0600 Subject: [PATCH 48/70] pr feedback changes to queue module --- terraform/modules/queue/main.tf | 17 ----------------- terraform/services/cost-anomaly/main.tf | 24 +++++++++++++++++++++++- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index d77f76cc..6ed13e6d 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -32,23 +32,6 @@ data "aws_iam_policy_document" "sns_send_message" { source_policy_documents = var.source_policy_documents override_policy_documents = var.override_policy_documents - - statement { - actions = ["sqs:SendMessage"] - - principals { - type = "Service" - identifiers = ["sns.amazonaws.com"] - } - - resources = [aws_sqs_queue.this.arn] - - condition { - test = "ArnEquals" - variable = "aws:SourceArn" - values = [var.sns_topic_arn] - } - } } resource "aws_sqs_queue_policy" "sns_send_message" { diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 1d16aec1..88100686 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -55,10 +55,32 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { } } + +data "aws_iam_policy_document" "sns_send_message" { + + statement { + actions = ["sqs:SendMessage"] + + principals { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + + resources = [module.sns_to_slack_queue.arn] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [aws_sns_topic.cost_anomaly_sns.arn] + } + } +} + + module "sns_to_slack_queue" { source = "../../modules/queue" - source_policy_documents = var + source_policy_documents = data.aws_iam_policy_document.sns_send_message.json override_policy_documents = var.override_policy_documents name = "cost-anomaly-alert-queue" From 6423df5ed126b16ca7de6063c5886cc277626eb2 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:23:54 -0600 Subject: [PATCH 49/70] fmt --- terraform/services/cost-anomaly/main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 88100686..98f6df9c 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -62,7 +62,7 @@ data "aws_iam_policy_document" "sns_send_message" { actions = ["sqs:SendMessage"] principals { - type = "Service" + type = "Service" identifiers = ["sns.amazonaws.com"] } @@ -71,7 +71,7 @@ data "aws_iam_policy_document" "sns_send_message" { condition { test = "ArnEquals" variable = "aws:SourceArn" - values = [aws_sns_topic.cost_anomaly_sns.arn] + values = [aws_sns_topic.cost_anomaly_sns.arn] } } } @@ -80,10 +80,10 @@ data "aws_iam_policy_document" "sns_send_message" { module "sns_to_slack_queue" { source = "../../modules/queue" - source_policy_documents = data.aws_iam_policy_document.sns_send_message.json + source_policy_documents = data.aws_iam_policy_document.sns_send_message.json override_policy_documents = var.override_policy_documents - name = "cost-anomaly-alert-queue" + name = "cost-anomaly-alert-queue" app = "bcda" env = var.env From 9b208b6a673fe710497579fb25f551e19ac14031 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:28:50 -0600 Subject: [PATCH 50/70] correct tofu --- terraform/services/cost-anomaly/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 98f6df9c..bacd7032 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -80,7 +80,7 @@ data "aws_iam_policy_document" "sns_send_message" { module "sns_to_slack_queue" { source = "../../modules/queue" - source_policy_documents = data.aws_iam_policy_document.sns_send_message.json + source_policy_documents = [data.aws_iam_policy_document.sns_send_message.json] override_policy_documents = var.override_policy_documents name = "cost-anomaly-alert-queue" @@ -92,7 +92,7 @@ module "sns_to_slack_queue" { } resource "aws_sns_topic_subscription" "this" { - endpoint = sns_to_slack_queue + endpoint = module.sns_to_slack_queue protocol = "sqs" topic_arn = aws_sns_topic.cost_anomaly_sns.arn } From 334725b572de9c65766a2123ea6daeb00f071384 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:30:17 -0600 Subject: [PATCH 51/70] correct tofu --- terraform/modules/queue/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 6ed13e6d..d59095e5 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -37,7 +37,7 @@ data "aws_iam_policy_document" "sns_send_message" { resource "aws_sqs_queue_policy" "sns_send_message" { queue_url = aws_sqs_queue.this.id - policy = data.aws_iam_policy_document.sns_send_message[0].json + policy = data.aws_iam_policy_document.sns_send_message.json } From d73b12e227af12aae9a7dccc9296c8f76fe96262 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:38:33 -0600 Subject: [PATCH 52/70] correct fmt --- terraform/modules/queue/main.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index d59095e5..25ba2f13 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -30,7 +30,7 @@ resource "aws_sqs_queue_redrive_allow_policy" "this" { data "aws_iam_policy_document" "sns_send_message" { - source_policy_documents = var.source_policy_documents + source_policy_documents = var.source_policy_documents override_policy_documents = var.override_policy_documents } @@ -40,8 +40,6 @@ resource "aws_sqs_queue_policy" "sns_send_message" { policy = data.aws_iam_policy_document.sns_send_message.json } - - resource "aws_lambda_event_source_mapping" "this" { event_source_arn = aws_sqs_queue.this.arn function_name = var.function_name From f04465a9ed8d0ed6c5449d5322ff685494b9be14 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:46:47 -0600 Subject: [PATCH 53/70] add unique sid --- terraform/services/cost-anomaly/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index bacd7032..db866a34 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -59,6 +59,7 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { data "aws_iam_policy_document" "sns_send_message" { statement { + sid = "SnsSendMessage" actions = ["sqs:SendMessage"] principals { @@ -76,7 +77,6 @@ data "aws_iam_policy_document" "sns_send_message" { } } - module "sns_to_slack_queue" { source = "../../modules/queue" From 4ab13f66d56eed8c0e591fe12c463c0ec90a5dd2 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:48:55 -0600 Subject: [PATCH 54/70] fmt --- terraform/services/cost-anomaly/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index db866a34..f9d338ba 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -59,7 +59,7 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { data "aws_iam_policy_document" "sns_send_message" { statement { - sid = "SnsSendMessage" + sid = "SnsSendMessage" actions = ["sqs:SendMessage"] principals { From 83bfd8ff1a093c39a5e4b204c598e1599d4af94f Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Tue, 28 Oct 2025 09:52:30 -0600 Subject: [PATCH 55/70] fmt --- terraform/services/cost-anomaly/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index f9d338ba..9d9f0d86 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -92,7 +92,7 @@ module "sns_to_slack_queue" { } resource "aws_sns_topic_subscription" "this" { - endpoint = module.sns_to_slack_queue + endpoint = module.sns_to_slack_queue.arn protocol = "sqs" topic_arn = aws_sns_topic.cost_anomaly_sns.arn } From 612df8668603e7328b3c383d214a551a20215a1d Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 7 Nov 2025 13:52:33 -0700 Subject: [PATCH 56/70] pr feedback --- terraform/modules/queue/main.tf | 30 ++++++++++++++++++-- terraform/services/cost-anomaly/main.tf | 1 - terraform/services/cost-anomaly/terraform.tf | 1 - terraform/services/cost-anomaly/variables.tf | 11 ------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 25ba2f13..8bb249e1 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -29,15 +29,39 @@ resource "aws_sqs_queue_redrive_allow_policy" "this" { } data "aws_iam_policy_document" "sns_send_message" { + count = var.sns_topic_arn != "None" ? 1 : 0 - source_policy_documents = var.source_policy_documents - override_policy_documents = var.override_policy_documents + statement { + actions = ["sqs:SendMessage"] + + principals { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + + resources = [aws_sqs_queue.this.arn] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [var.sns_topic_arn] + } + } } resource "aws_sqs_queue_policy" "sns_send_message" { + count = var.sns_topic_arn != "None" ? 1 : 0 queue_url = aws_sqs_queue.this.id - policy = data.aws_iam_policy_document.sns_send_message.json + policy = data.aws_iam_policy_document.sns_send_message[0].json +} + +resource "aws_sns_topic_subscription" "this" { + count = var.sns_topic_arn != "None" ? 1 : 0 + + endpoint = aws_sqs_queue.this.arn + protocol = "sqs" + topic_arn = var.sns_topic_arn } resource "aws_lambda_event_source_mapping" "this" { diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 9d9f0d86..73f4cac3 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -81,7 +81,6 @@ module "sns_to_slack_queue" { source = "../../modules/queue" source_policy_documents = [data.aws_iam_policy_document.sns_send_message.json] - override_policy_documents = var.override_policy_documents name = "cost-anomaly-alert-queue" diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index 29f4e18c..5e28103b 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -8,7 +8,6 @@ terraform { version = "~>5" } } - required_version = "1.10.5" } provider "aws" { diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index 063be9d3..4fb611ce 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -7,14 +7,3 @@ variable "env" { } } -variable "source_policy_documents" { - description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" - type = list(string) - default = [] -} - -variable "override_policy_documents" { - description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" - type = list(string) - default = [] -} From 47b06003f2cca92381770dd2d745dbb6f4d7d47e Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 26 Sep 2025 08:41:31 -0600 Subject: [PATCH 57/70] set working directory at top of workflow --- terraform/modules/cluster/variables.tf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/terraform/modules/cluster/variables.tf b/terraform/modules/cluster/variables.tf index c73dd82d..5273e53f 100644 --- a/terraform/modules/cluster/variables.tf +++ b/terraform/modules/cluster/variables.tf @@ -1,13 +1,13 @@ variable "platform" { description = "Object that describes standardized platform values." type = object({ - app = string, - env = string, + app = string, + env = string, kms_alias_primary = object({ - target_key_arn = string, + target_key_arn = string }), - service = string, - is_ephemeral_env = string + service = string, + is_ephemeral_env = string }) } From baa3ba7a5e1a7e04f86a4b0c9161bbbd9e7493d0 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 7 Nov 2025 13:57:57 -0700 Subject: [PATCH 58/70] format --- terraform/services/cost-anomaly/main.tf | 2 +- terraform/services/cost-anomaly/variables.tf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 73f4cac3..624b31d4 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -80,7 +80,7 @@ data "aws_iam_policy_document" "sns_send_message" { module "sns_to_slack_queue" { source = "../../modules/queue" - source_policy_documents = [data.aws_iam_policy_document.sns_send_message.json] + source_policy_documents = [data.aws_iam_policy_document.sns_send_message.json] name = "cost-anomaly-alert-queue" diff --git a/terraform/services/cost-anomaly/variables.tf b/terraform/services/cost-anomaly/variables.tf index 4fb611ce..dba1f873 100644 --- a/terraform/services/cost-anomaly/variables.tf +++ b/terraform/services/cost-anomaly/variables.tf @@ -6,4 +6,3 @@ variable "env" { error_message = "Valid value for env is test or prod." } } - From 9707f68eaf1686ac40f99d4885d77d61efd9c5ed Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 7 Nov 2025 14:09:14 -0700 Subject: [PATCH 59/70] format --- terraform/modules/queue/main.tf | 5 +++-- terraform/modules/queue/variables.tf | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 8bb249e1..0511d9e0 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -8,8 +8,8 @@ resource "aws_sqs_queue" "dead_letter" { } resource "aws_sqs_queue" "this" { - name = var.name - kms_master_key_id = data.aws_kms_alias.kms_key.arn + name = var.name + kms_master_key_id = data.aws_kms_alias.kms_key.arn visibility_timeout_seconds = var.visibility_timeout_seconds @@ -47,6 +47,7 @@ data "aws_iam_policy_document" "sns_send_message" { values = [var.sns_topic_arn] } } + source_policy_documents = var.policy_documents } resource "aws_sqs_queue_policy" "sns_send_message" { diff --git a/terraform/modules/queue/variables.tf b/terraform/modules/queue/variables.tf index 500f6b0b..82c4fe88 100644 --- a/terraform/modules/queue/variables.tf +++ b/terraform/modules/queue/variables.tf @@ -41,14 +41,8 @@ variable "env" { } } -variable "source_policy_documents" { +variable "policy_documents" { description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" type = list(string) default = [] } - -variable "override_policy_documents" { - description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" - type = list(string) - default = [] -} From bb41791ba3439499f585d38b8785a87a51619c19 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Fri, 7 Nov 2025 14:40:42 -0700 Subject: [PATCH 60/70] format --- terraform/services/cost-anomaly/main.tf | 3 --- 1 file changed, 3 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 624b31d4..40d2a897 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -55,7 +55,6 @@ resource "aws_ce_anomaly_subscription" "realtime_subscription" { } } - data "aws_iam_policy_document" "sns_send_message" { statement { @@ -80,8 +79,6 @@ data "aws_iam_policy_document" "sns_send_message" { module "sns_to_slack_queue" { source = "../../modules/queue" - source_policy_documents = [data.aws_iam_policy_document.sns_send_message.json] - name = "cost-anomaly-alert-queue" app = "bcda" From 7f7680104d2c309b43622f745b757b631444b093 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 09:06:06 -0700 Subject: [PATCH 61/70] pr feedback --- terraform/services/cost-anomaly/terraform.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index 5e28103b..ebb0478e 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -2,12 +2,6 @@ terraform { backend "s3" { key = "cost-anomaly/terraform.tfstate" } - required_providers { - aws = { - source = "hashicorp/aws" - version = "~>5" - } - } } provider "aws" { From 7f1843883146371f97b78799344fc19a2167f482 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 12:51:20 -0700 Subject: [PATCH 62/70] pr feedback --- terraform/modules/queue/main.tf | 4 ++-- terraform/services/cost-anomaly/main.tf | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 0511d9e0..cb75d2af 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -29,7 +29,7 @@ resource "aws_sqs_queue_redrive_allow_policy" "this" { } data "aws_iam_policy_document" "sns_send_message" { - count = var.sns_topic_arn != "None" ? 1 : 0 + count = (var.sns_topic_arn != "None" && length(var.policy_documents)>0) ? 1 : 0 statement { actions = ["sqs:SendMessage"] @@ -51,7 +51,7 @@ data "aws_iam_policy_document" "sns_send_message" { } resource "aws_sqs_queue_policy" "sns_send_message" { - count = var.sns_topic_arn != "None" ? 1 : 0 + count = (var.sns_topic_arn != "None" && length(var.policy_documents)>0) ? 1 : 0 queue_url = aws_sqs_queue.this.id policy = data.aws_iam_policy_document.sns_send_message[0].json diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 40d2a897..e357c858 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -84,6 +84,7 @@ module "sns_to_slack_queue" { app = "bcda" env = var.env function_name = local.function_name + policy_documents = [data.aws_iam_policy_document.sns_send_message] } From f1b6951f2d91f1181dac5015b0f518556903df9a Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 13:01:39 -0700 Subject: [PATCH 63/70] format --- terraform/services/cost-anomaly/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index e357c858..e23338f8 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -81,9 +81,9 @@ module "sns_to_slack_queue" { name = "cost-anomaly-alert-queue" - app = "bcda" - env = var.env - function_name = local.function_name + app = "bcda" + env = var.env + function_name = local.function_name policy_documents = [data.aws_iam_policy_document.sns_send_message] } From 8e01d301732515a763a25ee4d77aa447cfccbf39 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 17:19:50 -0700 Subject: [PATCH 64/70] review feedback --- terraform/modules/queue/main.tf | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index cb75d2af..efd8cc81 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -28,33 +28,16 @@ resource "aws_sqs_queue_redrive_allow_policy" "this" { }) } -data "aws_iam_policy_document" "sns_send_message" { - count = (var.sns_topic_arn != "None" && length(var.policy_documents)>0) ? 1 : 0 - - statement { - actions = ["sqs:SendMessage"] - - principals { - type = "Service" - identifiers = ["sns.amazonaws.com"] - } - - resources = [aws_sqs_queue.this.arn] - - condition { - test = "ArnEquals" - variable = "aws:SourceArn" - values = [var.sns_topic_arn] - } - } +data "aws_iam_policy_document" "this" { + count = (var.sns_topic_arn != "None" || length(var.policy_documents)>0) ? 1 : 0 source_policy_documents = var.policy_documents } -resource "aws_sqs_queue_policy" "sns_send_message" { - count = (var.sns_topic_arn != "None" && length(var.policy_documents)>0) ? 1 : 0 +resource "aws_sqs_queue_policy" "this" { + count = (var.sns_topic_arn != "None" || length(var.policy_documents)>0) ? 1 : 0 queue_url = aws_sqs_queue.this.id - policy = data.aws_iam_policy_document.sns_send_message[0].json + policy = data.aws_iam_policy_document.this[0].json } resource "aws_sns_topic_subscription" "this" { From 1da35fe4824cb7b405ed1b97b10cc618b22c1683 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 17:31:36 -0700 Subject: [PATCH 65/70] review feedback --- terraform/modules/queue/main.tf | 11 --------- terraform/services/cclf-import/main.tf | 28 +++++++++++++++++++++++ terraform/services/opt-out-import/main.tf | 28 +++++++++++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index efd8cc81..5fc835b0 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -29,25 +29,14 @@ resource "aws_sqs_queue_redrive_allow_policy" "this" { } data "aws_iam_policy_document" "this" { - count = (var.sns_topic_arn != "None" || length(var.policy_documents)>0) ? 1 : 0 source_policy_documents = var.policy_documents } resource "aws_sqs_queue_policy" "this" { - count = (var.sns_topic_arn != "None" || length(var.policy_documents)>0) ? 1 : 0 - queue_url = aws_sqs_queue.this.id policy = data.aws_iam_policy_document.this[0].json } -resource "aws_sns_topic_subscription" "this" { - count = var.sns_topic_arn != "None" ? 1 : 0 - - endpoint = aws_sqs_queue.this.arn - protocol = "sqs" - topic_arn = var.sns_topic_arn -} - resource "aws_lambda_event_source_mapping" "this" { event_source_arn = aws_sqs_queue.this.arn function_name = var.function_name diff --git a/terraform/services/cclf-import/main.tf b/terraform/services/cclf-import/main.tf index a796c62c..bcf477ad 100644 --- a/terraform/services/cclf-import/main.tf +++ b/terraform/services/cclf-import/main.tf @@ -67,6 +67,34 @@ module "cclf_import_queue" { function_name = module.cclf_import_function.name sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value + policy_documents = [data.aws_iam_policy_document.sns_send_message] +} + +data "aws_iam_policy_document" "sns_send_message" { + + statement { + sid = "SnsSendMessage" + actions = ["sqs:SendMessage"] + + principals { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + + resources = [module.cclf_import_queue.arn] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [data.aws_ssm_parameter.bfd_sns_topic_arn.value] + } + } +} + +resource "aws_sns_topic_subscription" "this" { + endpoint = module.cclf_import_queue.arn + protocol = "sqs" + topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value } # Add a rule to the database security group to allow access from the function diff --git a/terraform/services/opt-out-import/main.tf b/terraform/services/opt-out-import/main.tf index 08750a22..e7a89443 100644 --- a/terraform/services/opt-out-import/main.tf +++ b/terraform/services/opt-out-import/main.tf @@ -100,6 +100,34 @@ module "opt_out_import_queue" { function_name = module.opt_out_import_function.name sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value + policy_documents = [data.aws_iam_policy_document.sns_send_message] +} + +data "aws_iam_policy_document" "sns_send_message" { + + statement { + sid = "SnsSendMessage" + actions = ["sqs:SendMessage"] + + principals { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + + resources = [module.opt_out_import_queue.arn] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [data.aws_ssm_parameter.bfd_sns_topic_arn.value] + } + } +} + +resource "aws_sns_topic_subscription" "this" { + endpoint = module.opt_out_import_queue.arn + protocol = "sqs" + topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value } # Add a rule to the database security group to allow access from the function From 64f75d94a9bf211c54c2de9a1144ab444db384e3 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 17:36:25 -0700 Subject: [PATCH 66/70] tofu fmt --- terraform/modules/queue/main.tf | 4 ++-- terraform/services/cclf-import/main.tf | 4 ++-- terraform/services/opt-out-import/main.tf | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 5fc835b0..6bbd8505 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -8,8 +8,8 @@ resource "aws_sqs_queue" "dead_letter" { } resource "aws_sqs_queue" "this" { - name = var.name - kms_master_key_id = data.aws_kms_alias.kms_key.arn + name = var.name + kms_master_key_id = data.aws_kms_alias.kms_key.arn visibility_timeout_seconds = var.visibility_timeout_seconds diff --git a/terraform/services/cclf-import/main.tf b/terraform/services/cclf-import/main.tf index bcf477ad..ba2c10ea 100644 --- a/terraform/services/cclf-import/main.tf +++ b/terraform/services/cclf-import/main.tf @@ -65,8 +65,8 @@ module "cclf_import_queue" { name = local.full_name - function_name = module.cclf_import_function.name - sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value + function_name = module.cclf_import_function.name + sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value policy_documents = [data.aws_iam_policy_document.sns_send_message] } diff --git a/terraform/services/opt-out-import/main.tf b/terraform/services/opt-out-import/main.tf index e7a89443..b0876fe2 100644 --- a/terraform/services/opt-out-import/main.tf +++ b/terraform/services/opt-out-import/main.tf @@ -98,8 +98,8 @@ module "opt_out_import_queue" { name = local.full_name - function_name = module.opt_out_import_function.name - sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value + function_name = module.opt_out_import_function.name + sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value policy_documents = [data.aws_iam_policy_document.sns_send_message] } From f13dc1b0952fc71b38a63031ae97064e30e50aa2 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 17:40:40 -0700 Subject: [PATCH 67/70] remove index --- terraform/modules/queue/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/modules/queue/main.tf b/terraform/modules/queue/main.tf index 6bbd8505..dac93fe3 100644 --- a/terraform/modules/queue/main.tf +++ b/terraform/modules/queue/main.tf @@ -34,7 +34,7 @@ data "aws_iam_policy_document" "this" { resource "aws_sqs_queue_policy" "this" { queue_url = aws_sqs_queue.this.id - policy = data.aws_iam_policy_document.this[0].json + policy = data.aws_iam_policy_document.this.json } resource "aws_lambda_event_source_mapping" "this" { From a1ddc617f456dcafe42d6e41ca59c9597348b373 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 17:46:39 -0700 Subject: [PATCH 68/70] remove index --- terraform/services/cclf-import/main.tf | 2 +- terraform/services/cost-anomaly/main.tf | 2 +- terraform/services/opt-out-import/main.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/services/cclf-import/main.tf b/terraform/services/cclf-import/main.tf index ba2c10ea..6beb9e04 100644 --- a/terraform/services/cclf-import/main.tf +++ b/terraform/services/cclf-import/main.tf @@ -67,7 +67,7 @@ module "cclf_import_queue" { function_name = module.cclf_import_function.name sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value - policy_documents = [data.aws_iam_policy_document.sns_send_message] + policy_documents = [data.aws_iam_policy_document.sns_send_message.json] } data "aws_iam_policy_document" "sns_send_message" { diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index e23338f8..8ac00ee1 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -84,7 +84,7 @@ module "sns_to_slack_queue" { app = "bcda" env = var.env function_name = local.function_name - policy_documents = [data.aws_iam_policy_document.sns_send_message] + policy_documents = [data.aws_iam_policy_document.sns_send_message.json] } diff --git a/terraform/services/opt-out-import/main.tf b/terraform/services/opt-out-import/main.tf index b0876fe2..d4f1134d 100644 --- a/terraform/services/opt-out-import/main.tf +++ b/terraform/services/opt-out-import/main.tf @@ -100,7 +100,7 @@ module "opt_out_import_queue" { function_name = module.opt_out_import_function.name sns_topic_arn = data.aws_ssm_parameter.bfd_sns_topic_arn.value - policy_documents = [data.aws_iam_policy_document.sns_send_message] + policy_documents = [data.aws_iam_policy_document.sns_send_message.json] } data "aws_iam_policy_document" "sns_send_message" { From 100bad4790cc757b6dd595349dc53535372619a8 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 18:07:37 -0700 Subject: [PATCH 69/70] use platform module --- terraform/services/cost-anomaly/main.tf | 15 +++++++++------ terraform/services/cost-anomaly/terraform.tf | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index 8ac00ee1..ee3bf414 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -2,15 +2,18 @@ data "aws_caller_identity" "current" {} locals { function_name = "cost-anomaly-alert" + app = "cdap" + service = "cost-anomaly" } -module "standards" { - source = "github.com/CMSgov/cdap//terraform/modules/standards" - app = "cdap" +module "platform" { + source ="github.com/CMSgov/cdap//terraform/modules/platform?ref=ff2ef539fb06f2c98f0e3ce0c8f922bdacb96d66" + providers = { aws = aws, aws.secondary = aws.secondary } + + app = local.app env = var.env - providers = { aws = aws, aws.secondary = aws.secondary } - root_module = "https://github.com/CMSgov/cdap/tree/main/terraform/services/cost-anomaly" - service = "cost-anomaly" + root_module = "https://github.com/CMSgov/cdap/tree/terraform/services/cost-anomaly" + service = local.service } resource "aws_ce_anomaly_monitor" "account_alerts" { diff --git a/terraform/services/cost-anomaly/terraform.tf b/terraform/services/cost-anomaly/terraform.tf index ebb0478e..4099eaee 100644 --- a/terraform/services/cost-anomaly/terraform.tf +++ b/terraform/services/cost-anomaly/terraform.tf @@ -8,7 +8,7 @@ provider "aws" { alias = "primary" region = "us-east-1" default_tags { - tags = module.standards.default_tags + tags = module.platform.default_tags } } @@ -16,6 +16,6 @@ provider "aws" { alias = "secondary" region = "us-west-2" default_tags { - tags = module.standards.default_tags + tags = module.platform.default_tags } } From 342770947960851364a287c884b1fbde1102c6c0 Mon Sep 17 00:00:00 2001 From: juliareynolds-nava Date: Mon, 10 Nov 2025 18:09:43 -0700 Subject: [PATCH 70/70] tofu fmt --- terraform/services/cost-anomaly/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/services/cost-anomaly/main.tf b/terraform/services/cost-anomaly/main.tf index ee3bf414..5e8ac745 100644 --- a/terraform/services/cost-anomaly/main.tf +++ b/terraform/services/cost-anomaly/main.tf @@ -2,12 +2,12 @@ data "aws_caller_identity" "current" {} locals { function_name = "cost-anomaly-alert" - app = "cdap" - service = "cost-anomaly" + app = "cdap" + service = "cost-anomaly" } module "platform" { - source ="github.com/CMSgov/cdap//terraform/modules/platform?ref=ff2ef539fb06f2c98f0e3ce0c8f922bdacb96d66" + source = "github.com/CMSgov/cdap//terraform/modules/platform?ref=ff2ef539fb06f2c98f0e3ce0c8f922bdacb96d66" providers = { aws = aws, aws.secondary = aws.secondary } app = local.app