__biancatcatcat

Creating a VPC using Boto3 with IPv6

This will guide you through using Boto3 to create a VPC programmatically.

This guide assumes:

  • That you have an AWS account
  • That you have Boto3 authentication configured

This will especially be helpful for you if:

  • You're migrating from EC2-Classic to VPC
  • You're migrating from Boto to Boto3

If you would just like the commands to run, you can skip to the code summary. Otherwise, read on for a step-by-step guide.

Detailed Guide

On the AWS VPC console, there are helpful wizards that will create and configure your VPC. Let's take the VPC with a Single Public Subnet wizard, and do the same thing using Boto3.

These are the goals:

  1. Create a new VPC
  2. Create a subnet for that VPC
  3. Create a security group that will allow you to SSH, HTTP, and HTTPS traffic into your VPC-launched instances
  4. Launch an instance into your VPC (optional)

What the wizard creates:

The VPC with a Single Public Subnet wizard creates for you one VPC, and:

  • One internet gateway
  • Two route tables:
    • A secondary route table
    • A "main" route table
      • "The main route table controls the routing for all subnets that are not explicitly associated with any other route table. You can add, remove, and modify routes in the main route table." [src]
  • A subnet, by default named 'Public Subnet', automatically associated with the secondary route table, and given access to the internet gateway

1. Creating the VPC

In an example file called vpc.py, let's create a VPC.

For this, we'll use Boto3's EC2 resource objects:

import boto3

ec2 = boto3.resource(service_name='ec2')  

Let's use ec2.create_vpc() to create our VPC and return it. The function returns a VPC resource object.

# vpc.py
import boto3

def create_and_configure_vpc():  
    ec2 = boto3.resource('ec2')
    vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16', AmazonProvidedIpv6CidrBlock=True)

    return vpc

Okay, so that should automatically set everything up! Kidding.

At this point, we have a VPC in our default region, and a main route table that's automatically created and attached to our VPC. We also have a default security group that allows traffic to and from within that security group.


2. Adding a subnet and gateway:

We can create a subnet by using the VPC resource's create_subnet() function. Let's modify what we have in create_and_configure_vpc() append the following before return vpc:

# vpc.py in create_and_configure_vpc()
# ... snip ...

# Assign IPv6 block for subnet using CIDR provided by Amazon, except different size (must use /64)
ipv6_subnet_cidr = vpc.ipv6_cidr_block_association_set[0]['Ipv6CidrBlock']  
ipv6_subnet_cidr = ipv6_subnet_cidr[:-2] + '64'

subnet = vpc.create_subnet(CidrBlock='10.0.0.0/24', Ipv6CidrBlock=ipv6_subnet_cidr)

return vpc

Note: While the other CIDR block sizes so far have been customizable, /64 is required by Amazon for the IPv6 subnet.

Note: You can also decide whether to automatically assign a public IPv4 and/or a public IPv6 address to network interfaces in this subnet using ec2.modify_subnet_attribute()

Let's create an Internet Gateway and attach it to our VPC.
Add the following lines to your function before the return:

# vpc.py in create_and_configure_vpc()
# ... snip ...

internet_gateway = ec2.create_internet_gateway()  
internet_gateway.attach_to_vpc(VpcId=vpc.vpc_id)

return vpc

3. Creating our route table

Next, let's create a route table.

# vpc.py in create_and_configure_vpc()
# ... snip ...

route_table = vpc.create_route_table()  

A newly created route table will have routes for the local network through IPv4 addresses and (if enabled) IPv6. However, we need to add the routes for our internet gateway, so let's write in the following in our function:

# vpc.py in create_and_configure_vpc()
# ... snip ...

route_ig_ipv4 = route_table.create_route(DestinationCidrBlock='0.0.0.0/0', GatewayId=internet_gateway.internet_gateway_id)  
route_ig_ipv6 = route_table.create_route(DestinationIpv6CidrBlock='::/0', GatewayId=internet_gateway.internet_gateway_id)  

Lastly, don't forget to associate this route table with our subnet!

# vpc.py in create_and_configure_vpc()
# ... snip ...

route_table.associate_with_subnet(SubnetId=subnet.id)  

Recap! After adding these three code snippets to our current function, here's what we have so far in vpc.py:

# vpc.py
import boto3

def create_and_configure_vpc():  
    ec2 = boto3.resource('ec2')

    # Create the VPC, and request an IPv6 CIDR block
    vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16', AmazonProvidedIpv6CidrBlock=True)

    # Assign IPv6 block for subnet using CIDR provided by Amazon, except different size (must use /64)
    ipv6_subnet_cidr = vpc.ipv6_cidr_block_association_set[0]['Ipv6CidrBlock']
    ipv6_subnet_cidr = ipv6_subnet_cidr[:-2] + '64'

    subnet = vpc.create_subnet(CidrBlock='10.0.0.0/24', Ipv6CidrBlock=ipv6_subnet_cidr)

    internet_gateway = ec2.create_internet_gateway()
    internet_gateway.attach_to_vpc(VpcId=vpc.vpc_id)

    route_table = vpc.create_route_table()
    route_ig_ipv4 = route_table.create_route(DestinationCidrBlock='0.0.0.0/0', GatewayId=internet_gateway.internet_gateway_id)
    route_ig_ipv6 = route_table.create_route(DestinationIpv6CidrBlock='::/0', GatewayId=internet_gateway.internet_gateway_id)

    route_table.associate_with_subnet(SubnetId=subnet.id)

    return vpc

