loading...

AWS – Creating a VPC and subnets

Create a Home page in ASP.Net Core 3.1 MVC

In this recipe, we’re going to build a secure network (VPC) in AWS. This network will consist of two public and private subnets split across two availability zones (AZ). It will also allow inbound connections to the public subnets for the following protocols:

  • SSH (port 22)
  • HTTP (port 80)
  • HTTPS (port 443):

Building a secure network

Getting ready

Before we proceed, you’re going to need to know the names of at least two of the AZ in the region we’re deploying to. The recipes in this book will typically deploy to us-east-1, so to get things moving, you can just use the following:

  • us-east-1a
  • us-east-1b
When you create an AWS account, your zones are randomly allocated. This means that us-east-1a in your account isn’t necessarily the same data center as us-east-1a in my account.

How to do it…

Just a heads up: this will be one of the larger templates that we’ll create in this book. Let’s get started:

  1. Go ahead and create a new CloudFormation template for our VPC. Use the filename 07-01-VPC.yaml.
The entire template can be downloaded from this book’s GitHub repository, which can be found at https://github.com/PacktPublishing/AWS-SysOps-Cookbook-Second-Edition.
  1. Start with the first two parameters, which correspond to the AZ we discussed previously. We don’t provide any default values for these parameters in order to maintain region portability:
Parameters:
  AvailabilityZone1: 
    Description: Availability zone 1 name (e.g. us-east-1a) 
    Type: AWS::EC2::AvailabilityZone::Name 
  AvailabilityZone2: 
    Description: Availability zone 2 name (e.g. us-east-1b) 
    Type: AWS::EC2::AvailabilityZone::Name

  1. Then, add the remaining parameters to define the IP address ranges for the following:
    • The entire VPC
    • The public subnets (A and B)
    • The private subnets (A and B):
  VPCCIDR: 
    Description: CIDR block for VPC 
    Type: String 
    Default: "172.31.0.0/21" # 2048 IP addresses 
  PublicSubnetACIDR: 
    Description: CIDR block for public subnet A 
    Type: String 
    Default: "172.31.0.0/23" # 512 IP address 
  PublicSubnetBCIDR: 
    Description: CIDR block for public subnet B 
    Type: String 
    Default: "172.31.2.0/23" # 512 IP address 
  PrivateSubnetACIDR: 
    Description: CIDR block for private subnet A 
    Type: String 
    Default: "172.31.4.0/23" # 512 IP address 
  PrivateSubnetBCIDR: 
    Description: CIDR block for private subnet B 
    Type: String 
    Default: "172.31.6.0/23" # 512 IP address      
AWS reserves a small number of IP addresses in your IP space for AWS-specific services. The VPC DNS server is one such example of this. It’s usually located at the second (*.2) IP address in the block allocated to your VPC.
  1. Now, we can start to define Resources. We’ll start by defining the VPC itself:
Resources: 
  # VPC & subnets 
  ExampleVPC: 
    Type: AWS::EC2::VPC 
    Properties: 
      CidrBlock: !Ref VPCCIDR 
      EnableDnsSupport: true 
      EnableDnsHostnames: true 
      Tags: 
        - { Key: Name, Value: Example VPC }

  1. Then, we will define the public subnets. Public subnets are defined as subnets with a route to the Internet Gateway (IGW):
PublicSubnetA: 
    Type: AWS::EC2::Subnet 
    Properties: 
      AvailabilityZone: !Ref AvailabilityZone1 
      CidrBlock: !Ref PublicSubnetACIDR 
      MapPublicIpOnLaunch: true 
      VpcId: !Ref ExampleVPC 
      Tags: 
        - { Key: Name, Value: Public Subnet A } 
  PublicSubnetB: 
    Type: AWS::EC2::Subnet 
    Properties: 
      AvailabilityZone: !Ref AvailabilityZone2 
      CidrBlock: !Ref PublicSubnetBCIDR 
      MapPublicIpOnLaunch: true 
      VpcId: !Ref ExampleVPC 
      Tags: 
        - { Key: Name, Value: Public Subnet B } 
  1. Next, we will add the private subnets:
PrivateSubnetA: 
  Type: AWS::EC2::Subnet 
  Properties: 
    AvailabilityZone: !Ref AvailabilityZone1 
    CidrBlock: !Ref PrivateSubnetACIDR 
    VpcId: !Ref ExampleVPC 
    Tags: 
      - { Key: Name, Value: Private Subnet A } 
