Connecting AWS VPCs for SSH Access

All Articles AI Culture Data Management Level 12 News Python Salesforce Software Development Testing

Picture this: you have a nice, shiny Terraform setup that modularizes an entire environment. Each deployed environment has its own network space, so we don't cross-contaminate resources. With your EC2 instances running the web apps, you generated supporting files from live data.

You would like to push those files to the EC2 quickly. “No problem,” you think “I'll just scp them from one to the other.” But the VPCs are separate, the networking is all separate, and nothing connects them.

There are a few ways to resolve this problem. One way is to use an S3 bucket, sync the files from the production server, and then pull them on the other instances.

If your instance hasn't been set up for those commands, this option will require several steps in addition to the S3 bucket terraform: install AWS CLI, configure credentials, etc.

Or, we can proceed with our original idea of SCP. The fundamental issue is the lack of connection between the VPCs. We can solve this with a peering connection, which can be configured in Terraform. Once the VPCs are peered, define routes to direct the traffic from each CIDR block to the other.

We want to be careful here to keep access control tight. We will allow the production server to have SSH access to the non-production instance, but not vice versa.

Production could still get a file from beta if needed, but the process must be instigated from the production instance. We don't want a rogue process on beta to have any potential of performing an action on another deployed environment.

Altogether, the terraform is fairly simple, as below. Your modularized environments will need to provide outputs for the VPC involved, the routing table, and the security groups used for access.

resource "aws_vpc_peering_connection" "vpc_peering_beta" {  
  vpc_id        = module.environment_prod.vpc_id  
  peer_vpc_id  = module.environment_beta.vpc_id  
  auto_accept  = true  
}  
resource "aws_vpc_peering_connection_accepter" "accept_peering_beta" {  
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering_beta.id  
  auto_accept              = true  
}  
resource "aws_route" "prod_to_beta" {  
  route_table_id        = module.environment_prod.public_route_table  
  destination_cidr_block = "10.125.32.0/20"  
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering_beta.id  
}  
resource "aws_route" "beta_to_prod" {  
  route_table_id        = module.environment_beta.public_route_table  
  destination_cidr_block = "10.135.40.0/22"  
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering_beta.id  
}  
resource "aws_security_group_rule" "allow_ssh_from_allow_all_out_beta" {  
  type        = "ingress"  
  from_port  = 22  
  to_port    = 22  
  protocol    = "tcp"  
  security_group_id = module.environment_beta.ssh_in_sg_id  
  source_security_group_id = module.environment_prod.allow_all_out_sg_id  
}

Originally published on 2025-03-19 by Matt Lewellyn

Reach out to us to discuss your complex deployment needs (or to chat about Star Trek)