July 12, 2024
aws, cli, vpc, bastion
Use AWS CLI to build a VPC with a Bastion
Create VPC
aws ec2 create-vpc --cidr-block $VPC_CIDR --tag-specifications 'ResourceType=vpc,Tags=[{Key="Name",Value="TOOLS_VPC"}]'
Create Subnets
aws ec2 create-subnet --availability-zone us-west-2a --cidr-block 10.0.0.0/26 --vpc-id $VPC_ID --tag-specifications 'ResourceType=subnet,Tags=[{Key="Name",Value="TOOLS_Public1"}]'
aws ec2 create-subnet --availability-zone us-west-2b --cidr-block 10.0.0.64/26 --vpc-id $VPC_ID --tag-specifications 'ResourceType=subnet,Tags=[{Key="Name",Value="TOOLS_Public2"}]'
aws ec2 create-subnet --availability-zone us-west-2a --cidr-block 10.0.0.128/26 --vpc-id $VPC_ID --tag-specifications 'ResourceType=subnet,Tags=[{Key="Name",Value="TOOLS_Private1"}]'
aws ec2 create-subnet --availability-zone us-west-2b --cidr-block 10.0.0.192/26 --vpc-id $VPC_ID --tag-specifications 'ResourceType=subnet,Tags=[{Key="Name",Value="TOOLS_Private2"}]'
Create Internet Gateway with Name tag
aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key="Name",Value="TOOLS_IGW"}]'
Attach IGW to new VPC
aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
Create Route Tables
aws ec2 create-route-table --vpc-id $VPC_ID --tag-specifications 'ResourceType=route-table,Tags=[{Key="Name",Value="TOOLS_PublicRT"}]'
aws ec2 create-route-table --vpc-id $VPC_ID --tag-specifications 'ResourceType=route-table,Tags=[{Key="Name",Value="TOOLS_PrivateRT"}]'
Associate Route Tables with Subnets (Need public1 for app server, public2 for NAT GW)
aws ec2 associate-route-table --subnet-id $TOOLS_SUBNET_PUBLIC1 --route-table-id $PUBLIC_ROUTE_TABLE_ID
aws ec2 associate-route-table --subnet-id $TOOLS_SUBNET_PUBLIC2 --route-table-id $PUBLIC_ROUTE_TABLE_ID
Create Route Entries
aws ec2 create-route --route-table-id $PUBLIC_ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
Create NACL: Note that if the NACL is not in the default VPC, you'll be unable to associate this NACL with any subnet using the AWS CLI
You can use the AWS Console to associate NACL's without an associationID, but not with the AWS CLI
aws ec2 create-network-acl --tag-specifications 'ResourceType=network-acl,Tags=[{Key="Name",Value="TOOLS_Public1"}]'
Create NACL Entry
MY_IP=$(curl ifconfig.co) aws ec2 create-network-acl-entry --ingress --rule-number 110 --rule-action allow --port-range From=22,To=22 --protocol tcp --cidr-block $MY_IP/32 --network-acl-id $PUBLIC_NACL_ID aws ec2 create-network-acl-entry --egress --rule-number 110 --rule-action allow --port-range From=1024,To=65535 --protocol tcp --cidr-block $MY_IP/32 --network-acl-id $PUBLIC_NACL_ID
Describe NACL with filter
When you create a new NACL, it is automatically associated with the default subnet if the subnet is in the default VPC.
We need to find the associationId for the NACL so we can replace it with a new one, to the correct subnet
Default subnets can only be created in the default VPC, so NACL cannot be associated using the CLI
aws ec2 describe-network-acls --filters 'Name="association.association-id",Values=$NACL_ASSOCIATION_ID' aws ec2 describe-network-acls --filters Name=network-acl-id,Values=$NACL_ID ## Remove all quotes for interpolation of shell variable aws ec2 describe-network-acls --filters Name=network-acl-id,Values=$PUBLIC_NACL_ID --query 'NetworkAcls[*].Associations[*].[NetworkAclAssociationId, NetworkAclId, SubnetId]'
Replace NACL AssociationId with a new AssociationId
aws ec2 replace-network-acl-association --association-id $CURRENT_NACL_ASSOCIATION_ID --network-acl-id $NACL_ID
Create Bastion Security Group
aws ec2 create-security-group --group-name ATD_BASTION_SG --description ATD_BASTION_SG --vpc-id $VPC_ID
Add Port 22 Ingress Rule to Bastion Security Group
aws ec2 authorize-security-group-ingress --group-id $BASTION_SECURITY_GROUP --protocol tcp --port 22 --cidr $MY_CIDR
Get Latest Amazon2 Linux Instance ID
aws ec2 describe-images --filters "Name=name,Values=amzn2-ami-hvm*" --query 'reverse(sort_by(Images, &CreationDate))[0]' --owners amazon
Create EC2 KeyPair
aws ec2 create-key-pair --key-name $TOOLS_BASTION_KEYNAME --key-type rsa --tag-specifications 'ResourceType=key-pair,Tags=[{Key="Name",Value="TOOLS_BASTION_KEYPAIR"}]' | jq -r ".KeyMaterial" > ./$TOOLS_BASTION_KEYNAME.pem
Create Bastion Instance
aws ec2 run-instances --image-id $AMI_ID --instance-type t2.micro --subnet-id $TOOLS_SUBNET_PUBLIC1 --associate-public-ip-address --key-name $TOOLS_BASTION_KEYNAME --tag-specifications 'ResourceType=instance,Tags=[{Key="Name",Value="TOOLS_BASTION"}]'
You Can't Specify a Network Interface like a public-ip-address at the same time as you specify a Security Group, so we do it here
aws ec2 modify-instance-attribute --instance-id i-0a1366d91f36caf34 --groups $BASTION_SECURITY_GROUP
Update Bastion SG to allow outbound port 22 to all instances in VPC
aws ec2 authorize-security-group-egress --group-id $BASTION_SECURITY_GROUP --protocol tcp --port 22 --cidr $VPC_CIDR
Update Public NACL to allow port 22 from instances to Bastion, and Bastion responses on ephemeral ports (1024-65535)
aws ec2 create-network-acl-entry --ingress --protocol tcp --rule-action allow --rule-number 120 --port-range From=1024,To=65535 --network-acl-id $PUBLIC_NACL_ID --cidr-block $VPC_CIDR
aws ec2 create-network-acl-entry --egress --protocol tcp --rule-action allow --rule-number 120 --port-range From=22,To=22 --network-acl-id $PUBLIC_NACL_ID --cidr-block $VPC_CIDR
Create Private3 NACL
aws ec2 create-network-acl --vpc-id $VPC_ID --tag-specifications 'ResourceType=network-acl,Tags=[{Key="Name",Value="TOOLS_Private3"}]'
Check that the new NACL is associate with a subnet, with the CLI there's no way to associate a NACL with a subnet without an 'associationId' AssociationId's are only available when a NACL has a subnet associated. If there's no default subnet, the association won't happen after the create-network-acl command.
Update Private3 NACL to allow port 22 inbound from Public1 CIDR
aws ec2 create-network-acl-entry --ingress --protocol tcp --rule-action allow --rule-number 110 --port-range From=22,To=22 --network-acl-id $PRIVATE3_NACL_ID --cidr-block $PUBLIC1_SUBNET_CIDR
Update Private3 NACL to allow ICMP pings from 0.0.0.0/0
aws ec2 create-network-acl-entry --ingress --protocol icmp --icmp-type-code Code="-1",Type="-1" --rule-action allow --rule-number 120 --network-acl-id $PRIVATE3_NACL_ID --cidr-block 0.0.0.0/0
Update Private3 NACL to allow ephemeral from 0.0.0.0/0
aws ec2 create-network-acl-entry --ingress --rule-action allow --protocol tcp --port-range From=1024,To=65535 --rule-number 140 --network-acl-id $PRIVATE3_NACL_ID --cidr-block 0.0.0.0/0
Update Private NACL to allow Outbound 443 to 0.0.0.0/0
aws ec2 create-network-acl-entry --egress --protocol tcp --rule-action allow --rule-number 110 --port-range From=443,To=443 --network-acl-id $PRIVATE3_NACL_ID --cidr-block 0.0.0.0/0
Update Private3 NACL to allow Outbound ICMP to 0.0.0.0/0
aws ec2 create-network-acl-entry --egress --protocol icmp --icmp-type-code Code="-1",Type="-1" --rule-action allow --rule-number 120 --network-acl-id $PRIVATE3_NACL_ID --cidr-block 0.0.0.0/0
Create SecurityGroup for Private Subnets
aws ec2 create-security-group --group-name ATD_Private34_SG --description ATD_Private34_SG --vpc-id $VPC_ID --tag-specifications 'ResourceType=security-group,Tags=[{Key="Name",Value="ATD_Private34_SG"}]'
Create ingress rule for SSH port 22 from Bastion Subnet in Private34 security group
aws ec2 authorize-security-group-ingress --group-id $PRIVATE34_SG --protocol tcp --port 22 --cidr $PUBLIC1_SUBNET_CIDR
Create egress rule for HTTPS 443 to 0.0.0.0/0 in Private34 security group
aws ec2 authorize-security-group-egress --group-id $PRIVATE34_SG --protocol tcp --port 443 --cidr 0.0.0.0/0
Create egress rule for ICMP to 0.0.0.0/0 in Private34 security group
aws ec2 authorize-security-group-egress --group-id $PRIVATE34_SG --ip-permissions IpProtocol=icmp,FromPort=-1,ToPort=-1,IpRanges='[{CidrIp=0.0.0.0/0}]'
Create PrivateAppServer ec2 instance
aws ec2 run-instances --image-id $AMI_ID --instance-type t2.micro --subnet-id $PRIVATE1_SUBNET_ID --key-name tools-private-keypair --tag-specifications 'ResourceType=instance,Tags=[{Key="Name",Value="TOOLS_PrivateAppServer"}]'
Update PrivateAppServer with Private34 SG
aws ec2 modify-instance-attribute --instance-id $PRIVATE_INSTANCE_ID --groups $PRIVATE1_SG
Update Private3 NACL to allow ephemeral Outbound to 0.0.0.0/0
aws ec2 create-network-acl-entry --egress --rule-action allow --protocol tcp --port-range From=1024,To=65535 --rule-number 130 --network-acl-id $PRIVATE3_NACL_ID --cidr-block 0.0.0.0/0
aws ec2 create-network-acl --tag-specifications 'ResourceType=network-acl,Tags=[{Key="Name",Value="TOOLS_Public2"}]' --vpc-id $VPC_ID
aws ec2 create-network-acl-entry --ingress --rule-number 110 --rule-action allow --port-range From=443,To=443 --protocol tcp --cidr-block $VPC_CIDR --network-acl-id $PUBLIC2_NACL_ID
aws ec2 create-network-acl-entry --ingress --rule-number 120 --rule-action allow --port-range From=1024,To=65535 --protocol tcp --cidr-block 0.0.0.0/0 --network-acl-id $PUBLIC2_NACL_ID
aws ec2 create-network-acl-entry --ingress --protocol icmp --icmp-type-code Code="-1",Type="-1" --rule-action allow --rule-number 130 --network-acl-id $PUBLIC2_NACL_ID --cidr-block 0.0.0.0/0
aws ec2 create-network-acl-entry --egress --protocol icmp --icmp-type-code Code="-1",Type="-1" --rule-action allow --rule-number 130 --network-acl-id $PUBLIC2_NACL_ID --cidr-block 0.0.0.0/0
aws ec2 create-network-acl-entry --egress --rule-number 120 --rule-action allow --port-range From=1024,To=65535 --protocol tcp --cidr-block $VPC_CIDR --network-acl-id $PUBLIC2_NACL_ID
aws ec2 create-network-acl-entry --egress --rule-number 110 --rule-action allow --port-range From=443,To=443 --protocol tcp --cidr-block 0.0.0.0/0 --network-acl-id $PUBLIC2_NACL_ID
Create EIP for NAT Gateway
aws ec2 allocate-address
Create NAT Gateway in Public2 Subnet
aws ec2 create-nat-gateway --allocation-id $EIP_ALLOCATION_ID --tag-specifications 'ResourceType=natgateway,Tags=[{Key="Name",Value="TOOLS_Public2_NATGW"}]' --subnet-id $PUBLIC2_SUBNET
If NAT GW is correctly configured, you can ping www.example.com from the Private ec2 instance
Mistakes:
1) didn't associate PublicRT with Public2 --> NATGW didn't allow ping to Internet from Private subnet
2) didn't set Private34 NACL to allow ICMP in from 0.0.0.0/0, used VPC CIDR in error --> this also broke ping response from Internet through NATGW
NOTE: this is a pain through AWS CLI, could combine some Rule setup, but this is better done with Terraform or CloudFormation
Loading comments...