PrivateSubnetB: 
  Type: AWS::EC2::Subnet 
  Properties: 
    AvailabilityZone: !Ref AvailabilityZone2 
    CidrBlock: !Ref PrivateSubnetBCIDR 
    VpcId: !Ref ExampleVPC 
    Tags: 
      - { Key: Name, Value: Private Subnet B }
  1. Then, we will create the IGW for the VPC:
ExampleIGW: 
  Type: AWS::EC2::InternetGateway 
  Properties: 
    Tags: 
      - { Key: Name, Value: Example Internet Gateway } 
IGWAttachment: 
  Type: AWS::EC2::VPCGatewayAttachment 
  DependsOn: ExampleIGW 
  Properties: 
    VpcId: !Ref ExampleVPC 
    InternetGatewayId: !Ref ExampleIGW
  1. We need to create a couple of route tables. The first one we’ll focus on is the public route table. We’ll assign this route table to the two public subnets we’ve created. This route table will have just one route in it, which will direct all internet-bound traffic to the internet gateway we created in the previous step:
PublicRouteTable: 
  Type: AWS::EC2::RouteTable 
  Properties: 
    VpcId: !Ref ExampleVPC 
    Tags: 
      - { Key: Name, Value: Public Route Table } 
PublicInternetRoute: 
  Type: AWS::EC2::Route 
  DependsOn: IGWAttachment 
  Properties: 
    RouteTableId: !Ref PublicRouteTable 
    GatewayId: !Ref ExampleIGW 
    DestinationCidrBlock: "0.0.0.0/0" 
RouteAssociationPublicA: 
   Type: AWS::EC2::SubnetRouteTableAssociation 
    Properties: 
      RouteTableId: !Ref PublicRouteTable 
      SubnetId: !Ref PublicSubnetA 
  RouteAssociationPublicB: 
    Type: AWS::EC2::SubnetRouteTableAssociation 
    Properties: 
      RouteTableId: !Ref PublicRouteTable 
      SubnetId: !Ref PublicSubnetB
  1. We’ll create the private route table in a similar fashion. Since the private subnet is isolated from the internet, we won’t add a route to the internet gateway. Note that if you were to follow the NAT gateway recipe in this book, it will require a routing table as an input parameter—this is the routing table you want to add NAT routes to:
PrivateRouteTable: 
  Type: AWS::EC2::RouteTable 
  Properties: 
    VpcId: !Ref ExampleVPC 
    Tags: 
      - { Key: Name, Value: Private Route Table } 
  PrivateSubnetAssociationA: 
    Type: AWS::EC2::SubnetRouteTableAssociation 
    Properties: 
      RouteTableId: !Ref PrivateRouteTable 
      SubnetId: !Ref PrivateSubnetA 
  PrivateSubnetAssociationB: 
    Type: AWS::EC2::SubnetRouteTableAssociation 
    Properties: 
      RouteTableId: !Ref PrivateRouteTable 
      SubnetId: !Ref PrivateSubnetB
  1. We can now focus on the security aspects of our network. Let’s focus on public subnets. These are the subnets you’ll add your load balancers to; you’ll also add things such as bastion boxes and NAT gateways. For this, we need to add a Network Access Control List (NACL) with several entries. An NACL is the equivalent of a stateless firewall that is applied to the subnet and allows the following:
    • Allows outbound traffic to all ports. Outbound access is unrestricted from hosts in our public subnets.
    • Allows inbound traffic to ephemeral ports (above 1024). This ensures that packets that are returned to us from our outbound connections are not dropped.
    • Allows inbound access to low port numbers for SSH, HTTP, and HTTPS (22, 80, and 443):
  PublicNACL: 
    Type: AWS::EC2::NetworkAcl 
    Properties: 
      VpcId: !Ref ExampleVPC 
      Tags: 
        - { Key: Name, Value: Example Public NACL } 

  1. Allow outbound to everywhere:
NACLRulePublicEgressAllowAll: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Egress: true 
      Protocol: 6 
      PortRange: { From: 1, To: 65535 } 
      RuleAction: allow 
      RuleNumber: 100 
      NetworkAclId: !Ref PublicNACL

  1. Allow outbound to the VPC on all protocols:
