diff --git a/terraform/backends/cdap-insights-mgmt.s3.hcl b/terraform/backends/cdap-insights-mgmt.s3.hcl new file mode 100644 index 00000000..61b08ed7 --- /dev/null +++ b/terraform/backends/cdap-insights-mgmt.s3.hcl @@ -0,0 +1,2 @@ +bucket = "cdap-mgmt-tfstate-20250930180004007700000001" +use_lockfile = true diff --git a/terraform/modules/bucket/main.tf b/terraform/modules/bucket/main.tf index 777937b4..179ba54f 100644 --- a/terraform/modules/bucket/main.tf +++ b/terraform/modules/bucket/main.tf @@ -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 +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" { @@ -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 +} 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}/" } diff --git a/terraform/modules/bucket/outputs.tf b/terraform/modules/bucket/outputs.tf index 0e72aae4..7be085a8 100644 --- a/terraform/modules/bucket/outputs.tf +++ b/terraform/modules/bucket/outputs.tf @@ -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 -} diff --git a/terraform/modules/platform/README.md b/terraform/modules/platform/README.md index 1cb5714b..dce08063 100644 --- a/terraform/modules/platform/README.md +++ b/terraform/modules/platform/README.md @@ -152,7 +152,6 @@ No modules. | [primary\_region](#output\_primary\_region) | The primary data.aws\_region object from the current caller identity | | [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` | | [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` | -| [region\_name](#output\_region\_name) | **Deprecated**. Use `primary_region.name`. The region name associated with the current caller identity | | [sdlc\_env](#output\_sdlc\_env) | The SDLC (production vs non-production) environment. | | [secondary\_region](#output\_secondary\_region) | The secondary data.aws\_region object associated with the secondary region. | | [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` | diff --git a/terraform/modules/platform/outputs.tf b/terraform/modules/platform/outputs.tf index 246e7621..514b0898 100644 --- a/terraform/modules/platform/outputs.tf +++ b/terraform/modules/platform/outputs.tf @@ -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 diff --git a/terraform/modules/standards/README.md b/terraform/modules/standards/README.md index 59082826..1f38539c 100644 --- a/terraform/modules/standards/README.md +++ b/terraform/modules/standards/README.md @@ -56,8 +56,8 @@ locals { | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~>5 | -| [aws.secondary](#provider\_aws.secondary) | ~>5 | +| [aws](#provider\_aws) | 6.14.1 | +| [aws.secondary](#provider\_aws.secondary) | 6.14.1 | diff --git a/terraform/modules/standards/main.tf b/terraform/modules/standards/main.tf index 26bd2c62..53022807 100644 --- a/terraform/modules/standards/main.tf +++ b/terraform/modules/standards/main.tf @@ -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 diff --git a/terraform/modules/standards/outputs.tf b/terraform/modules/standards/outputs.tf index 299499aa..79fdd0ba 100644 --- a/terraform/modules/standards/outputs.tf +++ b/terraform/modules/standards/outputs.tf @@ -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 -} - output "primary_region" { description = "The primary data.aws_region object from the current caller identity" sensitive = false @@ -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 } +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 @@ -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 +} diff --git a/terraform/modules/standards/terraform.tf b/terraform/modules/standards/terraform.tf index bd633a74..0885f73d 100644 --- a/terraform/modules/standards/terraform.tf +++ b/terraform/modules/standards/terraform.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~>5" + version = "~>6" configuration_aliases = [aws.secondary] } } diff --git a/terraform/services/insights/mgmt/README.md b/terraform/services/insights/mgmt/README.md new file mode 100644 index 00000000..2cde4a1c --- /dev/null +++ b/terraform/services/insights/mgmt/README.md @@ -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" +``` + + + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 6.14.1 | +| [aws.secondary](#provider\_aws.secondary) | 6.14.1 | + + +## Requirements + +No requirements. + + +## Inputs + +No inputs. + + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [sops](#module\_sops) | ../../../modules/sops | n/a | +| [standards](#module\_standards) | ../../../modules/standards | n/a | + + +## 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 | + + +## Outputs + +No outputs. + diff --git a/terraform/services/insights/mgmt/iam.tf b/terraform/services/insights/mgmt/iam.tf new file mode 100644 index 00000000..8972d67d --- /dev/null +++ b/terraform/services/insights/mgmt/iam.tf @@ -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 +} diff --git a/terraform/services/insights/mgmt/main.tf b/terraform/services/insights/mgmt/main.tf new file mode 100644 index 00000000..bdd1058e --- /dev/null +++ b/terraform/services/insights/mgmt/main.tf @@ -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 +} diff --git a/terraform/services/insights/mgmt/quicksight.tf b/terraform/services/insights/mgmt/quicksight.tf new file mode 100644 index 00000000..25df1bac --- /dev/null +++ b/terraform/services/insights/mgmt/quicksight.tf @@ -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 + ] +} diff --git a/terraform/services/insights/mgmt/terraform.tf b/terraform/services/insights/mgmt/terraform.tf new file mode 100644 index 00000000..7c93cf58 --- /dev/null +++ b/terraform/services/insights/mgmt/terraform.tf @@ -0,0 +1,20 @@ +terraform { + backend "s3" { + key = "insights/mgmt/terraform.tfstate" + } +} + +provider "aws" { + region = "us-east-1" + default_tags { + tags = module.standards.default_tags + } +} + +provider "aws" { + alias = "secondary" + region = "us-west-2" + default_tags { + tags = module.standards.default_tags + } +} diff --git a/terraform/services/insights/mgmt/values/mgmt.sopsw.yaml b/terraform/services/insights/mgmt/values/mgmt.sopsw.yaml new file mode 100644 index 00000000..a8a0f5b5 --- /dev/null +++ b/terraform/services/insights/mgmt/values/mgmt.sopsw.yaml @@ -0,0 +1,17 @@ +/cdap/mgmt/insights/sensitive/production-account: ENC[AES256_GCM,data:Q4IQ10q1U9Z9LHsV,iv:msDZEXCE1nDfDiT5OCwSy6P6Ux2+rg88MlPgBotKEGA=,tag:Ux+aJ1iwcf8dVu4v4gStXA==,type:int] +/cdap/mgmt/insights/sensitive/ip-restrictions: ENC[AES256_GCM,data:TH/Wefc/8xxF9qB+q/rp9iqhCBewzvz5K0o=,iv:0zj/O2MYd2hrbAZjpf/19f6ux/BVbEFawA6PujTVrn8=,tag:EoYOTqJjTS5eOM7RQHWC6w==,type:str] +/bb2/mgmt/insights/sensitive/production-account: ENC[AES256_GCM,data:DcCO+t2uT1KbBvLG,iv:ExMspQ9CN5hq8bPLFL5KKWiDuI6yXpTnLSf6kZQ4fQM=,tag:XmksqLTyohSNBxS6J6p38A==,type:int] +/bfd/mgmt/insights/sensitive/production-account: ENC[AES256_GCM,data:7+mpZZm1qvASmcTs,iv:mh1lrbiSGpT+yR+6QrlcHWlnWd3oFZr9wm40Y0RBEhQ=,tag:5do/BZFSoOrLIKcch78/Ug==,type:int] +sops: + kms: + - arn: arn:aws:kms:us-east-1:${ACCOUNT_ID}:alias/cdap-mgmt + created_at: "2025-09-30T19:13:00Z" + enc: AQICAHiKQjEEYvJPdywF5tXeCz/FMh8ciiBXHJWYytbH1uw6WAHXW/jBPCgfFFvNAduurTWAAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMYzrROySfTwxycQu4AgEQgDuf5uH3iGb0blD2+r43vtd6R77A+/Zk77BfVhHGC03I0Qm7EqgdymL6cntCAeMxbybefBN3BKWFVWYD+w== + aws_profile: "" + - arn: arn:aws:kms:us-west-2:${ACCOUNT_ID}:alias/cdap-mgmt + created_at: "2025-09-30T19:13:00Z" + enc: AQICAHgWPUweNgOZBy54eQNTANw37AMcHppSZnWTksh1eOtc+wGh8N9L+gMCpKxWGdj0ob8ZAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMJT/PBEbyPHJ4h8cgAgEQgDuOyZxSH2R8GQiz8eqcDM2X8xuWxvpC0pYAaH+KJ64dWv6DEGJ7i0luLQ+dqFTGV6/aAeG5d+ybjzNFFg== + aws_profile: "" + unencrypted_regex: /nonsensitive/ + mac_only_encrypted: true + version: 3.10.2 diff --git a/terraform/services/kms-keys/main.tf b/terraform/services/kms-keys/main.tf index ef97ef5e..c6e64262 100644 --- a/terraform/services/kms-keys/main.tf +++ b/terraform/services/kms-keys/main.tf @@ -1,9 +1,11 @@ module "standards" { - source = "github.com/CMSgov/cdap//terraform/modules/standards?ref=0bd3eeae6b03cc8883b7dbdee5f04deb33468260" + source = "../../modules/standards" #TODO: Update with appropriate reference + app = var.app env = var.env root_module = "https://github.com/CMSgov/cdap/tree/main/terraform/services/kms-keys" service = "kms-keys" + providers = { aws = aws, aws.secondary = aws.secondary } } locals { diff --git a/terraform/services/tfstate/main.tf b/terraform/services/tfstate/main.tf index 748f77e8..71169e4d 100644 --- a/terraform/services/tfstate/main.tf +++ b/terraform/services/tfstate/main.tf @@ -1,11 +1,23 @@ locals { - name = "${var.app}-${var.env}-tfstate" + app = var.app + env = var.env + name = "${local.app}-${local.env}-tfstate" +} + +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/tfstate" + service = "tfstate" + providers = { aws = aws, aws.secondary = aws.secondary } } module "tfstate_bucket" { - source = "../../modules/bucket" + source = "../../modules/bucket" #TODO: Update with appropriate reference name = local.name - app = var.app - env = var.env + app = local.app + env = local.env } diff --git a/terraform/services/tfstate/terraform.tf b/terraform/services/tfstate/terraform.tf index 6479a5f5..d2d58983 100644 --- a/terraform/services/tfstate/terraform.tf +++ b/terraform/services/tfstate/terraform.tf @@ -1,16 +1,19 @@ +terraform { + backend "s3" { + key = "tfstate/terraform.tfstate" + } +} + provider "aws" { default_tags { - tags = { - Terraform = true - business = "oeda" - code = "https://github.com/CMSgov/cdap/tree/main/terraform/services/tfstate" - } + tags = module.standards.default_tags } } -terraform { - # Comment out backend block and init without -backend-config for initial creation of resources - backend "s3" { - key = "tfstate/terraform.tfstate" +provider "aws" { + alias = "secondary" + region = "us-west-2" + default_tags { + tags = module.standards.default_tags } }