If at this point you feel the urge to break this up into smaller functions, feel free to do so. Adding all the code to a single function is just for the sake of this example.


4. Security Groups

Whew. Lastly, we need to create and configure our security groups. In this example, we'll only create one. Add the following line to our function:

# vpc.py in create_and_configure_vpc()
# ... snip ...

sg = vpc.create_security_group(GroupName="sample-name", Description="A sample description")  

This creates an empty group with no rules. In this case, we'll allow incoming TCP traffic on port 22 from all IP address (which ultimately you should modify for security reasons). We'll also allow incoming TCP traffic on port 80 and 443.

# vpc.py in create_and_configure_vpc()
# ... snip ...

ip_ranges = [{  
    'CidrIp': '0.0.0.0/0'
}]

ip_v6_ranges = [{  
    'CidrIpv6': '::/0'
}]

perms = [{  
    'IpProtocol': 'TCP',
    'FromPort': 80,
    'ToPort': 80,
    'IpRanges': ip_ranges,
    'Ipv6Ranges': ip_v6_ranges
}, {
    'IpProtocol': 'TCP',
    'FromPort': 443,
    'ToPort': 443,
    'IpRanges': ip_ranges,
    'Ipv6Ranges': ip_v6_ranges
}, {
    'IpProtocol': 'TCP',
    'FromPort': 22,
    'ToPort': 22,
    'IpRanges': ip_ranges,
    'Ipv6Ranges': ip_v6_ranges
}]

Note: While this is okay as an example, please be aware of the security risks of having your ports, notably SSH port 22, open to all.

And lastly, add these rules as ingress rules to the security group we created:

# vpc.py in create_and_configure_vpc()
# ... snip ...

sg.authorize_ingress(IpPermissions=perms)  

The IpPermissions parameter allows us to specify more than one rule at a time. The Code Summary shows the final function body.

Code Summary:

And you're done! Instances launched in this subnet should allow traffic to the internet, and TCP traffic from port 80, 22, and 443.

Here's the code summary!

Ideally you might want to separate these into separate functions and handle for HTTP error codes on the API requests.

def create_and_configure_vpc():  
    ec2 = boto3.resource('ec2')

    # Create the VPC
    vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16', AmazonProvidedIpv6CidrBlock=True)

    # Create an Internet Gateway and attach it to the VPC
    internet_gateway = ec2.create_internet_gateway()
    internet_gateway.attach_to_vpc(VpcId=vpc.vpc_id) # Returns None

    # Route Table
    route_table = vpc.create_route_table()

    route_ig_ipv4 = route_table.create_route(DestinationCidrBlock='0.0.0.0/0', GatewayId=internet_gateway.internet_gateway_id)
    route_ig_ipv6 = route_table.create_route(DestinationIpv6CidrBlock='::/0', GatewayId=internet_gateway.internet_gateway_id)

    # Create a subnet in our VPC
    # Assign IPv6 block for subnet using CIDR provided by Amazon, except different size (must use /64)
    ipv6_subnet_cidr = vpc.ipv6_cidr_block_association_set[0]['Ipv6CidrBlock']
    ipv6_subnet_cidr = ipv6_subnet_cidr[:-2] + '64'
    subnet = vpc.create_subnet(CidrBlock='10.0.0.0/24', Ipv6CidrBlock=ipv6_subnet_cidr)

    # Associate it with subnet
    route_table.associate_with_subnet(SubnetId=subnet.id)

    # Security groups
    sg = vpc.create_security_group(GroupName="sample-name", Description="A sample description")

    ip_ranges = [{
        'CidrIp': '0.0.0.0/0'
    }]

    ip_v6_ranges = [{
        'CidrIpv6': '::/0'
    }]

    perms = [{
        'IpProtocol': 'TCP',
        'FromPort': 80,
        'ToPort': 80,
        'IpRanges': ip_ranges,
        'Ipv6Ranges': ip_v6_ranges
    }, {
        'IpProtocol': 'TCP',
        'FromPort': 443,
        'ToPort': 443,
        'IpRanges': ip_ranges,
        'Ipv6Ranges': ip_v6_ranges
    }, {
        'IpProtocol': 'TCP',
        'FromPort': 22,
        'ToPort': 22,
        'IpRanges': ip_ranges, # Remember to change this!
        'Ipv6Ranges': ip_v6_ranges # Remember to change this!
    }]

    sg.authorize_ingress(IpPermissions=perms)

    return vpc

*Using boto3 to launch an instance is beyond the scope of this tutorial. However it should be straightforward using the AWS web console:

  • Click 'Launch Instance'
  • Choose your VPC and the subnet to launch the instance into
  • When prompted, select 'Existing Security Groups' and select the security group we created (sample-name)

Scribbles, notes, and troubleshooting:

  • "If your instance was launched from an AMI that is not configured to use DHCPv6, you must manually configure your instance to recognize an IPv6 address assigned to the instance." src

Links and extra reading:

Originally published: Mar 19, 2017

What would you like to see me write about? Comments and questions are welcome in the comments or on Twitter!