Back to Blog
technical-referenceawsvpcnetworkingrequirementsinfra

Requirements Document

Formal requirements for the MM AWS network infrastructure: VPC CIDRs, subnet tiers, NAT instance vs gateway decision, and Tailscale connectivity.

October 22, 2025·24 min read

Requirements Document

Introduction

This document defines the requirements for the MM AWS Network Infrastructure project for Mica Mirai (MM). The scope covers Phase 0 (Terraform remote state bootstrap), Phase 1 (VPC and networking), and Phase 2 (CI/CD pipeline via GitHub Actions), with no compute resources provisioned. All infrastructure is defined as code using Terraform, version-controlled in GitHub from day one, and deployed via Kiro spec-driven workflows. The architecture targets us-east-1 with multi-AZ deployment across two Availability Zones, and is designed to be EKS-ready for future workloads. Two environments are supported: dev (deployed first) and prod (promoted after dev is validated).

Glossary

  • Terraform_Bootstrap: The one-time manual process that provisions the S3 state bucket and DynamoDB lock table before remote state is available.
  • State_Bucket: The S3 bucket used to store Terraform remote state files
  • Lock_Table: The DynamoDB table used to prevent concurrent Terraform operations via state locking.
  • VPC: Virtual Private Cloud — the isolated AWS network. The dev VPC uses CIDR 10.1.0.0/16; the prod VPC uses CIDR 10.2.0.0/16.
  • IGW: Internet Gateway — the AWS resource that provides public internet access to the VPC.
  • NAT_Gateway: Network Address Translation Gateway — allows private subnet resources to initiate outbound internet connections without being directly reachable from the internet. Used in prod only.
  • NAT_Instance: An EC2 instance configured to perform network address translation, used in dev as a cost-saving alternative to a managed NAT Gateway. Requires source/destination check disabled.
  • EIP: Elastic IP Address — a static public IPv4 address allocated to a NAT Gateway or NAT Instance.
  • Public_Subnet: A subnet whose route table directs 0.0.0.0/0 traffic to the IGW.
  • Private_Subnet: A subnet whose route table directs 0.0.0.0/0 traffic to a NAT Gateway (prod) or NAT Instance (dev); has no direct IGW route.
  • Route_Table: An AWS resource that contains routing rules for a subnet.
  • Flow_Logs: VPC Flow Logs — network traffic metadata captured and sent to CloudWatch Logs.
  • CloudTrail: AWS CloudTrail — the audit service that records API calls across all AWS regions.
  • CloudTrail_Bucket: The dedicated S3 bucket that stores CloudTrail log files.
  • Tagging_Policy: The set of mandatory tag keys and values that must be applied to every provisioned AWS resource.
  • Naming_Convention: The format MM-{env}-{region-short}-{az-short}-{resource}-{purpose} applied to all resource names.
  • Environment: The deployment tier — one of dev or prod. dev is always deployed first; prod is only deployed after dev is validated.
  • ManagedBy: A tag value indicating the resource is managed exclusively by Terraform.
  • SSE: Server-Side Encryption — encryption of data at rest applied by the AWS service.
  • AZ: Availability Zone — an isolated data center location within the us-east-1 region (e.g., us-east-1a, us-east-1b).
  • Kiro_Spec: A declarative YAML intent file that describes infrastructure topology, constraints, and policies; Kiro validates and generates Terraform from it.
  • GitHub_Environment: A GitHub Actions deployment environment with protection rules (required reviewers) used to gate prod deployments.
  • OIDC: OpenID Connect — the federated identity protocol used by GitHub Actions to assume an AWS IAM role without static credentials.
  • IAM_Role_GHA: The AWS IAM role (MM-github-actions-role) assumed by GitHub Actions via OIDC to run Terraform operations.
  • tfvars: Terraform variable definition files (.tfvars) that supply environment-specific values to a shared Terraform configuration.
  • Environment_Workspace: A variable-driven Terraform deployment targeting a specific environment tier (dev or prod), differentiated by a dedicated .tfvars file and a unique state key path.
  • Common_Tags: The Terraform locals block (local.common_tags) that defines all mandatory tag key-value pairs; every resource block merges this map into its tags argument.
  • Backend: The Terraform remote state configuration in terraform/network/backend.tf that points to the State_Bucket and Lock_Table provisioned in Phase 0.
  • GitHub_Actions: The CI/CD platform used to automate terraform plan on pull requests and terraform apply on merges to main, authenticating to AWS via OIDC.