NACLRulePublicEgressAllowAllToVPC: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: !Ref VPCCIDR 
      Egress: true 
      Protocol: -1 
      RuleAction: allow 
      RuleNumber: 200 
      NetworkAclId: !Ref PublicNACL 
  1. Allow inbound from everywhere to ephemeral ports:
NACLRulePublicIngressAllowEphemeral: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Protocol: 6 
      PortRange: { From: 1024, To: 65535 } 
      RuleAction: allow 
      RuleNumber: 100 
      NetworkAclId: !Ref PublicNACL 
  1. Allow inbound from everywhere on port 22 for SSH:
NACLRulePublicIngressAllowSSH: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Protocol: 6 
      PortRange: { From: 22, To: 22 } 
      RuleAction: allow 
      RuleNumber: 200 
      NetworkAclId: !Ref PublicNACL 
  1. Allow inbound from everywhere on port 443 for HTTPS:
NACLRulePublicIngressAllowHTTPS: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Protocol: 6 
      PortRange: { From: 443, To: 443 } 
      RuleAction: allow 
      RuleNumber: 300 
      NetworkAclId: !Ref PublicNACL

  1. Allow inbound from everywhere on port 80 for HTTP:
NACLRulePublicIngressAllowHTTP: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Protocol: 6 
      PortRange: { From: 80, To: 80 } 
      RuleAction: allow 
      RuleNumber: 400 
      NetworkAclId: !Ref PublicNACL 
  1. Allow inbound from VPC on all protocols:
NACLRulePublicIngressAllowFromVPC: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: !Ref VPCCIDR 
      Protocol: -1 
      RuleAction: allow 
      RuleNumber: 500 
      NetworkAclId: !Ref PublicNACL 
  1. Associate the NACLs with the subnets:
NACLAssociationPublicSubnetA: 
    Type: AWS::EC2::SubnetNetworkAclAssociation 
    Properties: 
      NetworkAclId: !Ref PublicNACL 
      SubnetId: !Ref PublicSubnetA 
  NACLAssociationPublicSubnetB: 
    Type: AWS::EC2::SubnetNetworkAclAssociation 
    Properties: 
      NetworkAclId: !Ref PublicNACL 
      SubnetId: !Ref PublicSubnetB
  1. We need to do the same for our private subnets. These subnets are somewhat easier to deal with. They should only be allowed to talk to hosts within our VPC, so we just need to add some NACLs that allow inbound and outbound traffic in our VPCs IP range:
  PrivateNACL: 
    Type: AWS::EC2::NetworkAcl 
    Properties: 
      VpcId: !Ref ExampleVPC 
      Tags: 
        - { Key: Name, Value: Example Private NACL }

  1. Allow all protocols from the VPC range:
NACLRulePrivateIngressAllowVPC: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: !Ref VPCCIDR 
      Protocol: -1 
      RuleAction: allow 
      RuleNumber: 100 
      NetworkAclId: !Ref PrivateNACL
  1. Allow TCP responses from everywhere:
NACLRulePrivateIngressAllowEphemeral: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Protocol: 6 
      PortRange: { From: 1024, To: 65535 } 
      RuleAction: allow 
      RuleNumber: 200 
      NetworkAclId: !Ref PrivateNACL 
  1. The following code allows outbound traffic to everywhere (all protocols):
NACLRulePrivateEgressAllowVPC: 
    Type: AWS::EC2::NetworkAclEntry 
    Properties: 
      CidrBlock: "0.0.0.0/0" 
      Egress: true 
      Protocol: -1 
      RuleAction: allow 
      RuleNumber: 100 
      NetworkAclId: !Ref PrivateNACL 
  1. Associate the NACLs with the subnets:
NACLAssociationPrivateSubnetA: 
    Type: AWS::EC2::SubnetNetworkAclAssociation 
    Properties: 
      NetworkAclId: !Ref PrivateNACL 
      SubnetId: !Ref PrivateSubnetA 
  NACLAssociationPrivateSubnetB: 
    Type: AWS::EC2::SubnetNetworkAclAssociation 
    Properties: 
      NetworkAclId: !Ref PrivateNACL 
      SubnetId: !Ref PrivateSubnetB

  1. Finally, we’ll add some Outputs to our template. These outputs are usually candidates that we can feed into other templates or components of automation:
     
