Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions terraform/backends/cdap-insights-mgmt.s3.hcl
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously, we don't use the hcl suffix on the other backends, so this is non-standard. However, this would be a nice touch to enable native highlighting/formatting in IDEs, etc.

Copy link
Member

@gsf gsf Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our backends use the *.s3.tfbackend naming convention as recommended at https://developer.hashicorp.com/terraform/language/backend#file in order to hopefully look familiar to any engineer coming from other terraform projects. I'm not wedded to anything in the HashiCorp docs, however, and I like giving the editor hints for file formatting. Also, *.s3.hcl is shorter, which is always a plus.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bucket = "cdap-mgmt-tfstate-20250930180004007700000001"
use_lockfile = true
28 changes: 17 additions & 11 deletions terraform/modules/bucket/main.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module "bucket_key" {
source = "../key"
name = "${var.name}-bucket"
description = "For ${var.name} S3 bucket and its access logs"
user_roles = var.cross_account_read_roles
Comment on lines -1 to -5
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change probably shouldn't be accepted until other module declaration sources of the modules/bucket child are pinned to existing references.

locals {
cdap_ssm = zipmap(
data.aws_ssm_parameters_by_path.cdap.names,
data.aws_ssm_parameters_by_path.cdap.values
)
access_logs_bucket = lookup(local.cdap_ssm, "/cdap/bucket-access-logs-bucket", null)
}

resource "aws_s3_bucket" "this" {
Expand Down Expand Up @@ -99,19 +100,24 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
}
}

data "aws_iam_account_alias" "current" {}
data "aws_ssm_parameters_by_path" "cdap" {
path = "/cdap"
recursive = true
}
Comment on lines +103 to +106
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the aws_ssm_parameters_by_path data source allows for empty results. Paired with the call to the zipmap() in local.cdap_ssm and the lookup() on the same for local.access_logs_bucket, this helps us avoid failures on missing configuration and obviates the need for hard-coding bucket-access-logs buckets.


data "aws_s3_bucket" "bucket_access_logs" {
bucket = (data.aws_iam_account_alias.current.account_alias == "aws-cms-oeda-bcda-prod"
? "bucket-access-logs-20250411172631068600000001"
: "bucket-access-logs-20250409172631068600000001"
)
count = local.access_logs_bucket == null ? 0 : 1

bucket = local.access_logs_bucket
}


resource "aws_s3_bucket_logging" "this" {
count = local.access_logs_bucket == null ? 0 : 1

bucket = aws_s3_bucket.this.id

target_bucket = data.aws_s3_bucket.bucket_access_logs.id
target_bucket = data.aws_s3_bucket.bucket_access_logs[0].id
target_prefix = "${aws_s3_bucket.this.id}/"
}

Expand Down
15 changes: 0 additions & 15 deletions terraform/modules/bucket/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,3 @@ output "id" {
description = "ID for the S3 bucket"
value = aws_s3_bucket.this.id
}

output "key_alias" {
description = "Key Alias for this bucket"
value = module.bucket_key.alias
}

output "key_arn" {
description = "KEY ARN for this bucket"
value = module.bucket_key.arn
}