Requirements

Requirement 1: Terraform Remote State — S3 Bucket

User Story: As a platform engineer, I want a versioned, encrypted, and access-controlled S3 bucket for Terraform remote state, so that infrastructure state is durable, auditable, and protected from accidental exposure.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision one S3 bucket designated as the State_Bucket for storing Terraform remote state.
  2. THE Terraform_Bootstrap SHALL enable versioning on the State_Bucket so that every state file revision is retained.
  3. THE Terraform_Bootstrap SHALL enable SSE on the State_Bucket using AWS-managed keys (SSE-S3 or SSE-KMS).
  4. THE Terraform_Bootstrap SHALL configure the State_Bucket to block all public access by setting all four S3 Block Public Access flags to true.
  5. THE Terraform_Bootstrap SHALL enable S3 server access logging on the State_Bucket, delivering access logs to a designated logging prefix or bucket.
  6. WHEN a Terraform operation writes state, THE State_Bucket SHALL store the state file in the configured key path.
  7. IF a Terraform operation attempts to read a state file that does not exist, THEN THE State_Bucket SHALL return a not-found response that Terraform interprets as an empty state.

Requirement 2: Terraform Remote State — DynamoDB Lock Table

User Story: As a platform engineer, I want a DynamoDB table for Terraform state locking, so that concurrent Terraform runs cannot corrupt the shared state file.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision one DynamoDB table designated as the Lock_Table.
  2. THE Lock_Table SHALL use LockID as its partition key with attribute type String.
  3. WHEN a Terraform operation acquires a state lock, THE Lock_Table SHALL create a lock record for the duration of the operation.
  4. WHEN a Terraform operation completes or is interrupted, THE Lock_Table SHALL release or allow force-unlock of the lock record.
  5. IF a second Terraform operation attempts to acquire a lock while one is held, THEN THE Lock_Table SHALL reject the second operation with a lock conflict error.
  6. THE Lock_Table SHALL have SSE enabled using AWS-managed keys.

Requirement 3: VPC

User Story: As a platform engineer, I want a dedicated VPC with a defined CIDR block in us-east-1 for each environment, so that all MM workloads are isolated in a controlled network boundary that can accommodate future EKS pod and service CIDRs.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision one VPC per Environment in the us-east-1 region.
  2. WHERE Environment is dev, THE VPC SHALL use CIDR block 10.1.0.0/16.
  3. WHERE Environment is prod, THE VPC SHALL use CIDR block 10.2.0.0/16.
  4. THE dev VPC CIDR and the prod VPC CIDR SHALL be non-overlapping to allow future VPC peering or Transit Gateway connectivity.
  5. THE VPC SHALL have DNS hostnames enabled.
  6. THE VPC SHALL have DNS resolution enabled.
  7. THE VPC SHALL be named according to the Naming_Convention with resource token vpc and purpose token core.
  8. THE VPC SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 4: Public Subnets

User Story: As a platform engineer, I want two public subnets spread across two Availability Zones, so that internet-facing load balancers and NAT egress resources have redundant placement.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision two Public_Subnets within the VPC, one in each of two distinct AZs in us-east-1.
  2. EACH Public_Subnet SHALL use a /24 CIDR block drawn from the VPC CIDR range.
  3. THE two Public_Subnets SHALL reside in different AZs to satisfy the multi-AZ requirement.
  4. EACH Public_Subnet SHALL be named according to the Naming_Convention with resource token subnet and purpose token public.
  5. EACH Public_Subnet SHALL have all mandatory tags from the Tagging_Policy applied at creation time.
  6. THE Public_Subnets SHALL NOT have map_public_ip_on_launch enabled by default, as no EC2 or EKS workloads are placed in public subnets.