Outputs: 
  ExampleVPC: 
    Value: !Ref ExampleVPC 
  PublicSubnetA: 
    Value: !Ref PublicSubnetA 
  PublicSubnetB: 
    Value: !Ref PublicSubnetB 
  PrivateRouteTable: 
    Value: !Ref PrivateRouteTable 
  PublicRouteTable: 
    Value: !Ref PublicRouteTable 
  PrivateSubnetA: 
    Value: !Ref PrivateSubnetA 
  PrivateSubnetB: 
    Value: !Ref PrivateSubnetB  
  1. You can go ahead and create your VPC in the web console or via the CLI using the following command:
      aws cloudformation create-stack \
        --stack-name secure-vpc \
        --template-body file://07-01-VPC.yaml \
        --parameters \
        ParameterKey=AvailabilityZone1,ParameterValue=<az-1> \
        ParameterKey=AvailabilityZone2,ParameterValue=<az-2>

You can fine-tune this template for your own use, or if you don’t need it after completing the recipe, delete it to avoid any future charges associated with the VPC.

How it works…

When you run this template, AWS will create an isolated, secure network just for you. While it contains a number of resources and concepts that will be familiar to network administrators, it’s essentially an empty shell, which you can now go ahead and populate.

For example, each VPC contains a virtual router. You can’t see it and you can’t log into it to perform any special configuration, but you can customize its behavior by modifying the route tables in this template.

The NACLs we’ve deployed are not stateful and should not be considered a substitution for security groups. NACLs are complementary to security groups, which are stateful and frankly much easier to change and manage than NACLs. While the NACLs in our recipe allow everywhere (0.0.0.0/0) to make inbound connections to port 22, you’ll want to use security groups to lock this down to a specific IP range (your corporate data center, for example).

There’s more…

Actually, there’s a lot more. Despite the amount of code in this recipe, we’ve really only covered the basics of what’s possible with VPCs and networking in AWS. Here are some of the main VPC topics you’ll encounter as you progress with your VPC usage:

  • Direct Connect is a method of connecting DC to your VPC using a private, dedicated pipe. Doing this often provides better network performance, and may also be cheaper than a VPN connection over the internet.
  • You can configure your VPC to connect to your corporate DC over the internet via VPN. This requires that you run supported VPN hardware in your DC.
  • IPv6 is an advanced option that greatly expands the number of available addresses. We’ve left it out to keep things simple.
  • The VPC endpoints feature exposes AWS endpoints inside your VPC so that you don’t have to route traffic over the public internet to consume them. 
  • In VPC peering, you can peer a VPC to one or more VPCs so that (unencrypted) traffic can flow between them. The IP ranges must not clash and, while the peering is free, you will still need to pay for traffic between VPCs. Transitive peering isn’t supported, so if you need the traffic to traverse VPCs, you’ll require a VPN/routing appliance of some kind. Alternatively, you can use the Transit Gateway service.
  • VPC sizing:
    • For IPv4, you can deploy networks between sizes /28 and /16.
    • For IPv6, your VPCs will be fixed in size at /56.
    • A VPC can be resized after creation.
  • Regarding VPC flow logs, you will want to enable VPC flow logs in order to monitor traffic and do any kind of network debugging.
  • Multicast traffic isn’t supported.
  • Subnets must reside in a single AZ; they can’t span AZs.
  • Elastic load balancers (ELBs) can scale out to use a lot of private IP addresses if you are sending a large amount of traffic through them. Keep this in mind when you’re sizing your subnets.
  • The number of VPCs you can deploy is limited to five per region, per account. You can request to increase this limit if necessary. Internet gateways have the same limit, and increasing one limit increases the other.
  • The default VPC:
    • First and foremost, the default VPC is created automatically for you when you create your account. It has some different properties and behaviors to the VPCs you create for yourself.
    • If you try to launch an EC2 instance without specifying a subnet ID, AWS will attempt to launch it in your default VPC.
    • It consists of only public subnets. These subnets are configured to provide a public IP address to all instances by default.
    • It’s possible to delete the default VPC in a region.

See also

  • Check out the AWS VPC quick start for another best practice example of a VPC that you can install with a single click: https://aws.amazon.com/quickstart/architecture/vpc/

Comments are closed.

loading...