Mission 6.4 · Stage 6 — Environments
Sharing Outputs with Other Teams — Without Sharing State
Goal: The analytics team's separate Terraform config can find ShopLite's VPC, private subnets, and database security group through SSM parameters, without ever getting read access to ShopLite's state.
By the end of this mission
- Compare
terraform_remote_state, data-source lookups, and published parameters - Publish selected outputs to SSM Parameter Store
- Consume them in a separate configuration with correct types
- Explain why remote state access is a security decision
Part 1
Understand it first
Three ways to share values between configurations
terraform_remote_state: read another configuration's OUTPUTS directly from its state file. Easy, but reading outputs requires read access to the WHOLE state file, including every secret and attribute in it. It also couples the consumer to the producer's backend layout.
Data-source LOOKUPS: find resources by tags or names (data "aws_vpc" { tags = { Name = "shoplite-prod-vpc" } }). No coupling to state, but it depends on naming conventions, and the lookup can match zero or several things.
PUBLISHED VALUES: the producer writes the values it wants to share to a well-known place, such as SSM parameters under /shoplite/prod/network/*, and consumers read them with aws_ssm_parameter data sources. That's an explicit, least-privilege contract: consumers get read access to exactly those parameters and nothing else.
Outputs are an interface
Whatever you publish, other teams will build on it. Publish only stable, necessary values (IDs, ARNs, endpoints), document them, and treat changes as breaking changes, just like module outputs in Stage 5.
Part 2
Your project after this mission · 3 files change
- analytics/
- backend.tfnew
- main.tfnew
- infra/
- envs/
- dev.s3.tfbackend
- dev.tfvars
- prod.s3.tfbackend
- prod.tfvars
- alb.tf
- backend.tf
- billing.tf
- checks.tf
- database.tf
- ecr.tf
- ecs.tf
- iam.tf
- locals.tf
- logs.tf
- main.tf
- outputs.tf
- probe.tf
- providers.tf
- published.tfnew
- refactors.tf
- security.tf
- storage.tf
- tf
- uploads.tf
- variables.tf
- versions.tf
Part 3
Build it, step by step
- 1
Publish ShopLite's shareable values
A map of name → value, published with
for_each. Lists are stored asStringList(comma-separated). The path includes the environment, so each environment publishes its own set.infra/published.tfwhole filehcl locals { published = { "network/vpc_id" = { type = "String", value = module.network.vpc_id } "network/private_subnet_ids" = { type = "StringList", value = join(",", module.network.private_subnet_ids) } "security/db_sg_id" = { type = "String", value = aws_security_group.db.id } "database/endpoint" = { type = "String", value = aws_db_instance.main.address } } } resource "aws_ssm_parameter" "published" { for_each = local.published name = "/${var.project}/${var.environment}/${each.key}" type = each.value.type value = each.value.value description = "Published by ShopLite Terraform for other teams. Do not edit by hand." }terminal$ ./tf dev applyaws ssm get-parameters-by-path --path /shoplite/dev --recursive --query 'Parameters[].[Name,Value]' --output text── expected output ──Plan: 4 to add, 0 to change, 0 to destroy..../shoplite/dev/database/endpoint shoplite-dev-db.c1x2y3z4a5b6.ap-south-1.rds.amazonaws.com/shoplite/dev/network/private_subnet_ids subnet-0aa11bb22cc33dd44,subnet-0ee55ff66aa77bb88/shoplite/dev/network/vpc_id vpc-0c1d2e3f4a5b6c7d8/shoplite/dev/security/db_sg_id sg-0c3d4e5f607182930 - 2
The analytics team's configuration
A separate root with its own state (key
analytics/dev/terraform.tfstate). It reads the parameters, splits the StringList back into a list, and creates its own security group in ShopLite's VPC that the DB will accept. That last part needs a rule on ShopLite's side too, which is a deliberate, reviewable change by the owning team.analytics/main.tfwhole filehcl provider "aws" { region = "ap-south-1" } data "aws_ssm_parameter" "vpc_id" { name = "/shoplite/dev/network/vpc_id" } data "aws_ssm_parameter" "private_subnet_ids" { name = "/shoplite/dev/network/private_subnet_ids" } locals { vpc_id = data.aws_ssm_parameter.vpc_id.value private_subnet_ids = split(",", data.aws_ssm_parameter.private_subnet_ids.value) } resource "aws_security_group" "etl" { name_prefix = "analytics-etl-" vpc_id = local.vpc_id description = "Analytics ETL jobs" } output "etl_security_group_id" { value = aws_security_group.etl.id } output "subnets_used" { value = local.private_subnet_ids }terminal$ cd ../analytics && terraform init && terraform apply── expected output ──data.aws_ssm_parameter.vpc_id: Read complete after 0s [id=/shoplite/dev/network/vpc_id]data.aws_ssm_parameter.private_subnet_ids: Read complete after 0s [id=/shoplite/dev/network/private_subnet_ids]...Apply complete! Resources: 1 added, 0 changed, 0 destroyed.Outputs:etl_security_group_id = "sg-0d4e5f6071829304a"subnets_used = tolist(["subnet-0aa11bb22cc33dd44","subnet-0ee55ff66aa77bb88",])The analytics role needs only ssm:GetParameter on arn:aws:ssm:*:*:parameter/shoplite/dev/* — not s3:GetObject on ShopLite's state bucket. - 3
Compare with terraform_remote_state (don't keep it)
For contrast, the remote-state version is shorter, but granting it means the analytics role must read
shoplite/dev/terraform.tfstate, which includes the DB endpoint, every IAM ARN, and any secret that has ever been stored in state. It also only sees root OUTPUTS, so ShopLite would need to add them.analytics/main.tfadd to filehcl Read-only illustration. Delete after trying.
data "terraform_remote_state" "shoplite" { backend = "s3" config = { bucket = "shoplite-tfstate-c41e" key = "shoplite/dev/terraform.tfstate" region = "ap-south-1" } } # data.terraform_remote_state.shoplite.outputs.vpc_id
Checkpoint — you should now have
- ✓ShopLite publishes four values under
/shoplite/<env>/...viafor_each. - ✓The
analytics/config reads them with its own state and no access to ShopLite's state. - ✓You can explain why
terraform_remote_stateimplies full state read access.
Part 4
Break it on purpose
Make each change, run the command, and read the error before revealing the diagnosis. Recognising these messages on sight is what makes you fast on a real team. Undo the change afterwards.
Break #1
Treat a StringList as a list
In analytics/main.tf, set private_subnet_ids = data.aws_ssm_parameter.private_subnet_ids.value (no split) and use it in a resource that expects a list, such as subnet_ids in an aws_db_subnet_group. Run terraform plan.
Break #2
Read an output that isn't exported
Using the terraform_remote_state example, reference data.terraform_remote_state.shoplite.outputs.db_security_group_id, which ShopLite doesn't output.
Part 5
Interview questions from this mission
How can one Terraform configuration use values from another?
What's the security concern with terraform_remote_state?
Before you stop