Requirement 5: Private Subnets

User Story: As a platform engineer, I want two private subnets spread across two Availability Zones, so that future EKS node groups and application workloads are isolated from direct internet access.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision two Private_Subnets within the VPC, one in each of the same two AZs used for the Public_Subnets.
  2. EACH Private_Subnet SHALL use a /24 CIDR block drawn from the VPC CIDR range, non-overlapping with any Public_Subnet CIDR.
  3. THE two Private_Subnets SHALL reside in different AZs to satisfy the multi-AZ requirement.
  4. EACH Private_Subnet SHALL be named according to the Naming_Convention with resource token subnet and purpose token private.
  5. EACH Private_Subnet SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 6: Internet Gateway

User Story: As a platform engineer, I want an Internet Gateway attached to the VPC, so that resources in public subnets can communicate with the public internet.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision one IGW and attach it to the VPC.
  2. THE IGW SHALL be named according to the Naming_Convention with resource token igw and purpose token core.
  3. THE IGW SHALL have all mandatory tags from the Tagging_Policy applied at creation time.
  4. WHEN the IGW is detached or deleted, THE VPC SHALL lose all inbound and outbound internet connectivity for public subnets.

Requirement 7: Elastic IPs for NAT Egress Resources

User Story: As a platform engineer, I want a dedicated Elastic IP per NAT egress resource, so that each has a stable, predictable public IP address for outbound traffic.

Acceptance Criteria

  1. WHERE Environment is prod, THE Terraform_Bootstrap SHALL provision two EIPs, one for each NAT_Gateway.
  2. WHERE Environment is dev, THE Terraform_Bootstrap SHALL provision one EIP for the NAT_Instance.
  3. EACH EIP SHALL be allocated in the vpc domain.
  4. EACH EIP SHALL be named according to the Naming_Convention with resource token eip and purpose token nat.
  5. EACH EIP SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 8: NAT Egress — NAT Instance (dev) and NAT Gateways (prod)

User Story: As a platform engineer, I want cost-optimised NAT egress in dev using a single NAT Instance, and highly available NAT Gateways in prod with one per Availability Zone, so that private subnet resources have outbound internet access appropriate to each environment's cost and availability requirements.

Acceptance Criteria

  1. WHERE Environment is dev, THE Terraform_Bootstrap SHALL provision one NAT_Instance placed in one of the Public_Subnets to provide outbound internet access for all private subnets in the VPC.
  2. WHERE Environment is dev, THE NAT_Instance SHALL have source/destination check disabled (source_dest_check = false) so that it can forward traffic on behalf of other instances.
  3. WHERE Environment is dev, THE NAT_Instance SHALL be associated with the EIP provisioned for it.
  4. WHERE Environment is dev, THE NAT_Instance SHALL be named according to the Naming_Convention with resource token ec2 and purpose token nat.
  5. WHERE Environment is dev, THE NAT_Instance SHALL have all mandatory tags from the Tagging_Policy applied at creation time.
  6. WHERE Environment is prod, THE Terraform_Bootstrap SHALL provision two NAT_Gateways, one placed in each Public_Subnet (one per AZ).
  7. WHERE Environment is prod, EACH NAT_Gateway SHALL be associated with the EIP provisioned for its AZ.
  8. WHERE Environment is prod, EACH NAT_Gateway SHALL be of type public.
  9. WHERE Environment is prod, EACH NAT_Gateway SHALL be named according to the Naming_Convention with resource token nat and the AZ short token reflecting its AZ.
  10. WHERE Environment is prod, EACH NAT_Gateway SHALL have all mandatory tags from the Tagging_Policy applied at creation time.
  11. WHERE Environment is prod, WHILE a NAT_Gateway is provisioning, THE Terraform_Bootstrap SHALL wait for the NAT_Gateway to reach the available state before associating route tables.

Requirement 9: Public Route Table

