diff --git a/ops/services/10-config/README.md b/ops/services/10-config/README.md
index 022a1ea32..b8bf4c67f 100644
--- a/ops/services/10-config/README.md
+++ b/ops/services/10-config/README.md
@@ -92,8 +92,8 @@ No requirements.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [env](#input\_env) | The application environment (dev, test, sandbox, prod) | `string` | n/a | yes |
| [create\_local\_sops\_wrapper](#input\_create\_local\_sops\_wrapper) | When `true`, creates sops wrapper file at `bin/sopsw`. | `bool` | `false` | no |
+| [parent\_env](#input\_parent\_env) | The parent environment of the current solution. Will correspond with `terraform.workspace`".
Necessary on `tofu init` and `tofu workspace select` \_only\_. In all other situations, parent env
will be divined from `terraform.workspace`. | `string` | `null` | no |
| [region](#input\_region) | n/a | `string` | `"us-east-1"` | no |
| [secondary\_region](#input\_secondary\_region) | n/a | `string` | `"us-west-2"` | no |
diff --git a/ops/services/10-config/main.tf b/ops/services/10-config/main.tf
index e7d96ec50..5423ec01d 100644
--- a/ops/services/10-config/main.tf
+++ b/ops/services/10-config/main.tf
@@ -1,5 +1,7 @@
locals {
- service = "config"
+ service = "config"
+ default_tags = module.platform.default_tags
+ env = terraform.workspace
}
module "platform" {
diff --git a/ops/services/10-config/values/prod.sopsw.yaml b/ops/services/10-config/values/prod.sopsw.yaml
index a9e1f16e9..a257de1d9 100644
--- a/ops/services/10-config/values/prod.sopsw.yaml
+++ b/ops/services/10-config/values/prod.sopsw.yaml
@@ -200,6 +200,8 @@
/bcda/${env}/sensitive/worker/BB_CLIENT_CERT.pem: ENC[AES256_GCM,data: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,iv:yMdChzg2fjmCbjmchIuuSvoz+/6+FQILgqRhNs+2N2g=,tag:o8XGfQaH0jsabSkDO8q9NA==,type:str]
/bcda/${env}/sensitive/worker/BB_CLIENT_KEY.pem: ENC[AES256_GCM,data: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,iv:UBLyIpokk1J1orOziKIMepzkV5sbFem4U1Bwzan8EKo=,tag:h1232aCofGKIEVwJlQV9wg==,type:str]
/bcda/${env}/sensitive/worker/config.yml: ENC[AES256_GCM,data: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,iv:J/1ZH/uHwqaQJZY5GBaAtOJ90chS+IaoMEl203kzEAE=,tag:yBlnx3KxItJcRSvEoM/K5w==,type:str]
+/bcda/${env}/bene_prefs/sensitive/iam_bucket_role_arn: ENC[AES256_GCM,data:fODp/5DSn5veEd2N7RYb9tQgHej3L7ut/Pfie7wKLBuDSM6vx2PvrJ4xQHPgd5pV6jOSxx1w17zc8ErT80CwCYf+agYki9zz/vRaMsJhnoCRpNxUZgAEB6LUqYTf/z8Wosod,iv:RE7M4VbXtYfvnUATm/LD+aztuMDUET2nWims3r6GYyA=,tag:VOXHOFICu4GfsU+KxILzJA==,type:str]
+/bcda/${env}/bene_prefs/sensitive/sns_topic_arn: ENC[AES256_GCM,data:S2bIl7FM5W/WAN60B1Ls0i2m8sP60SW/ZxRPWXXrOwcP7EMlif0yMAcx21IdIaH+Ak0N500+TvwloK7VRh0hAbLt/ff5tU8=,iv:NIusJubYySLkhYWlCUoHXQWtaqIQuTFVy/XmGCtvQEo=,tag:vn8RWx3TvmjpjT7Grn1u8Q==,type:str]
sops:
kms:
- arn: arn:aws:kms:us-east-1:${ACCOUNT_ID}:alias/bcda-prod
diff --git a/ops/services/20-bene-prefs/README.md b/ops/services/20-bene-prefs/README.md
new file mode 100644
index 000000000..9c0cc2073
--- /dev/null
+++ b/ops/services/20-bene-prefs/README.md
@@ -0,0 +1,79 @@
+
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 5.100.0 |
+
+
+## Requirements
+
+No requirements.
+
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [parent\_env](#input\_parent\_env) | The parent environment of the current solution. Will correspond with `terraform.workspace`".
Necessary on `tofu init` and `tofu workspace select` \_only\_. In all other situations, parent env
will be divined from `terraform.workspace`. | `string` | `null` | no |
+| [region](#input\_region) | n/a | `string` | `"us-east-1"` | no |
+| [secondary\_region](#input\_secondary\_region) | n/a | `string` | `"us-west-2"` | no |
+
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [bucket](#module\_bucket) | github.com/CMSgov/cdap//terraform/modules/bucket | 787224b |
+| [platform](#module\_platform) | github.com/CMSgov/cdap//terraform/modules/platform | ff2ef539fb06f2c98f0e3ce0c8f922bdacb96d66 |
+
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_policy.assume_bucket_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.default_function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_lambda_event_source_mapping.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource |
+| [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
+| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group_rule.db](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
+| [aws_sns_topic_subscription.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource |
+| [aws_sqs_queue.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource |
+| [aws_iam_policy_document.assume_bucket_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.default_function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_rds_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/rds_cluster) | data source |
+| [aws_security_groups.db](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/security_groups) | data source |
+
+
+## Outputs
+
+No outputs.
+
\ No newline at end of file
diff --git a/ops/services/20-bene-prefs/main.tf b/ops/services/20-bene-prefs/main.tf
new file mode 100644
index 000000000..ea582474f
--- /dev/null
+++ b/ops/services/20-bene-prefs/main.tf
@@ -0,0 +1,301 @@
+locals {
+ service = "bene-prefs"
+ default_tags = module.platform.default_tags
+ env = terraform.workspace
+
+ account_id = module.platform.aws_caller_identity.account_id
+ kms_key_arn_primary = module.platform.kms_alias_primary.target_key_arn
+ kms_key_arn_secondary = module.platform.kms_alias_secondary.target_key_arn
+ name_prefix = "${local.service_prefix}-${local.service}"
+ private_subnets = nonsensitive(toset(keys(module.platform.private_subnets)))
+}
+
+module "platform" {
+ source = "github.com/CMSgov/cdap//terraform/modules/platform?ref=ff2ef539fb06f2c98f0e3ce0c8f922bdacb96d66"
+
+ providers = { aws = aws, aws.secondary = aws.secondary }
+
+ app = local.app
+ env = local.env
+ root_module = "https://github.com/CMSgov/bcda-app/tree/main/ops/services/10-config"
+ service = local.service
+ ssm_root_map = {
+ bene_prefs = "/bcda/${local.env}/bene_prefs/"
+ }
+}
+
+data "aws_rds_cluster" "this" {
+ cluster_identifier = "${local.app}-${local.env}-aurora"
+}
+
+data "aws_security_groups" "db" {
+ tags = {
+ Name = "bcda-${local.env}-db"
+ }
+}
+
+resource "aws_security_group_rule" "db" {
+ type = "ingress"
+ from_port = data.aws_rds_cluster.this.port
+ to_port = data.aws_rds_cluster.this.port
+ protocol = "tcp"
+ security_group_id = one([data.aws_security_groups.db.ids])[0]
+ source_security_group_id = aws_security_group.this.id
+}
+
+# ---------------------------------------------------------------------------
+# Managed policies
+# ---------------------------------------------------------------------------
+
+data "aws_iam_policy_document" "assume_bucket_role" {
+ statement {
+ sid = "AssumeBucketRole"
+ actions = ["sts:AssumeRole"]
+ resources = [module.platform.ssm.bene_prefs.iam_bucket_role_arn.value]
+ }
+}
+
+resource "aws_iam_policy" "assume_bucket_role" {
+ name = "bcda-${local.env}-${local.service}-assume-bucket-role"
+ path = module.platform.iam_defaults.path
+ description = "Allows ${local.service} to assume the S3 bucket role from SSM."
+
+ policy = data.aws_iam_policy_document.assume_bucket_role.json
+}
+
+data "aws_iam_policy_document" "default_function" {
+ statement {
+ sid = "SsmSqsLogsEc2"
+ actions = [
+ "ssm:GetParameters",
+ "ssm:GetParameter",
+ "sqs:ReceiveMessage",
+ "sqs:GetQueueAttributes",
+ "sqs:DeleteMessage",
+ "logs:PutLogEvents",
+ "logs:CreateLogStream",
+ "logs:CreateLogGroup",
+ ]
+ resources = ["*"] #TODO: Consider splitting into discrete statements/policy allowances
+ }
+ statement {
+
+ sid = "KmsEncryptDecrypt"
+ actions = [
+ "kms:GenerateDataKey",
+ "kms:Encrypt",
+ "kms:Decrypt",
+ ]
+ resources = [
+ local.kms_key_arn_primary,
+ local.kms_key_arn_secondary,
+ ]
+ }
+}
+
+resource "aws_iam_policy" "default_function" {
+ name = "bcda-${local.env}-${local.service}-default-function"
+ path = module.platform.iam_defaults.path
+ description = "SSM, SQS, CloudWatch Logs, EC2 networking, and KMS permissions for ${local.service}."
+
+ policy = data.aws_iam_policy_document.default_function.json
+}
+
+# ---------------------------------------------------------------------------
+# IAM role
+# ---------------------------------------------------------------------------
+
+resource "aws_iam_role" "this" {
+ assume_role_policy = jsonencode({
+ Version = "2012-10-17"
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "lambda.amazonaws.com"
+ }
+ },
+ {
+ Action = [
+ "sts:TagSession",
+ "sts:AssumeRoleWithWebIdentity",
+ ]
+ Condition = {
+ StringEquals = {
+ "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
+ }
+ StringLike = {
+ "token.actions.githubusercontent.com:sub" = "repo:CMSgov/bcda-app:*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Federated = "arn:aws:iam::${local.account_id}:oidc-provider/token.actions.githubusercontent.com"
+ }
+ },
+ {
+ Action = [
+ "sts:TagSession",
+ "sts:AssumeRole",
+ ]
+ Effect = "Allow"
+ Principal = {
+ AWS = [
+ module.platform.kion_roles["ct-ado-dasg-application-admin"].arn,
+ module.platform.kion_roles["ct-ado-bcda-application-admin"].arn,
+ ]
+ }
+ },
+ ]
+ })
+
+ force_detach_policies = true
+ name = "bcda-${local.env}-${local.service}"
+ path = module.platform.iam_defaults.path
+ permissions_boundary = module.platform.iam_defaults.boundary
+}
+
+resource "aws_iam_role_policy_attachment" "this" {
+ #TODO: Complexity below is for eventual targeting of `test` and `prod` environments
+ for_each = { for k, v in {
+ assume_bucket_role = try(aws_iam_policy.assume_bucket_role.arn, "")
+ default_function = try(aws_iam_policy.default_function.arn, "")
+ } : k => v if length(v) > 0 }
+
+ role = aws_iam_role.this.name
+ policy_arn = each.value
+}
+
+module "bucket" {
+ source = "github.com/CMSgov/cdap//terraform/modules/bucket?ref=787224b"
+
+ app = local.app
+ env = local.env
+ name = "${local.app}-${local.env}-${local.service}-lambda"
+ ssm_parameter = "/${local.app}/${local.env}/${local.service}/nonsensitive/bucket_name"
+}
+
+resource "aws_lambda_function" "this" {
+ s3_key = "function-3540b70393e3dc30f375eee2e8635a65c6f21036.zip"
+ s3_bucket = module.bucket.id
+ package_type = "Zip"
+ handler = "bootstrap"
+
+ function_name = local.name_prefix
+ description = "Ingests the most recent beneficiary opt-out list from BFD"
+ kms_key_arn = local.kms_key_arn_primary
+ memory_size = 128
+ reserved_concurrent_executions = 1
+ role = aws_iam_role.this.arn
+ runtime = "provided.al2023"
+ skip_destroy = false
+ timeout = 900
+ architectures = [
+ "x86_64",
+ ]
+
+ tags = {
+ code = "https://github.com/CMSgov/bcda-app/tree/main/bcda/lambda/optout"
+ }
+
+ lifecycle {
+ ignore_changes = [
+ s3_object_version,
+ s3_key,
+ ]
+ }
+
+ environment {
+ variables = {
+ APP_NAME = local.name_prefix
+ DB_HOST = "postgres://${data.aws_rds_cluster.this.endpoint}:${data.aws_rds_cluster.this.port}/bcda"
+ ENV = local.env
+ }
+ }
+
+ ephemeral_storage {
+ size = 512
+ }
+
+ logging_config {
+ log_format = "Text"
+ log_group = "/aws/lambda/bcda-${local.env}-${local.service}"
+ }
+
+ tracing_config {
+ mode = "Active"
+ }
+
+ vpc_config {
+ ipv6_allowed_for_dual_stack = false
+ security_group_ids = [aws_security_group.this.id]
+ subnet_ids = local.private_subnets
+ }
+}
+
+resource "aws_security_group" "this" {
+ description = "Temporary SG for ${local.name_prefix}"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
+ ]
+ description = ""
+ from_port = 0
+ ipv6_cidr_blocks = [
+ "::/0",
+ ]
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
+ },
+ ]
+ name = local.name_prefix
+ tags = { Name = local.name_prefix }
+}
+
+resource "aws_sqs_queue" "this" {
+ content_based_deduplication = false
+ delay_seconds = 0
+ fifo_queue = false
+ kms_data_key_reuse_period_seconds = 300
+ kms_master_key_id = local.kms_key_arn_primary
+ name = local.name_prefix
+ policy = jsonencode({
+ Version = "2012-10-17"
+ Statement = [
+ {
+ Action = "sqs:SendMessage"
+ Condition = {
+ ArnEquals = {
+ "aws:SourceArn" = module.platform.ssm.bene_prefs.sns_topic_arn.value
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Service = "sns.amazonaws.com"
+ }
+ Resource = "arn:aws:sqs:us-east-1:${local.account_id}:${local.name_prefix}"
+ Sid = "SnsSendMessage"
+ },
+ ]
+ })
+ receive_wait_time_seconds = 0
+ visibility_timeout_seconds = 900
+}
+
+resource "aws_sns_topic_subscription" "this" {
+ endpoint = aws_sqs_queue.this.arn
+ protocol = "sqs"
+ topic_arn = module.platform.ssm.bene_prefs.sns_topic_arn.value
+}
+
+resource "aws_lambda_event_source_mapping" "this" {
+ event_source_arn = aws_sqs_queue.this.arn
+ function_name = aws_lambda_function.this.function_name
+ batch_size = 1
+ enabled = true
+}
diff --git a/ops/services/20-bene-prefs/tofu.tf b/ops/services/20-bene-prefs/tofu.tf
new file mode 120000
index 000000000..007a2292a
--- /dev/null
+++ b/ops/services/20-bene-prefs/tofu.tf
@@ -0,0 +1 @@
+../root.tofu.tf
\ No newline at end of file
diff --git a/ops/services/README.md b/ops/services/README.md
new file mode 100644
index 000000000..f0b30b1a8
--- /dev/null
+++ b/ops/services/README.md
@@ -0,0 +1,29 @@
+# BCDA Terraservices
+- each directory herein is an opinionated module for terraform (tofu)
+- each module is terraform workspace-enabled, unless otherwise specified
+- the two-digit numbers for each module is significant and identifies the sequence of operations for ordered module application
+
+## Requirements
+Some of these modules have requirements for local development or remote CI. Those are including (but not limited to):
+- jq *and* yq for manipulating json and yaml documents
+- awscli
+
+## Applying Changes
+Applying changes for these modules requires initialization of the state **and** selection of the appropriate environmental workspace.
+Both can be achieved with the following commands:
+
+```sh
+### prod environment
+TF_WORKSPACE=default tofu init -var parent_env=prod -reconfigure && tofu workspace select -var parent_env=prod -or-create prod
+
+### sandbox environment
+TF_WORKSPACE=default tofu init -var parent_env=sandbox -reconfigure && tofu workspace select -var parent_env=sandbox -or-create sandbox
+
+### test environment
+TF_WORKSPACE=default tofu init -var parent_env=test -reconfigure && tofu workspace select -var parent_env=test -or-create test
+
+### dev environment
+TF_WORKSPACE=default tofu init -var parent_env=dev -reconfigure && tofu workspace select -var parent_env=dev -or-create dev
+```
+
+After the state has been initialized and the workspace selected, users or automation can apply changes by running `tofu apply`.
diff --git a/ops/services/root.tofu.tf b/ops/services/root.tofu.tf
index fb48b7b24..5b81e0078 100644
--- a/ops/services/root.tofu.tf
+++ b/ops/services/root.tofu.tf
@@ -1,12 +1,22 @@
# This root tofu.tf is symlink'd to by all per-env Terraservices. Changes to this tofu.tf apply to
# _all_ Terraservices, so be careful!
-variable "env" {
- description = "The application environment (dev, test, sandbox, prod)"
- type = string
- validation {
- condition = contains(["dev", "test", "sandbox", "prod"], var.env)
- error_message = "Valid value for env is dev, test, sandbox, or prod."
+locals {
+ app = "bcda"
+ established_envs = ["dev", "test", "sandbox", "prod"]
+ service_prefix = "${local.app}-${local.env}"
+
+ parent_env = coalesce(
+ var.parent_env,
+ one([for x in local.established_envs : x if can(regex("${x}$$", terraform.workspace))]),
+ "invalid-parent-environment;do-better"
+ )
+
+ state_buckets = {
+ dev = "bcda-dev-tfstate-20250409202710600700000001"
+ test = "bcda-test-tfstate-20250409171646342600000001"
+ sandbox = "bcda-sandbox-tfstate-20250416201512973800000001"
+ prod = "bcda-prod-tfstate-20250411203841436200000001"
}
}
@@ -22,33 +32,43 @@ variable "secondary_region" {
type = string
}
-locals {
- app = "bcda"
- env = var.env
- service_prefix = "${local.app}-${local.env}"
-
- state_buckets = {
- dev = "bcda-dev-tfstate-20250409202710600700000001"
- test = "bcda-test-tfstate-20250409171646342600000001"
- sandbox = "bcda-sandbox-tfstate-20250416201512973800000001"
- prod = "bcda-prod-tfstate-20250411203841436200000001"
+variable "parent_env" {
+ description = <<-EOF
+ The parent environment of the current solution. Will correspond with `terraform.workspace`".
+ Necessary on `tofu init` and `tofu workspace select` _only_. In all other situations, parent env
+ will be divined from `terraform.workspace`.
+ EOF
+ type = string
+ nullable = true
+ default = null
+ validation {
+ condition = var.parent_env == null || one([for x in local.established_envs : x if var.parent_env == x && endswith(terraform.workspace, x)]) != null
+ error_message = "Invalid parent environment name."
}
}
provider "aws" {
region = var.region
+ default_tags {
+ tags = local.default_tags
+ }
}
provider "aws" {
- alias = "secondary"
+ alias = "secondary"
+
region = var.secondary_region
+ default_tags {
+ tags = local.default_tags
+ }
}
terraform {
backend "s3" {
- bucket = local.state_buckets[local.env]
+ bucket = local.state_buckets[local.parent_env]
+ encrypt = true
key = "ops/services/${local.service}/tofu.tfstate"
- use_lockfile = true
region = var.region
+ use_lockfile = true
}
}