Working with VPC peering connections
The following code example shows how to:
Create VPCs for peering
Create a VPC peering connection
Update route tables
Verify the VPC peering connection
Clean up resources
- Bash
-
- AWS CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # VPC Peering Connection Script - Version 6 (Security Enhanced) # This script establishes a VPC peering connection between two VPCs, # creates subnets if needed, and configures the necessary route tables. # It will use existing VPCs if available, or create new ones if needed. set -euo pipefail # Security: Set strict umask umask 0077 # Initialize log file with restricted permissions LOG_FILE="./vpc-peering-script-v6.log" touch "$LOG_FILE" chmod 0600 "$LOG_FILE" echo "Starting VPC Peering script at $(date)" > "$LOG_FILE" # Configuration declare -r AWS_REGION="${AWS_REGION:-us-east-1}" declare -r MAX_RETRIES=3 declare -r RETRY_DELAY=5 # Validate script is run from secure location if [[ "$LOG_FILE" != /* ]] && [[ "$LOG_FILE" != ./* ]]; then echo "ERROR: Log file path must be absolute or relative starting with ./" >&2 exit 1 fi # Function to sanitize variable for safe command execution sanitize_var() { local var="$1" if [[ ! "$var" =~ ^[a-zA-Z0-9_/.-]+$ ]]; then echo "ERROR: Invalid characters in variable: $var" | tee -a "$LOG_FILE" return 1 fi echo "$var" return 0 } # Function to escape string for safe use in commands escape_string() { local string="$1" printf '%s\n' "$string" | sed -e 's/[\/&]/\\&/g' } # Function to log commands and their output securely log_cmd() { local cmd="$1" # Validate command doesn't contain suspicious patterns if [[ "$cmd" =~ (\$\(|\`|;.*rm|;.*mv|;.*cp) ]]; then echo "ERROR: Suspicious command pattern detected" | tee -a "$LOG_FILE" return 1 fi echo "$(date): COMMAND: $cmd" >> "$LOG_FILE" eval "$cmd" 2>&1 | tee -a "$LOG_FILE" return "${PIPESTATUS[0]}" } # Function to check for errors check_error() { local exit_code="$1" local error_msg="${2:-Command failed}" if [ "$exit_code" -ne 0 ]; then echo "ERROR: $error_msg (exit code: $exit_code)" | tee -a "$LOG_FILE" echo "See $LOG_FILE for details" cleanup_on_error exit "$exit_code" fi } # Function to validate AWS CLI is available and configured validate_aws_cli() { if ! command -v aws &> /dev/null; then echo "ERROR: AWS CLI is not installed" | tee -a "$LOG_FILE" exit 1 fi # Check AWS CLI version local aws_version aws_version=$(aws --version 2>&1 | cut -d' ' -f1 | cut -d'/' -f2) echo "AWS CLI version: $aws_version" >> "$LOG_FILE" if ! aws sts get-caller-identity --region "$AWS_REGION" &>/dev/null; then echo "ERROR: AWS CLI is not properly configured or credentials are invalid" | tee -a "$LOG_FILE" exit 1 fi # Validate caller identity local account_id account_id=$(aws sts get-caller-identity --query 'Account' --output text 2>/dev/null) if [[ ! "$account_id" =~ ^[0-9]{12}$ ]]; then echo "ERROR: Invalid AWS account ID" | tee -a "$LOG_FILE" exit 1 fi echo "Authenticated as AWS Account: $account_id" | tee -a "$LOG_FILE" } # Function to validate CIDR blocks validate_cidr() { local cidr="$1" if ! [[ "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then echo "ERROR: Invalid CIDR block format: $cidr" | tee -a "$LOG_FILE" return 1 fi # Additional validation for IP octets local ip_part="${cidr%/*}" local mask_part="${cidr#*/}" IFS='.' read -r -a octets <<< "$ip_part" for octet in "${octets[@]}"; do if (( octet > 255 )); then echo "ERROR: Invalid octet value in CIDR: $cidr" | tee -a "$LOG_FILE" return 1 fi done if (( mask_part > 32 || mask_part < 0 )); then echo "ERROR: Invalid CIDR mask value: $mask_part" | tee -a "$LOG_FILE" return 1 fi return 0 } # Function to clean up resources on error cleanup_on_error() { echo "Error encountered. Attempting to clean up resources..." | tee -a "$LOG_FILE" # List created resources echo "Resources created:" | tee -a "$LOG_FILE" for resource in "${CREATED_RESOURCES[@]:-}"; do echo "- $resource" | tee -a "$LOG_FILE" done # Clean up in reverse order with retry logic for ((i=${#CLEANUP_COMMANDS[@]}-1; i>=0; i--)); do echo "Executing cleanup: ${CLEANUP_COMMANDS[$i]}" >> "$LOG_FILE" local retry_count=0 while [ $retry_count -lt $MAX_RETRIES ]; do if eval "${CLEANUP_COMMANDS[$i]}" 2>&1 >> "$LOG_FILE"; then break else retry_count=$((retry_count + 1)) if [ $retry_count -lt $MAX_RETRIES ]; then echo "Cleanup command failed, retrying in ${RETRY_DELAY}s..." >> "$LOG_FILE" sleep "$RETRY_DELAY" fi fi done done } # Array to store created resources and cleanup commands declare -a CREATED_RESOURCES=() declare -a CLEANUP_COMMANDS=() # Trap errors and cleanup trap cleanup_on_error EXIT echo "Setting up VPC peering connection..." # Validate AWS CLI validate_aws_cli # Check VPC quota — need room for up to 2 new VPCs VPC_COUNT=$(aws ec2 describe-vpcs --region "$AWS_REGION" --query 'length(Vpcs)' --output text 2>/dev/null || echo 99) VPC_LIMIT=5 VPCS_NEEDED=2 # Check if prereq stack provides a VPC we can use as VPC1 PREREQ_VPC_ID="" PREREQ_STACK=$(aws cloudformation describe-stacks --region "$AWS_REGION" --stack-name tutorial-prereqs-vpc-public --query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "") if [[ "$PREREQ_STACK" == "CREATE_COMPLETE" || "$PREREQ_STACK" == "UPDATE_COMPLETE" ]]; then PREREQ_VPC_ID=$(aws cloudformation describe-stacks --region "$AWS_REGION" --stack-name tutorial-prereqs-vpc-public --query 'Stacks[0].Outputs[?OutputKey==`VpcId`].OutputValue' --output text 2>/dev/null || echo "") if [ -n "$PREREQ_VPC_ID" ]; then echo "Found prereq stack VPC: $PREREQ_VPC_ID (10.0.0.0/16)" VPCS_NEEDED=1 fi fi AVAILABLE=$((VPC_LIMIT - VPC_COUNT)) if [ "$AVAILABLE" -lt "$VPCS_NEEDED" ]; then echo "ERROR: Need $VPCS_NEEDED VPC slots but only $AVAILABLE available ($VPC_COUNT/$VPC_LIMIT used in $AWS_REGION)." echo "Free up VPCs or run in a different region: AWS_REGION=<region> bash $0" exit 1 fi # Set up VPCs if [ -n "$PREREQ_VPC_ID" ]; then # Use prereq VPC as VPC1, create VPC2 VPC1_ID="$PREREQ_VPC_ID" VPC1_CIDR="10.0.0.0/16" echo "Using prereq stack VPC as VPC1: $VPC1_ID ($VPC1_CIDR)" echo "Creating VPC2..." VPC2_ID=$(log_cmd "aws ec2 create-vpc --region '$AWS_REGION' --cidr-block 10.2.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=VPC2-Peering-Demo}]' --query 'Vpc.VpcId' --output text") check_error $? "Failed to create VPC2" VPC2_ID=$(sanitize_var "$VPC2_ID") || check_error 1 "Invalid VPC2_ID returned" VPC2_CIDR="10.2.0.0/16" CREATED_RESOURCES+=("VPC2: $VPC2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc --region '$AWS_REGION' --vpc-id '$VPC2_ID'") echo "VPC2 created with ID: $VPC2_ID" echo "Waiting for VPC2 to be available..." log_cmd "aws ec2 wait vpc-available --region '$AWS_REGION' --vpc-ids '$VPC2_ID'" check_error $? "Timeout waiting for VPC2 to become available" else # Create both VPCs echo "Creating VPC1..." VPC1_ID=$(log_cmd "aws ec2 create-vpc --region '$AWS_REGION' --cidr-block 10.1.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=VPC1-Peering-Demo}]' --query 'Vpc.VpcId' --output text") check_error $? "Failed to create VPC1" VPC1_ID=$(sanitize_var "$VPC1_ID") || check_error 1 "Invalid VPC1_ID returned" VPC1_CIDR="10.1.0.0/16" CREATED_RESOURCES+=("VPC1: $VPC1_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc --region '$AWS_REGION' --vpc-id '$VPC1_ID'") echo "VPC1 created with ID: $VPC1_ID" echo "Creating VPC2..." VPC2_ID=$(log_cmd "aws ec2 create-vpc --region '$AWS_REGION' --cidr-block 10.2.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=VPC2-Peering-Demo}]' --query 'Vpc.VpcId' --output text") check_error $? "Failed to create VPC2" VPC2_ID=$(sanitize_var "$VPC2_ID") || check_error 1 "Invalid VPC2_ID returned" VPC2_CIDR="10.2.0.0/16" CREATED_RESOURCES+=("VPC2: $VPC2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc --region '$AWS_REGION' --vpc-id '$VPC2_ID'") echo "VPC2 created with ID: $VPC2_ID" echo "Waiting for VPCs to be available..." log_cmd "aws ec2 wait vpc-available --region '$AWS_REGION' --vpc-ids '$VPC1_ID' '$VPC2_ID'" check_error $? "Timeout waiting for VPCs to become available" fi echo "Using the following VPCs:" echo "VPC1: $VPC1_ID ($VPC1_CIDR)" echo "VPC2: $VPC2_ID ($VPC2_CIDR)" # Verify the VPCs exist and are available echo "Verifying VPCs..." log_cmd "aws ec2 describe-vpcs --region '$AWS_REGION' --vpc-ids '$VPC1_ID' '$VPC2_ID' --query 'Vpcs[*].[VpcId,State,CidrBlock]' --output table" check_error $? "Failed to verify VPCs" # Determine subnet CIDR blocks based on VPC CIDR blocks # Use .100.0/24 to avoid overlap with prereq stack subnets (.1-.4) VPC1_SUBNET_CIDR=$(echo "$VPC1_CIDR" | sed 's/0\.0\/16/100.0\/24/') VPC2_SUBNET_CIDR=$(echo "$VPC2_CIDR" | sed 's/0\.0\/16/100.0\/24/') # Sanitize subnet CIDR blocks VPC1_SUBNET_CIDR=$(sanitize_var "$VPC1_SUBNET_CIDR") || check_error 1 "Invalid VPC1_SUBNET_CIDR format" VPC2_SUBNET_CIDR=$(sanitize_var "$VPC2_SUBNET_CIDR") || check_error 1 "Invalid VPC2_SUBNET_CIDR format" validate_cidr "$VPC1_SUBNET_CIDR" || check_error 1 "Invalid subnet CIDR for VPC1" validate_cidr "$VPC2_SUBNET_CIDR" || check_error 1 "Invalid subnet CIDR for VPC2" # Create subnets in both VPCs echo "Creating subnet in VPC1..." SUBNET1_ID=$(log_cmd "aws ec2 create-subnet --region '$AWS_REGION' --vpc-id '$VPC1_ID' --cidr-block '$VPC1_SUBNET_CIDR' --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=VPC1-Peering-Subnet}]' --query 'Subnet.SubnetId' --output text") check_error $? "Failed to create subnet in VPC1" SUBNET1_ID=$(sanitize_var "$SUBNET1_ID") || check_error 1 "Invalid SUBNET1_ID returned" CREATED_RESOURCES+=("Subnet in VPC1: $SUBNET1_ID") CLEANUP_COMMANDS+=("aws ec2 delete-subnet --region '$AWS_REGION' --subnet-id '$SUBNET1_ID'") echo "Subnet created in VPC1 with ID: $SUBNET1_ID (CIDR: $VPC1_SUBNET_CIDR)" echo "Creating subnet in VPC2..." SUBNET2_ID=$(log_cmd "aws ec2 create-subnet --region '$AWS_REGION' --vpc-id '$VPC2_ID' --cidr-block '$VPC2_SUBNET_CIDR' --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=VPC2-Peering-Subnet}]' --query 'Subnet.SubnetId' --output text") check_error $? "Failed to create subnet in VPC2" SUBNET2_ID=$(sanitize_var "$SUBNET2_ID") || check_error 1 "Invalid SUBNET2_ID returned" CREATED_RESOURCES+=("Subnet in VPC2: $SUBNET2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-subnet --region '$AWS_REGION' --subnet-id '$SUBNET2_ID'") echo "Subnet created in VPC2 with ID: $SUBNET2_ID (CIDR: $VPC2_SUBNET_CIDR)" # Create a VPC peering connection echo "Creating VPC peering connection..." PEERING_ID=$(log_cmd "aws ec2 create-vpc-peering-connection --region '$AWS_REGION' --vpc-id '$VPC1_ID' --peer-vpc-id '$VPC2_ID' --tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=VPC1-VPC2-Peering}]' --query 'VpcPeeringConnection.VpcPeeringConnectionId' --output text") check_error $? "Failed to create VPC peering connection" PEERING_ID=$(sanitize_var "$PEERING_ID") || check_error 1 "Invalid PEERING_ID returned" CREATED_RESOURCES+=("VPC Peering Connection: $PEERING_ID") CLEANUP_COMMANDS+=("aws ec2 delete-vpc-peering-connection --region '$AWS_REGION' --vpc-peering-connection-id '$PEERING_ID'") echo "VPC Peering Connection created with ID: $PEERING_ID" # Accept the VPC peering connection echo "Accepting VPC peering connection..." log_cmd "aws ec2 accept-vpc-peering-connection --region '$AWS_REGION' --vpc-peering-connection-id '$PEERING_ID'" check_error $? "Failed to accept VPC peering connection" echo "VPC Peering Connection accepted" # Wait for the peering connection to become active echo "Waiting for peering connection to become active..." log_cmd "aws ec2 wait vpc-peering-connection-exists --region '$AWS_REGION' --vpc-peering-connection-ids '$PEERING_ID'" check_error $? "Timeout waiting for peering connection to become active" # Create a route table for VPC1 echo "Creating route table for VPC1..." RTB1_ID=$(log_cmd "aws ec2 create-route-table --region '$AWS_REGION' --vpc-id '$VPC1_ID' --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=VPC1-RouteTable}]' --query 'RouteTable.RouteTableId' --output text") check_error $? "Failed to create route table for VPC1" RTB1_ID=$(sanitize_var "$RTB1_ID") || check_error 1 "Invalid RTB1_ID returned" CREATED_RESOURCES+=("Route Table for VPC1: $RTB1_ID") CLEANUP_COMMANDS+=("aws ec2 delete-route-table --region '$AWS_REGION' --route-table-id '$RTB1_ID'") echo "Route table created for VPC1 with ID: $RTB1_ID" # Create a route from VPC1 to VPC2 echo "Creating route from VPC1 to VPC2..." log_cmd "aws ec2 create-route --region '$AWS_REGION' --route-table-id '$RTB1_ID' --destination-cidr-block '$VPC2_CIDR' --vpc-peering-connection-id '$PEERING_ID'" check_error $? "Failed to create route from VPC1 to VPC2" echo "Route created from VPC1 to VPC2" # Associate the route table with the subnet in VPC1 echo "Associating route table with subnet in VPC1..." RTB1_ASSOC_ID=$(log_cmd "aws ec2 associate-route-table --region '$AWS_REGION' --route-table-id '$RTB1_ID' --subnet-id '$SUBNET1_ID' --query 'AssociationId' --output text") check_error $? "Failed to associate route table with subnet in VPC1" RTB1_ASSOC_ID=$(sanitize_var "$RTB1_ASSOC_ID") || check_error 1 "Invalid RTB1_ASSOC_ID returned" CREATED_RESOURCES+=("Route Table Association for VPC1: $RTB1_ASSOC_ID") CLEANUP_COMMANDS+=("aws ec2 disassociate-route-table --region '$AWS_REGION' --association-id '$RTB1_ASSOC_ID'") echo "Route table associated with subnet in VPC1" # Create a route table for VPC2 echo "Creating route table for VPC2..." RTB2_ID=$(log_cmd "aws ec2 create-route-table --region '$AWS_REGION' --vpc-id '$VPC2_ID' --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=VPC2-RouteTable}]' --query 'RouteTable.RouteTableId' --output text") check_error $? "Failed to create route table for VPC2" RTB2_ID=$(sanitize_var "$RTB2_ID") || check_error 1 "Invalid RTB2_ID returned" CREATED_RESOURCES+=("Route Table for VPC2: $RTB2_ID") CLEANUP_COMMANDS+=("aws ec2 delete-route-table --region '$AWS_REGION' --route-table-id '$RTB2_ID'") echo "Route table created for VPC2 with ID: $RTB2_ID" # Create a route from VPC2 to VPC1 echo "Creating route from VPC2 to VPC1..." log_cmd "aws ec2 create-route --region '$AWS_REGION' --route-table-id '$RTB2_ID' --destination-cidr-block '$VPC1_CIDR' --vpc-peering-connection-id '$PEERING_ID'" check_error $? "Failed to create route from VPC2 to VPC1" echo "Route created from VPC2 to VPC1" # Associate the route table with the subnet in VPC2 echo "Associating route table with subnet in VPC2..." RTB2_ASSOC_ID=$(log_cmd "aws ec2 associate-route-table --region '$AWS_REGION' --route-table-id '$RTB2_ID' --subnet-id '$SUBNET2_ID' --query 'AssociationId' --output text") check_error $? "Failed to associate route table with subnet in VPC2" RTB2_ASSOC_ID=$(sanitize_var "$RTB2_ASSOC_ID") || check_error 1 "Invalid RTB2_ASSOC_ID returned" CREATED_RESOURCES+=("Route Table Association for VPC2: $RTB2_ASSOC_ID") CLEANUP_COMMANDS+=("aws ec2 disassociate-route-table --region '$AWS_REGION' --association-id '$RTB2_ASSOC_ID'") echo "Route table associated with subnet in VPC2" # Verify the VPC peering connection echo "Verifying VPC peering connection..." log_cmd "aws ec2 describe-vpc-peering-connections --region '$AWS_REGION' --vpc-peering-connection-ids '$PEERING_ID' --query 'VpcPeeringConnections[0].[VpcPeeringConnectionId,Status.Code,AccepterVpcInfo.VpcId,RequesterVpcInfo.VpcId]' --output table" check_error $? "Failed to verify VPC peering connection" echo "VPC peering connection verified" # Display summary of created resources echo "" echo "==============================================" echo "SUMMARY OF CREATED RESOURCES" echo "==============================================" echo "VPC1 ID: $VPC1_ID" echo "VPC1 CIDR: $VPC1_CIDR" echo "Subnet1 ID: $SUBNET1_ID (CIDR: $VPC1_SUBNET_CIDR)" echo "VPC2 ID: $VPC2_ID" echo "VPC2 CIDR: $VPC2_CIDR" echo "Subnet2 ID: $SUBNET2_ID (CIDR: $VPC2_SUBNET_CIDR)" echo "Peering Connection ID: $PEERING_ID" echo "Route Table 1 ID: $RTB1_ID" echo "Route Table 1 Association ID: $RTB1_ASSOC_ID" echo "Route Table 2 ID: $RTB2_ID" echo "Route Table 2 Association ID: $RTB2_ASSOC_ID" echo "" echo "Created resources:" for resource in "${CREATED_RESOURCES[@]+"${CREATED_RESOURCES[@]}"}"; do echo "- $resource" done echo "==============================================" echo "" # Test connectivity (optional) echo "==============================================" echo "CONNECTIVITY TEST" echo "==============================================" echo "To test connectivity between VPCs, you would need to:" echo "1. Launch EC2 instances in each subnet" echo "2. Configure security groups to allow traffic" echo "3. Test ping or other network connectivity" echo "" # Automatic cleanup echo "" echo "==============================================" echo "CLEANUP CONFIRMATION" echo "==============================================" echo "Auto-confirming cleanup of all created resources..." CLEANUP_CHOICE="y" if [[ "${CLEANUP_CHOICE,,}" == "y" ]]; then echo "Starting cleanup process..." # Clean up in reverse order echo "Disassociating route table from subnet in VPC2..." log_cmd "aws ec2 disassociate-route-table --region '$AWS_REGION' --association-id '$RTB2_ASSOC_ID'" || true echo "Disassociating route table from subnet in VPC1..." log_cmd "aws ec2 disassociate-route-table --region '$AWS_REGION' --association-id '$RTB1_ASSOC_ID'" || true echo "Deleting route table for VPC2..." log_cmd "aws ec2 delete-route-table --region '$AWS_REGION' --route-table-id '$RTB2_ID'" || true echo "Deleting route table for VPC1..." log_cmd "aws ec2 delete-route-table --region '$AWS_REGION' --route-table-id '$RTB1_ID'" || true echo "Deleting VPC peering connection..." log_cmd "aws ec2 delete-vpc-peering-connection --region '$AWS_REGION' --vpc-peering-connection-id '$PEERING_ID'" || true echo "Deleting subnet in VPC2..." log_cmd "aws ec2 delete-subnet --region '$AWS_REGION' --subnet-id '$SUBNET2_ID'" || true echo "Deleting subnet in VPC1..." log_cmd "aws ec2 delete-subnet --region '$AWS_REGION' --subnet-id '$SUBNET1_ID'" || true # Delete VPCs if they were created by this script if [ "$CREATE_VPCS" = true ]; then echo "Deleting VPC2..." log_cmd "aws ec2 delete-vpc --region '$AWS_REGION' --vpc-id '$VPC2_ID'" || true echo "Deleting VPC1..." log_cmd "aws ec2 delete-vpc --region '$AWS_REGION' --vpc-id '$VPC1_ID'" || true elif [ "$CREATE_VPC2_ONLY" = true ]; then echo "Deleting VPC2..." log_cmd "aws ec2 delete-vpc --region '$AWS_REGION' --vpc-id '$VPC2_ID'" || true fi echo "Cleanup completed successfully." else echo "Cleanup skipped. Resources will remain in your AWS account." echo "" echo "To manually clean up later, you can delete resources in this order:" echo "1. Route table associations" echo "2. Route tables" echo "3. VPC peering connection" echo "4. Subnets" if [ "$CREATE_VPCS" = true ] || [ "$CREATE_VPC2_ONLY" = true ]; then echo "5. VPCs (if created by this script)" fi fi echo "Script execution completed. See $LOG_FILE for detailed logs."-
For API details, see the following topics in AWS CLI Command Reference.
-
For a complete list of AWS SDK developer guides and code examples, see Create Amazon EC2 resources using an AWS SDK. This topic also includes information about getting started and details about previous SDK versions.
Working with Amazon EBS encryption, snapshots, and volume initialization
Monitor API requests using CloudWatch