User Story: As a platform engineer, I want a route table for public subnets that routes all internet-bound traffic to the Internet Gateway, so that resources in public subnets can reach and be reached from the internet.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision one public Route_Table associated with the VPC.
  2. THE public Route_Table SHALL contain a route for destination 0.0.0.0/0 with the IGW as the target.
  3. THE Terraform_Bootstrap SHALL associate both Public_Subnets with the public Route_Table.
  4. THE public Route_Table SHALL be named according to the Naming_Convention with resource token rt and purpose token public.
  5. THE public Route_Table SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 10: Private Route Tables

User Story: As a platform engineer, I want private route tables that route outbound traffic through the appropriate NAT egress resource for each environment, so that private subnet resources can reach the internet without direct IGW exposure.

Acceptance Criteria

  1. WHERE Environment is prod, THE Terraform_Bootstrap SHALL provision two private Route_Tables, one per AZ.
  2. WHERE Environment is prod, EACH private Route_Table SHALL contain a route for destination 0.0.0.0/0 with the NAT_Gateway in the same AZ as the target.
  3. WHERE Environment is dev, THE Terraform_Bootstrap SHALL provision one private Route_Table shared by both Private_Subnets.
  4. WHERE Environment is dev, THE private Route_Table SHALL contain a route for destination 0.0.0.0/0 with the NAT_Instance as the target (using network_interface_id of the NAT_Instance's primary ENI).
  5. THE Terraform_Bootstrap SHALL associate each Private_Subnet with its corresponding private Route_Table.
  6. NO private Route_Table SHALL contain a route with the IGW as a target.
  7. EACH private Route_Table SHALL be named according to the Naming_Convention with resource token rt and purpose token private; AZ-specific route tables SHALL also include the AZ short token.
  8. EACH private Route_Table SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 11: VPC Flow Logs

User Story: As a platform engineer, I want VPC Flow Logs sent to CloudWatch Logs, so that network traffic metadata is available for security analysis, troubleshooting, and compliance auditing.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL enable Flow_Logs on the VPC with destination type cloud-watch-logs.
  2. THE Flow_Logs SHALL capture traffic for all network interfaces in the VPC (traffic type ALL).
  3. THE Flow_Logs SHALL deliver log records to a dedicated CloudWatch Logs log group.
  4. WHERE Environment is dev, THE CloudWatch Logs log group SHALL have a retention period of 14 days.
  5. WHERE Environment is prod, THE CloudWatch Logs log group SHALL have a retention period of 90 days.
  6. THE Flow_Logs log group SHALL be named according to the Naming_Convention with resource token logs and purpose token flow.
  7. THE Flow_Logs log group SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 12: CloudTrail

User Story: As a platform engineer, I want AWS CloudTrail enabled with multi-region coverage before any other infrastructure is provisioned, so that all API activity across all regions is audited from the start.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision a CloudTrail trail with is_multi_region_trail set to true.
  2. THE CloudTrail trail SHALL be enabled and logging before any Phase 1 resources are provisioned.
  3. THE CloudTrail trail SHALL log management events for all API calls (read and write).
  4. THE CloudTrail trail SHALL deliver log files to the CloudTrail_Bucket.
  5. THE CloudTrail trail SHALL have log_file_validation_enabled set to true.
  6. THE CloudTrail trail SHALL have include_global_service_events set to true.
  7. THE CloudTrail trail SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 13: CloudTrail S3 Bucket

User Story: As a platform engineer, I want a dedicated, encrypted S3 bucket for CloudTrail logs with access logging enabled, so that audit records are tamper-evident, durable, and themselves audited.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision one CloudTrail_Bucket dedicated to storing CloudTrail log files.
  2. THE CloudTrail_Bucket SHALL have SSE enabled using AWS-managed keys (SSE-S3 or SSE-KMS).
  3. THE CloudTrail_Bucket SHALL block all public access by setting all four S3 Block Public Access flags to true.
  4. THE CloudTrail_Bucket SHALL have S3 server access logging enabled, delivering access logs to a separate S3 access-log bucket or a prefix within a designated logging bucket.
  5. WHERE Environment is dev, THE CloudTrail_Bucket SHALL have an object lifecycle rule that expires log objects after 90 days.
  6. WHERE Environment is prod, THE CloudTrail_Bucket SHALL have an object lifecycle rule that expires log objects after 365 days.
  7. THE CloudTrail_Bucket SHALL have a bucket policy that permits CloudTrail to write log files and denies all other public or cross-account write access.
  8. THE CloudTrail_Bucket SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 14: Naming Convention

User Story: As a platform engineer, I want all AWS resources named using a consistent convention, so that resources are immediately identifiable by environment, region, AZ, type, and purpose without consulting external documentation.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL apply the Name tag to every provisioned resource using the format MM-{env}-{region-short}-{az-short}-{resource}-{purpose}.
  2. WHERE a resource is not AZ-specific, THE Terraform_Bootstrap SHALL omit the {az-short} token from the name.
  3. THE {env} token SHALL be one of dev or prod in lowercase.
  4. THE {region-short} token for us-east-1 SHALL be use1.
  5. THE {az-short} token for us-east-1a SHALL be use1a and for us-east-1b SHALL be use1b.
  6. THE {resource} token SHALL use the values defined in the Glossary: vpc, subnet, igw, nat, rt, eip, sg, logs.
  7. WHEN a resource name is constructed, THE Terraform_Bootstrap SHALL prefix the name with MM.

Requirement 15: Mandatory Tagging Policy

User Story: As a platform engineer, I want every provisioned resource to carry a defined set of mandatory tags, so that resources can be tracked for cost allocation, ownership, compliance, and lifecycle management.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL apply all mandatory tags to every provisioned resource at creation time.
  2. THE mandatory tag set SHALL include: Name, Environment, Owner, CostCenter, Project, ManagedBy, CreatedBy, CreationDate, DataClassification, Criticality, BackupPolicy, and PatchGroup.
  3. THE ManagedBy tag value SHALL be Terraform for all resources.
  4. THE Project tag value SHALL be mm-aws-infra for all resources.
  5. IF a resource is created without all mandatory tags, THEN THE Terraform_Bootstrap SHALL fail validation before the apply step.
  6. WHERE a resource is subject to compliance requirements, THE Compliance tag SHALL be applied with the applicable compliance standard value.
  7. THE Terraform_Bootstrap SHALL enforce tagging via Terraform variable defaults and locals so that no resource block omits the mandatory tag set.

Requirement 16: Infrastructure as Code Constraints

User Story: As a platform engineer, I want all infrastructure defined exclusively as Terraform code committed to GitHub, so that every change is auditable, repeatable, and deployable without manual console intervention.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL define all AWS resources as Terraform configuration files committed to the GitHub repository before any terraform apply is executed.
  2. THE Terraform_Bootstrap SHALL pass terraform validate without errors before any terraform apply is executed.
  3. THE Terraform_Bootstrap SHALL produce idempotent plans — running terraform plan on an already-applied configuration SHALL produce a plan with zero changes.
  4. WHEN deploying to a new environment, THE Terraform_Bootstrap SHALL produce infrastructure identical in structure to any previously deployed environment when given the same input variables.
  5. THE Terraform_Bootstrap SHALL NOT contain hardcoded AWS credentials, access keys, or secret keys in any Terraform file, variable file, or GitHub-committed configuration.
  6. AFTER Phase 0 bootstrap, THE Terraform_Bootstrap SHALL configure the Terraform backend to use the State_Bucket and Lock_Table for all subsequent operations.
  7. IF a terraform apply is interrupted, THEN THE Terraform_Bootstrap SHALL allow re-execution without manual state repair, relying on the Lock_Table to prevent concurrent runs.

Requirement 17: Security Baseline

User Story: As a platform engineer, I want the network architecture to enforce a security baseline that prevents workloads from being accidentally exposed to the internet and ensures all data stores are encrypted, so that the infrastructure meets MM's security policy from day one.

Acceptance Criteria

  1. THE Private_Subnets SHALL NOT have a route to the IGW in their associated Route_Tables.
  2. THE Terraform_Bootstrap SHALL NOT provision any EC2 instances, EKS nodes, or compute workloads in Public_Subnets.
  3. THE Terraform_Bootstrap SHALL NOT create any IAM users with static long-term access keys.
  4. WHEN outbound internet access is required from a Private_Subnet, THE NAT egress resource (NAT_Instance in dev, NAT_Gateway in prod) in the same VPC SHALL be the sole egress path.
  5. THE State_Bucket SHALL have SSE enabled and public access blocked at all times.
  6. THE CloudTrail_Bucket SHALL have SSE enabled and public access blocked at all times.
  7. THE Lock_Table SHALL have SSE enabled at all times.

Requirement 18: Repository Folder Structure

User Story: As a platform engineer, I want the GitHub repository to have a defined folder structure committed before any spec or Terraform code is written, so that all contributors work within a consistent, predictable layout from the start.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL require the following top-level directories to exist in the GitHub repository before any Terraform or spec file is committed: specs/, terraform/bootstrap/, terraform/network/, modules/, .github/workflows/, and docs/.
  2. WHEN a contributor opens the repository, THE repository SHALL contain the defined folder structure with at minimum placeholder files (e.g., .gitkeep) so that the structure is visible.
  3. IF any required directory is absent from the repository at the time of the first Terraform apply, THEN THE pipeline SHALL fail with a descriptive error identifying the missing directory.

Requirement 19: Kiro Spec Files

User Story: As a platform engineer, I want declarative Kiro spec YAML files to serve as the source of truth for infrastructure intent, so that Kiro can validate and generate Terraform from a human-readable topology description.

Acceptance Criteria

  1. THE repository SHALL contain a Kiro_Spec file at specs/phase0-state.yaml that describes the bootstrap infrastructure (State_Bucket and Lock_Table).
  2. THE repository SHALL contain a Kiro_Spec file at specs/phase1-network.yaml that describes the network infrastructure (VPC, subnets, IGW, NAT Gateways, route tables, Flow Logs, CloudTrail).
  3. WHEN Kiro validates a Kiro_Spec file, THE Kiro_Spec SHALL pass validation without errors before any Terraform is generated or applied.
  4. THE Kiro_Spec files SHALL be committed to the repository before the corresponding Terraform configuration files are written.
  5. IF a Kiro_Spec file is modified, THEN THE corresponding Terraform configuration SHALL be regenerated or reviewed for consistency before the next apply.

Requirement 20: Terraform Backend Configuration

User Story: As a platform engineer, I want the Terraform network module to use the S3 remote state created in Phase 0 as its backend, so that network state is stored durably and consistently with the bootstrap state.

Acceptance Criteria

  1. THE terraform/network/backend.tf file SHALL configure the Terraform backend to use the State_Bucket provisioned in Phase 0.
  2. THE backend configuration SHALL set encrypt = true to ensure state files are encrypted at rest.
  3. THE backend configuration SHALL reference the Lock_Table for state locking.
  4. THE backend configuration SHALL set the state key to network/{env}/terraform.tfstate, where {env} is the Environment value (dev or prod), so that each Environment stores its state at a distinct path.
  5. WHEN terraform init is executed for the network module, THE Terraform_Bootstrap SHALL connect to the State_Bucket and confirm the backend is reachable before proceeding.
  6. IF the State_Bucket is unreachable during terraform init, THEN THE Terraform_Bootstrap SHALL fail with a descriptive error and SHALL NOT proceed to plan or apply.

Requirement 21: Tag Enforcement via Terraform Locals

User Story: As a platform engineer, I want tag enforcement implemented through a shared local.common_tags block in Terraform, so that no resource can be applied without the full mandatory tag set and tag values are consistent across all resources.

Acceptance Criteria

  1. THE Terraform configuration SHALL define a local.common_tags map containing all mandatory tags from the Tagging_Policy.
  2. EVERY resource block in the Terraform configuration SHALL merge local.common_tags into its tags argument using tags = merge(local.common_tags, { Name = "..." }).
  3. IF a resource block does not reference local.common_tags in its tags argument, THEN THE Terraform_Bootstrap SHALL fail terraform validate or a pre-apply lint check.
  4. WHEN local.common_tags is updated, THE change SHALL propagate to all resources on the next terraform apply without requiring individual resource edits.
  5. THE Terraform configuration SHALL define input variables for: environment, owner, cost_center, created_by, and creation_date; these variables SHALL be referenced in local.common_tags so that tag values are supplied per-environment via tfvars files.

Requirement 22: EKS-Specific Subnet Tags

User Story: As a platform engineer, I want public and private subnets tagged for EKS load balancer discovery, so that future EKS clusters can automatically identify the correct subnets for external and internal load balancers without manual configuration.

Acceptance Criteria

  1. EACH Private_Subnet SHALL carry the tag kubernetes.io/role/internal-elb with value "1" to enable EKS internal load balancer discovery.
  2. EACH Public_Subnet SHALL carry the tag kubernetes.io/role/elb with value "1" to enable EKS external load balancer discovery.
  3. THE EKS subnet tags SHALL be applied at subnet creation time as part of the Tagging_Policy merge.
  4. IF the kubernetes.io/role/internal-elb tag is absent from a Private_Subnet, THEN THE Terraform_Bootstrap SHALL fail validation before the apply step.
  5. IF the kubernetes.io/role/elb tag is absent from a Public_Subnet, THEN THE Terraform_Bootstrap SHALL fail validation before the apply step.

Requirement 23: Multi-Environment Isolation

User Story: As a platform engineer, I want each environment (dev and prod) to have fully isolated AWS resources with no sharing between environments, so that a failure or misconfiguration in one environment cannot affect the other.

Acceptance Criteria

  1. EACH Environment SHALL have its own VPC, subnets, NAT egress resources (NAT_Instance for dev, NAT_Gateways for prod), Route_Tables, Flow_Logs log group, CloudTrail trail, and CloudTrail_Bucket — no resource SHALL be shared between environments.
  2. EACH Environment SHALL use a separate state key in the State_Bucket following the format network/{env}/terraform.tfstate (e.g., network/dev/terraform.tfstate for dev and network/prod/terraform.tfstate for prod).
  3. EACH Environment SHALL have its own tfvars file located at terraform/network/envs/{env}.tfvars (e.g., terraform/network/envs/dev.tfvars and terraform/network/envs/prod.tfvars).
  4. WHEN Terraform is applied for one Environment, THE apply SHALL reference only the tfvars file for that Environment and SHALL NOT modify resources belonging to another Environment.
  5. IF a Terraform plan for one Environment includes changes to resources tagged with a different Environment value, THEN THE pipeline SHALL fail with an error before apply is permitted.

Requirement 24: Multi-Environment Deploy Order and Promotion Gate

User Story: As a platform engineer, I want dev to always be deployed before prod, with a mandatory approval gate before any prod deployment, so that production infrastructure is never modified without explicit human review of a validated dev deployment.

Acceptance Criteria

  1. THE pipeline SHALL deploy to the dev Environment before deploying to the prod Environment.
  2. WHEN a change is merged to main, THE pipeline SHALL apply to dev automatically and SHALL NOT apply to prod without a manual approval step.
  3. THE GitHub Actions pipeline SHALL use GitHub_Environments named dev and prod so that the prod GitHub_Environment protection rules require at least one reviewer approval before a prod deployment proceeds.
  4. IF the dev deployment fails, THEN THE pipeline SHALL NOT proceed to the prod deployment.
  5. WHEN a reviewer approves the prod deployment in the GitHub_Environment, THE pipeline SHALL apply the same Terraform configuration to prod using the prod tfvars file.

Requirement 25: IAM OIDC Provider and Role for GitHub Actions

User Story: As a platform engineer, I want GitHub Actions to authenticate to AWS via OIDC with a scoped IAM role, so that no static AWS credentials are stored in GitHub and the blast radius of a compromised token is minimized.

Acceptance Criteria

  1. THE Terraform_Bootstrap SHALL provision an IAM OIDC identity provider for https://token.actions.githubusercontent.com with audience sts.amazonaws.com.
  2. THE Terraform_Bootstrap SHALL provision the IAM_Role_GHA named MM-github-actions-role with a trust policy that permits assumption only by the specific GitHub organization, repository, and branch (refs/heads/main).
  3. THE IAM_Role_GHA trust policy SHALL NOT permit assumption from any branch other than refs/heads/main or from repositories outside the designated MM GitHub organization.
  4. THE Terraform_Bootstrap SHALL NOT create any IAM user or static access key for use by GitHub Actions.
  5. WHEN GitHub Actions executes a workflow on main, THE workflow SHALL assume the IAM_Role_GHA via OIDC using aws-actions/configure-aws-credentials@v4 and SHALL NOT use hardcoded credentials.
  6. THE IAM_Role_GHA SHALL have all mandatory tags from the Tagging_Policy applied at creation time.

Requirement 26: GitHub Actions CI/CD Pipeline

User Story: As a platform engineer, I want a GitHub Actions workflow that automatically validates and applies Terraform on pull requests and merges to main, so that infrastructure changes are reviewed, validated, and deployed consistently without manual CLI execution.

Acceptance Criteria

  1. THE repository SHALL contain a GitHub Actions workflow file at .github/workflows/terraform.yaml.
  2. THE workflow SHALL trigger on pull requests and pushes to main for path changes under terraform/** and specs/**.
  3. THE workflow SHALL use hashicorp/setup-terraform@v3 to install Terraform.
  4. THE workflow SHALL use aws-actions/configure-aws-credentials@v4 with OIDC to assume the IAM_Role_GHA — no static AWS credentials SHALL be used.
  5. WHEN a pull request is opened or updated, THE workflow SHALL run terraform init, terraform validate, and terraform plan and SHALL post the plan output as a pull request comment.
  6. WHEN a push to main occurs, THE workflow SHALL run terraform apply -auto-approve for the dev Environment after a successful plan.
  7. WHEN the dev deployment succeeds and a reviewer approves the prod GitHub_Environment, THE workflow SHALL run terraform apply -auto-approve for the prod Environment.
  8. IF terraform plan or terraform validate fails on a pull request, THEN THE workflow SHALL mark the pull request check as failed and SHALL NOT proceed to apply.

Requirement 27: Terraform Plan Verification Checklist

User Story: As a platform engineer, I want the Terraform plan to produce an exact expected resource count before any apply is permitted, so that unintended resource additions or deletions are caught before they reach AWS.

Acceptance Criteria

  1. BEFORE a terraform apply is executed for Phase 1, THE plan output SHALL include exactly the following resource types:
    • dev environment: 1 VPC, 4 subnets (2 public, 2 private), 1 IGW, 1 EIP, 1 NAT_Instance (EC2), 1 Route_Table (public), 1 Route_Table (private, shared), 4 route table associations, 1 VPC Flow Log, 1 CloudTrail trail, and all mandatory tags present on every resource.
    • prod environment: 1 VPC, 4 subnets (2 public, 2 private), 1 IGW, 2 EIPs, 2 NAT_Gateways, 3 Route_Tables (1 public, 2 private), 4 route table associations, 1 VPC Flow Log, 1 CloudTrail trail, and all mandatory tags present on every resource.
  2. IF the plan resource count deviates from the expected count, THEN THE pipeline SHALL halt and require a human reviewer to confirm the deviation before apply proceeds.
  3. AFTER each terraform apply, THE platform engineer SHALL verify the deployed resources in the AWS Console against the post-apply checklist defined in the project documentation.
  4. THE post-apply checklist SHALL confirm: VPC exists with correct CIDR, all subnets are in the correct AZs, NAT egress resource is operational (available state for NAT_Gateways in prod; running EC2 instance with source/destination check disabled for NAT_Instance in dev), route tables have correct routes, Flow_Logs are delivering to CloudWatch, and CloudTrail is active and logging.