output "key_id" {
description = "KEY identifier for this bucket"
value = module.bucket_key.id
}
1 change: 0 additions & 1 deletion terraform/modules/platform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ No modules.
| <a name="output_primary_region"></a> [primary\_region](#output\_primary\_region) | The primary data.aws\_region object from the current caller identity |
| <a name="output_private_subnets"></a> [private\_subnets](#output\_private\_subnets) | Map of current VPC **private** [aws\_subnet data sources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet), keyed by `subnet_id` |
| <a name="output_public_subnets"></a> [public\_subnets](#output\_public\_subnets) | Map of current VPC **public** [aws\_subnet data sources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet), keyed by `id` |
| <a name="output_region_name"></a> [region\_name](#output\_region\_name) | **Deprecated**. Use `primary_region.name`. The region name associated with the current caller identity |
| <a name="output_sdlc_env"></a> [sdlc\_env](#output\_sdlc\_env) | The SDLC (production vs non-production) environment. |
| <a name="output_secondary_region"></a> [secondary\_region](#output\_secondary\_region) | The secondary data.aws\_region object associated with the secondary region. |
| <a name="output_security_groups"></a> [security\_groups](#output\_security\_groups) | Map of current VPC's common [aws\_security\_group data sources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/security_group#attribute-reference), keyed by `name` |
Expand Down
6 changes: 0 additions & 6 deletions terraform/modules/platform/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ output "service" {
value = local.service
}

output "region_name" {
description = "**Deprecated**. Use `primary_region.name`. The region name associated with the current caller identity"
sensitive = false
value = data.aws_region.primary.name
}

output "primary_region" {
description = "The primary data.aws_region object from the current caller identity"
sensitive = false
Expand Down
12 changes: 7 additions & 5 deletions terraform/modules/standards/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ locals {

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~>5 |
| <a name="provider_aws.secondary"></a> [aws.secondary](#provider\_aws.secondary) | ~>5 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | 6.14.1 |
| <a name="provider_aws.secondary"></a> [aws.secondary](#provider\_aws.secondary) | 6.14.1 |

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Expand All @@ -68,7 +68,7 @@ locals {

| Name | Version |
|------|---------|
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~>5 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~>6 |

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Expand Down Expand Up @@ -117,13 +117,15 @@ No modules.

| Name | Description |
|------|-------------|
| <a name="output_account_id"></a> [account\_id](#output\_account\_id) | The AWS account ID associated with the current caller identity |
| <a name="output_account_id"></a> [account\_id](#output\_account\_id) | Deprecated. Use `aws_caller_identity.account_id`. The AWS account ID associated with the current caller identity |
| <a name="output_app"></a> [app](#output\_app) | The short name for the delivery team or ADO. |
| <a name="output_aws_caller_identity"></a> [aws\_caller\_identity](#output\_aws\_caller\_identity) | The current data.aws\_caller\_identity object. |
| <a name="output_default_permissions_boundary"></a> [default\_permissions\_boundary](#output\_default\_permissions\_boundary) | Default permissions boundary [aws\_iam\_policy data source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy#attribute-reference) |
| <a name="output_default_tags"></a> [default\_tags](#output\_default\_tags) | Map of tags for use in AWS provider block `default_tags`. Merges collection of standard tags with optional, user-specificed `additional_tags` |
| <a name="output_env"></a> [env](#output\_env) | The solution's application environment name. |
| <a name="output_is_ephemeral_env"></a> [is\_ephemeral\_env](#output\_is\_ephemeral\_env) | Returns true when environment is \_ephemeral\_, false when \_established\_ |
| <a name="output_parent_env"></a> [parent\_env](#output\_parent\_env) | The solution's source environment. For established environments this is equal to the environment's name |
| <a name="output_primary_region"></a> [primary\_region](#output\_primary\_region) | The primary data.aws\_region object from the current caller identity |
| <a name="output_region_name"></a> [region\_name](#output\_region\_name) | **Deprecated**. Use `primary_region.name`. The region name associated with the current caller identity |
| <a name="output_secondary_region"></a> [secondary\_region](#output\_secondary\_region) | The secondary data.aws\_region object associated with the secondary region. |
| <a name="output_service"></a> [service](#output\_service) | The name of the current service or terraservice. |
<!-- END_TF_DOCS -->
4 changes: 4 additions & 0 deletions terraform/modules/standards/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ locals {
root_module = var.root_module
service = var.service

established_envs = ["test", "dev", "sandbox", "prod", "mgmt"]
parent_env = one([for x in local.established_envs : x if can(regex("${x}$$", local.env))])

static_tags = {
application = local.app
business = "oeda"
environment = local.env
parent_env = local.parent_env
service = local.service
terraform = true
tf_root_module = local.root_module
Expand Down
26 changes: 19 additions & 7 deletions terraform/modules/standards/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ output "service" {
value = local.service
}

output "region_name" {
description = "**Deprecated**. Use `primary_region.name`. The region name associated with the current caller identity"
sensitive = false
value = data.aws_region.this.name
}

Comment on lines -13 to -18
Copy link
Member Author

@mjburling mjburling Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change.

As noted, this was already deprecated in favor of the primary_region output. With updates to aws provider version 6, data.aws_region has a deprecated name and an attribute, in favor of region, so... references like this would be most correct as data.aws_region.this.region. ¯\_(ツ)_/¯

output "primary_region" {
description = "The primary data.aws_region object from the current caller identity"
sensitive = false
Expand All @@ -29,11 +23,17 @@ output "secondary_region" {
}

output "account_id" {
description = "The AWS account ID associated with the current caller identity"
description = "Deprecated. Use `aws_caller_identity.account_id`. The AWS account ID associated with the current caller identity"
sensitive = true
value = data.aws_caller_identity.this.account_id
}
Comment on lines 25 to 29
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deprecating account_id to match how the platform module surfaces this. It might be interesting to start using the opentofu-specific deprecation fields once we're confident that all terraform is executed under opentofu....


output "aws_caller_identity" {
description = "The current data.aws_caller_identity object."
sensitive = true
value = data.aws_caller_identity.this
}

output "env" {
description = "The solution's application environment name."
sensitive = false
Expand All @@ -51,3 +51,15 @@ output "default_permissions_boundary" {
sensitive = false
value = data.aws_iam_policy.permissions_boundary
}

output "is_ephemeral_env" {
description = "Returns true when environment is _ephemeral_, false when _established_"
sensitive = false
value = local.env != local.parent_env
}

output "parent_env" {
description = "The solution's source environment. For established environments this is equal to the environment's name"
sensitive = false
value = local.parent_env
}
2 changes: 1 addition & 1 deletion terraform/modules/standards/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>5"
version = "~>6"
configuration_aliases = [aws.secondary]
}
}
Expand Down
85 changes: 85 additions & 0 deletions terraform/services/insights/mgmt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# CDAP Insights Management Environment

This root module configures the fundamental platform resources in the AWS DASG Insights account, including IAM, QuickSight, and SSM Parameters.

## Dependencies
- `services/kms-keys`
- `services/bucket-access-logging`
- `services/tfstate`

## Bootstrapping

This module is intended to serve the single `mgmt` environment. Initialization is done through the following:

```sh
tofu init -backend-config="../../../backends/cdap-insights-mgmt.s3.hcl"
```

<!-- BEGIN_TF_DOCS -->
<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Manually updating sections between TF_DOCS tags may be overwritten.
See https://terraform-docs.io/user-guide/configuration/ for more information.
-->
## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 6.14.1 |
| <a name="provider_aws.secondary"></a> [aws.secondary](#provider\_aws.secondary) | 6.14.1 |

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Manually updating sections between TF_DOCS tags may be overwritten.
See https://terraform-docs.io/user-guide/configuration/ for more information.
-->
## Requirements

No requirements.

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Manually updating sections between TF_DOCS tags may be overwritten.
See https://terraform-docs.io/user-guide/configuration/ for more information.
-->
## Inputs

No inputs.

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Manually updating sections between TF_DOCS tags may be overwritten.
See https://terraform-docs.io/user-guide/configuration/ for more information.
-->
## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_sops"></a> [sops](#module\_sops) | ../../../modules/sops | n/a |
| <a name="module_standards"></a> [standards](#module\_standards) | ../../../modules/standards | n/a |

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Manually updating sections between TF_DOCS tags may be overwritten.
See https://terraform-docs.io/user-guide/configuration/ for more information.
-->
## Resources

| Name | Type |
|------|------|
| [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_quicksight_account_settings.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/quicksight_account_settings) | resource |
| [aws_quicksight_ip_restriction.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/quicksight_ip_restriction) | resource |
| [aws_kms_alias.primary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_alias) | data source |
| [aws_kms_alias.secondary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_alias) | data source |

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Manually updating sections between TF_DOCS tags may be overwritten.
See https://terraform-docs.io/user-guide/configuration/ for more information.
-->
## Outputs

No outputs.
<!-- END_TF_DOCS -->
32 changes: 32 additions & 0 deletions terraform/services/insights/mgmt/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
resource "aws_iam_role" "this" {
assume_role_policy = jsonencode(
{
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "quicksight.amazonaws.com"
}
},
]
Version = "2012-10-17"
}
)
force_detach_policies = true
max_session_duration = 3600
name = "${local.service_prefix}-quicksight-service"
path = "/service-role/"
}

# Basic Policy Attachments, Further Attachments Necessary
resource "aws_iam_role_policy_attachment" "this" {
for_each = toset([
"arn:aws:iam::aws:policy/service-role/AmazonSageMakerQuickSightVPCPolicy", #AWS-managed, allowing CRUD on ENIs, Limited VPC Resources
"arn:aws:iam::aws:policy/service-role/AWSQuickSightListIAM", #AWS-managed, allows `iam:List*`
"arn:aws:iam::aws:policy/service-role/AWSQuicksightAthenaAccess", #AWS-managed, allows access to glue, athena, and athena-related s3 resources
])

role = aws_iam_role.this.name
policy_arn = each.value
}
50 changes: 50 additions & 0 deletions terraform/services/insights/mgmt/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
locals {
app = "cdap"
env = "mgmt"
service = "insights"
service_prefix = "${local.app}-${local.env}-${local.service}"
account_id = module.standards.aws_caller_identity.id

kms_key_aliases = {
kms_alias_primary = data.aws_kms_alias.primary,
kms_alias_secondary = data.aws_kms_alias.secondary
}

cdap_ssm = zipmap(
data.aws_ssm_parameters_by_path.cdap.names,
data.aws_ssm_parameters_by_path.cdap.values
)

ip_restrictions = jsondecode(lookup(nonsensitive(local.cdap_ssm), "/cdap/mgmt/insights/sensitive/ip-restrictions", "{}"))
}

module "standards" {
source = "../../../modules/standards" #TODO: Update with appropriate reference

app = local.app
env = local.env
root_module = "https://github.com/CMSgov/cdap/tree/main/terraform/services/insights/mgmt"
service = local.service
providers = { aws = aws, aws.secondary = aws.secondary }
}

data "aws_kms_alias" "primary" {
name = "alias/${local.app}-${local.env}"
}

data "aws_kms_alias" "secondary" {
provider = aws.secondary
name = "alias/${local.app}-${local.env}"
}

module "sops" {
source = "../../../modules/sops" #TODO: Update with appropriate reference

platform = merge(module.standards, local.kms_key_aliases)
}

data "aws_ssm_parameters_by_path" "cdap" {
path = "/cdap"
recursive = true
with_decryption = true
}
15 changes: 15 additions & 0 deletions terraform/services/insights/mgmt/quicksight.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
resource "aws_quicksight_account_settings" "this" {
aws_account_id = local.account_id
default_namespace = "default"
termination_protection_enabled = true
}

resource "aws_quicksight_ip_restriction" "this" {
enabled = length(local.ip_restrictions) > 0

ip_restriction_rule_map = local.ip_restrictions

depends_on = [
module.sops
]
}
Loading