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
devVPC uses CIDR10.1.0.0/16; theprodVPC uses CIDR10.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
prodonly. - NAT_Instance: An EC2 instance configured to perform network address translation, used in
devas 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
devorprod.devis always deployed first;prodis only deployed afterdevis 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
proddeployments. - 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 (
devorprod), differentiated by a dedicated.tfvarsfile and a unique state key path. - Common_Tags: The Terraform
localsblock (local.common_tags) that defines all mandatory tag key-value pairs; every resource block merges this map into itstagsargument. - Backend: The Terraform remote state configuration in
terraform/network/backend.tfthat points to the State_Bucket and Lock_Table provisioned in Phase 0. - GitHub_Actions: The CI/CD platform used to automate
terraform planon pull requests andterraform applyon merges tomain, 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
- THE Terraform_Bootstrap SHALL provision one S3 bucket designated as the State_Bucket for storing Terraform remote state.
- THE Terraform_Bootstrap SHALL enable versioning on the State_Bucket so that every state file revision is retained.
- THE Terraform_Bootstrap SHALL enable SSE on the State_Bucket using AWS-managed keys (SSE-S3 or SSE-KMS).
- THE Terraform_Bootstrap SHALL configure the State_Bucket to block all public access by setting all four S3 Block Public Access flags to
true. - THE Terraform_Bootstrap SHALL enable S3 server access logging on the State_Bucket, delivering access logs to a designated logging prefix or bucket.
- WHEN a Terraform operation writes state, THE State_Bucket SHALL store the state file in the configured key path.
- 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
- THE Terraform_Bootstrap SHALL provision one DynamoDB table designated as the Lock_Table.
- THE Lock_Table SHALL use
LockIDas its partition key with attribute typeString. - WHEN a Terraform operation acquires a state lock, THE Lock_Table SHALL create a lock record for the duration of the operation.
- WHEN a Terraform operation completes or is interrupted, THE Lock_Table SHALL release or allow force-unlock of the lock record.
- 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.
- 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
- THE Terraform_Bootstrap SHALL provision one VPC per Environment in the us-east-1 region.
- WHERE Environment is
dev, THE VPC SHALL use CIDR block10.1.0.0/16. - WHERE Environment is
prod, THE VPC SHALL use CIDR block10.2.0.0/16. - THE
devVPC CIDR and theprodVPC CIDR SHALL be non-overlapping to allow future VPC peering or Transit Gateway connectivity. - THE VPC SHALL have DNS hostnames enabled.
- THE VPC SHALL have DNS resolution enabled.
- THE VPC SHALL be named according to the Naming_Convention with resource token
vpcand purpose tokencore. - 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
- THE Terraform_Bootstrap SHALL provision two Public_Subnets within the VPC, one in each of two distinct AZs in us-east-1.
- EACH Public_Subnet SHALL use a
/24CIDR block drawn from the VPC CIDR range. - THE two Public_Subnets SHALL reside in different AZs to satisfy the multi-AZ requirement.
- EACH Public_Subnet SHALL be named according to the Naming_Convention with resource token
subnetand purpose tokenpublic. - EACH Public_Subnet SHALL have all mandatory tags from the Tagging_Policy applied at creation time.
- THE Public_Subnets SHALL NOT have
map_public_ip_on_launchenabled 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
- THE Terraform_Bootstrap SHALL provision two Private_Subnets within the VPC, one in each of the same two AZs used for the Public_Subnets.
- EACH Private_Subnet SHALL use a
/24CIDR block drawn from the VPC CIDR range, non-overlapping with any Public_Subnet CIDR. - THE two Private_Subnets SHALL reside in different AZs to satisfy the multi-AZ requirement.
- EACH Private_Subnet SHALL be named according to the Naming_Convention with resource token
subnetand purpose tokenprivate. - 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
- THE Terraform_Bootstrap SHALL provision one IGW and attach it to the VPC.
- THE IGW SHALL be named according to the Naming_Convention with resource token
igwand purpose tokencore. - THE IGW SHALL have all mandatory tags from the Tagging_Policy applied at creation time.
- 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
- WHERE Environment is
prod, THE Terraform_Bootstrap SHALL provision two EIPs, one for each NAT_Gateway. - WHERE Environment is
dev, THE Terraform_Bootstrap SHALL provision one EIP for the NAT_Instance. - EACH EIP SHALL be allocated in the
vpcdomain. - EACH EIP SHALL be named according to the Naming_Convention with resource token
eipand purpose tokennat. - 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
- 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. - 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. - WHERE Environment is
dev, THE NAT_Instance SHALL be associated with the EIP provisioned for it. - WHERE Environment is
dev, THE NAT_Instance SHALL be named according to the Naming_Convention with resource tokenec2and purpose tokennat. - WHERE Environment is
dev, THE NAT_Instance SHALL have all mandatory tags from the Tagging_Policy applied at creation time. - WHERE Environment is
prod, THE Terraform_Bootstrap SHALL provision two NAT_Gateways, one placed in each Public_Subnet (one per AZ). - WHERE Environment is
prod, EACH NAT_Gateway SHALL be associated with the EIP provisioned for its AZ. - WHERE Environment is
prod, EACH NAT_Gateway SHALL be of typepublic. - WHERE Environment is
prod, EACH NAT_Gateway SHALL be named according to the Naming_Convention with resource tokennatand the AZ short token reflecting its AZ. - WHERE Environment is
prod, EACH NAT_Gateway SHALL have all mandatory tags from the Tagging_Policy applied at creation time. - WHERE Environment is
prod, WHILE a NAT_Gateway is provisioning, THE Terraform_Bootstrap SHALL wait for the NAT_Gateway to reach theavailablestate 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
- THE Terraform_Bootstrap SHALL provision one public Route_Table associated with the VPC.
- THE public Route_Table SHALL contain a route for destination
0.0.0.0/0with the IGW as the target. - THE Terraform_Bootstrap SHALL associate both Public_Subnets with the public Route_Table.
- THE public Route_Table SHALL be named according to the Naming_Convention with resource token
rtand purpose tokenpublic. - 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
- WHERE Environment is
prod, THE Terraform_Bootstrap SHALL provision two private Route_Tables, one per AZ. - WHERE Environment is
prod, EACH private Route_Table SHALL contain a route for destination0.0.0.0/0with the NAT_Gateway in the same AZ as the target. - WHERE Environment is
dev, THE Terraform_Bootstrap SHALL provision one private Route_Table shared by both Private_Subnets. - WHERE Environment is
dev, THE private Route_Table SHALL contain a route for destination0.0.0.0/0with the NAT_Instance as the target (usingnetwork_interface_idof the NAT_Instance's primary ENI). - THE Terraform_Bootstrap SHALL associate each Private_Subnet with its corresponding private Route_Table.
- NO private Route_Table SHALL contain a route with the IGW as a target.
- EACH private Route_Table SHALL be named according to the Naming_Convention with resource token
rtand purpose tokenprivate; AZ-specific route tables SHALL also include the AZ short token. - 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
- THE Terraform_Bootstrap SHALL enable Flow_Logs on the VPC with destination type
cloud-watch-logs. - THE Flow_Logs SHALL capture traffic for all network interfaces in the VPC (traffic type
ALL). - THE Flow_Logs SHALL deliver log records to a dedicated CloudWatch Logs log group.
- WHERE Environment is
dev, THE CloudWatch Logs log group SHALL have a retention period of 14 days. - WHERE Environment is
prod, THE CloudWatch Logs log group SHALL have a retention period of 90 days. - THE Flow_Logs log group SHALL be named according to the Naming_Convention with resource token
logsand purpose tokenflow. - 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
- THE Terraform_Bootstrap SHALL provision a CloudTrail trail with
is_multi_region_trailset totrue. - THE CloudTrail trail SHALL be enabled and logging before any Phase 1 resources are provisioned.
- THE CloudTrail trail SHALL log management events for all API calls (read and write).
- THE CloudTrail trail SHALL deliver log files to the CloudTrail_Bucket.
- THE CloudTrail trail SHALL have
log_file_validation_enabledset totrue. - THE CloudTrail trail SHALL have
include_global_service_eventsset totrue. - 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
- THE Terraform_Bootstrap SHALL provision one CloudTrail_Bucket dedicated to storing CloudTrail log files.
- THE CloudTrail_Bucket SHALL have SSE enabled using AWS-managed keys (SSE-S3 or SSE-KMS).
- THE CloudTrail_Bucket SHALL block all public access by setting all four S3 Block Public Access flags to
true. - 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.
- WHERE Environment is
dev, THE CloudTrail_Bucket SHALL have an object lifecycle rule that expires log objects after 90 days. - WHERE Environment is
prod, THE CloudTrail_Bucket SHALL have an object lifecycle rule that expires log objects after 365 days. - 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.
- 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
- THE Terraform_Bootstrap SHALL apply the
Nametag to every provisioned resource using the formatMM-{env}-{region-short}-{az-short}-{resource}-{purpose}. - WHERE a resource is not AZ-specific, THE Terraform_Bootstrap SHALL omit the
{az-short}token from the name. - THE
{env}token SHALL be one ofdevorprodin lowercase. - THE
{region-short}token for us-east-1 SHALL beuse1. - THE
{az-short}token for us-east-1a SHALL beuse1aand for us-east-1b SHALL beuse1b. - THE
{resource}token SHALL use the values defined in the Glossary:vpc,subnet,igw,nat,rt,eip,sg,logs. - 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
- THE Terraform_Bootstrap SHALL apply all mandatory tags to every provisioned resource at creation time.
- THE mandatory tag set SHALL include:
Name,Environment,Owner,CostCenter,Project,ManagedBy,CreatedBy,CreationDate,DataClassification,Criticality,BackupPolicy, andPatchGroup. - THE
ManagedBytag value SHALL beTerraformfor all resources. - THE
Projecttag value SHALL bemm-aws-infrafor all resources. - IF a resource is created without all mandatory tags, THEN THE Terraform_Bootstrap SHALL fail validation before the apply step.
- WHERE a resource is subject to compliance requirements, THE
Compliancetag SHALL be applied with the applicable compliance standard value. - 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
- THE Terraform_Bootstrap SHALL define all AWS resources as Terraform configuration files committed to the GitHub repository before any
terraform applyis executed. - THE Terraform_Bootstrap SHALL pass
terraform validatewithout errors before anyterraform applyis executed. - THE Terraform_Bootstrap SHALL produce idempotent plans — running
terraform planon an already-applied configuration SHALL produce a plan with zero changes. - 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.
- THE Terraform_Bootstrap SHALL NOT contain hardcoded AWS credentials, access keys, or secret keys in any Terraform file, variable file, or GitHub-committed configuration.
- AFTER Phase 0 bootstrap, THE Terraform_Bootstrap SHALL configure the Terraform backend to use the State_Bucket and Lock_Table for all subsequent operations.
- IF a
terraform applyis 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
- THE Private_Subnets SHALL NOT have a route to the IGW in their associated Route_Tables.
- THE Terraform_Bootstrap SHALL NOT provision any EC2 instances, EKS nodes, or compute workloads in Public_Subnets.
- THE Terraform_Bootstrap SHALL NOT create any IAM users with static long-term access keys.
- WHEN outbound internet access is required from a Private_Subnet, THE NAT egress resource (NAT_Instance in
dev, NAT_Gateway inprod) in the same VPC SHALL be the sole egress path. - THE State_Bucket SHALL have SSE enabled and public access blocked at all times.
- THE CloudTrail_Bucket SHALL have SSE enabled and public access blocked at all times.
- 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
- 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/, anddocs/. - 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. - 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
- THE repository SHALL contain a Kiro_Spec file at
specs/phase0-state.yamlthat describes the bootstrap infrastructure (State_Bucket and Lock_Table). - THE repository SHALL contain a Kiro_Spec file at
specs/phase1-network.yamlthat describes the network infrastructure (VPC, subnets, IGW, NAT Gateways, route tables, Flow Logs, CloudTrail). - WHEN Kiro validates a Kiro_Spec file, THE Kiro_Spec SHALL pass validation without errors before any Terraform is generated or applied.
- THE Kiro_Spec files SHALL be committed to the repository before the corresponding Terraform configuration files are written.
- 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
- THE
terraform/network/backend.tffile SHALL configure the Terraform backend to use the State_Bucket provisioned in Phase 0. - THE backend configuration SHALL set
encrypt = trueto ensure state files are encrypted at rest. - THE backend configuration SHALL reference the Lock_Table for state locking.
- THE backend configuration SHALL set the state key to
network/{env}/terraform.tfstate, where{env}is the Environment value (devorprod), so that each Environment stores its state at a distinct path. - WHEN
terraform initis executed for the network module, THE Terraform_Bootstrap SHALL connect to the State_Bucket and confirm the backend is reachable before proceeding. - 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
- THE Terraform configuration SHALL define a
local.common_tagsmap containing all mandatory tags from the Tagging_Policy. - EVERY resource block in the Terraform configuration SHALL merge
local.common_tagsinto itstagsargument usingtags = merge(local.common_tags, { Name = "..." }). - IF a resource block does not reference
local.common_tagsin itstagsargument, THEN THE Terraform_Bootstrap SHALL failterraform validateor a pre-apply lint check. - WHEN
local.common_tagsis updated, THE change SHALL propagate to all resources on the nextterraform applywithout requiring individual resource edits. - THE Terraform configuration SHALL define input variables for:
environment,owner,cost_center,created_by, andcreation_date; these variables SHALL be referenced inlocal.common_tagsso 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
- EACH Private_Subnet SHALL carry the tag
kubernetes.io/role/internal-elbwith value"1"to enable EKS internal load balancer discovery. - EACH Public_Subnet SHALL carry the tag
kubernetes.io/role/elbwith value"1"to enable EKS external load balancer discovery. - THE EKS subnet tags SHALL be applied at subnet creation time as part of the Tagging_Policy merge.
- IF the
kubernetes.io/role/internal-elbtag is absent from a Private_Subnet, THEN THE Terraform_Bootstrap SHALL fail validation before the apply step. - IF the
kubernetes.io/role/elbtag 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
- EACH Environment SHALL have its own VPC, subnets, NAT egress resources (NAT_Instance for
dev, NAT_Gateways forprod), Route_Tables, Flow_Logs log group, CloudTrail trail, and CloudTrail_Bucket — no resource SHALL be shared between environments. - EACH Environment SHALL use a separate state key in the State_Bucket following the format
network/{env}/terraform.tfstate(e.g.,network/dev/terraform.tfstatefordevandnetwork/prod/terraform.tfstateforprod). - EACH Environment SHALL have its own tfvars file located at
terraform/network/envs/{env}.tfvars(e.g.,terraform/network/envs/dev.tfvarsandterraform/network/envs/prod.tfvars). - 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.
- 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
- THE pipeline SHALL deploy to the
devEnvironment before deploying to theprodEnvironment. - WHEN a change is merged to
main, THE pipeline SHALL apply todevautomatically and SHALL NOT apply toprodwithout a manual approval step. - THE GitHub Actions pipeline SHALL use GitHub_Environments named
devandprodso that theprodGitHub_Environment protection rules require at least one reviewer approval before aproddeployment proceeds. - IF the
devdeployment fails, THEN THE pipeline SHALL NOT proceed to theproddeployment. - WHEN a reviewer approves the
proddeployment in the GitHub_Environment, THE pipeline SHALL apply the same Terraform configuration toprodusing theprodtfvars 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
- THE Terraform_Bootstrap SHALL provision an IAM OIDC identity provider for
https://token.actions.githubusercontent.comwith audiencests.amazonaws.com. - THE Terraform_Bootstrap SHALL provision the IAM_Role_GHA named
MM-github-actions-rolewith a trust policy that permits assumption only by the specific GitHub organization, repository, and branch (refs/heads/main). - THE IAM_Role_GHA trust policy SHALL NOT permit assumption from any branch other than
refs/heads/mainor from repositories outside the designated MM GitHub organization. - THE Terraform_Bootstrap SHALL NOT create any IAM user or static access key for use by GitHub Actions.
- WHEN GitHub Actions executes a workflow on
main, THE workflow SHALL assume the IAM_Role_GHA via OIDC usingaws-actions/configure-aws-credentials@v4and SHALL NOT use hardcoded credentials. - 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
- THE repository SHALL contain a GitHub Actions workflow file at
.github/workflows/terraform.yaml. - THE workflow SHALL trigger on pull requests and pushes to
mainfor path changes underterraform/**andspecs/**. - THE workflow SHALL use
hashicorp/setup-terraform@v3to install Terraform. - THE workflow SHALL use
aws-actions/configure-aws-credentials@v4with OIDC to assume the IAM_Role_GHA — no static AWS credentials SHALL be used. - WHEN a pull request is opened or updated, THE workflow SHALL run
terraform init,terraform validate, andterraform planand SHALL post the plan output as a pull request comment. - WHEN a push to
mainoccurs, THE workflow SHALL runterraform apply -auto-approvefor thedevEnvironment after a successful plan. - WHEN the
devdeployment succeeds and a reviewer approves theprodGitHub_Environment, THE workflow SHALL runterraform apply -auto-approvefor theprodEnvironment. - IF
terraform planorterraform validatefails 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
- BEFORE a
terraform applyis executed for Phase 1, THE plan output SHALL include exactly the following resource types:devenvironment: 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.prodenvironment: 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.
- 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.
- 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. - THE post-apply checklist SHALL confirm: VPC exists with correct CIDR, all subnets are in the correct AZs, NAT egress resource is operational (
availablestate for NAT_Gateways inprod; running EC2 instance with source/destination check disabled for NAT_Instance indev), route tables have correct routes, Flow_Logs are delivering to CloudWatch, and CloudTrail is active and logging.