<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Colin Barker</title>
    <description>The latest articles on Forem by Colin Barker (@mystcb).</description>
    <link>https://forem.com/mystcb</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1028133%2F1e73becf-8917-4ee9-a1da-171c8eeef63b.jpeg</url>
      <title>Forem: Colin Barker</title>
      <link>https://forem.com/mystcb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mystcb"/>
    <language>en</language>
    <item>
      <title>Enabling IPv6 on AWS using Terraform - EC2 "Pet" Instance (Part 2)</title>
      <dc:creator>Colin Barker</dc:creator>
      <pubDate>Mon, 06 Mar 2023 12:27:39 +0000</pubDate>
      <link>https://forem.com/aws-builders/enabling-ipv6-on-aws-using-terraform-ec2-pet-instance-part-2-1jp9</link>
      <guid>https://forem.com/aws-builders/enabling-ipv6-on-aws-using-terraform-ec2-pet-instance-part-2-1jp9</guid>
      <description>&lt;p&gt;Header photo by &lt;a href="https://unsplash.com/@nasa?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;NASA&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/Q1p7bh3SHj8?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;: This is Part 2 of my IPv6 on AWS series, the &lt;a href="https://dev.to/mystcb/enabling-ipv6-on-aws-using-terraform-part-1-1i46"&gt;first part is available here&lt;/a&gt;. ⚠️&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a "pet" instance, and why?
&lt;/h2&gt;

&lt;p&gt;Before I start, this is an exceptional use case. When using AWS you should always use ephemeral instances, it is always best practice. There are times however, you might need to do this to a singular instance for any number of very valid reasons. In my case, I have a very cheap proxy service between the outside world any my home network. I use this external service as a very cheap method to enable access to some systems that can be accessed through a Site-to-Site VPN to my home. You might have other reasons too, for example - Microsoft Active Directory servers which are hosted on EC2 instances would be considered "pet".&lt;/p&gt;

&lt;p&gt;In my case, I have this one static instance using an Elastic IPv4 IP, and I would like to give it an IPv6 address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pets vs Cattle Analogy
&lt;/h3&gt;

&lt;p&gt;"&lt;em&gt;cattle, not pets&lt;/em&gt;" - &lt;a href="https://twitter.com/randybias/status/444306871545892864"&gt;Bill Baker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This phase used during a presentation about Scaling SQL Servers way back in 2006, to show how attitudes towards computing evolved since the early days.&lt;/p&gt;

&lt;p&gt;It describes the idea that servers can be two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pets&lt;/strong&gt;: These are your pride and joys, there is just one Oz the Cat that sits on your lap while writing blog posts about IPv6, you look after them, nurture them, and you deal with everything that comes their way as and when it happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cattle&lt;/strong&gt;: You have a farm, you have a vast amount of animals that help you produce several products that you sell to the market. If one of those animals becomes an issue, you "replace" them. No sentimental attachment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will say, this analogy will probably not last the test of time - the latter "Cattle" explanation can upset a few people for different reasons and you probably will see this change in the future. Hoping for something like &lt;em&gt;"Cooker vs Food"&lt;/em&gt; or &lt;em&gt;"House vs Tent"&lt;/em&gt;, but I can't see that happening any time soon!&lt;/p&gt;

&lt;p&gt;So with that in mind, you can see the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pet Instances&lt;/strong&gt;: They have a set hostname, they have had loads of love, care, and attention given to them. When they go wrong, you investigate, identify the issues, remediate, and bring back to health. They also make lots of noises when you need to give them attention. (Yes, Oz is meowing at me at the moment!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cattle Instances&lt;/strong&gt;: They have a randomly generated unique identifier, you keep the safe and secure, but once the instance starts to fail, you quickly take them out of the loop, and replace with a healthy instance. The failed instance, you just get rid of.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Starting Point
&lt;/h2&gt;

&lt;p&gt;Before we begin, we need to set the scene a little. What we will be working with is incredibly simple - an EC2 instance sitting in a Public Subnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zTDL0xcN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2StartingPoint.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zTDL0xcN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2StartingPoint.png" alt="Very basic setup of an EC2 instance in a public subnet" width="321" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the code to deploy the above diagram on my &lt;a href="https://github.com/mystcb/ipv6-on-aws/tree/main/03-sample-vpc-with-ec2"&gt;GitHub - IPv6 on AWS repo&lt;/a&gt;. Feel free to follow along in your own sandbox account if you wish!&lt;/p&gt;

&lt;p&gt;The main block of code we need to start looking at is the &lt;code&gt;ec2_instance&lt;/code&gt; resource, there are some comments inline to explain what we are doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Build the EC2 instance using Amazon Linux 2&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amazon_linux_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;   &lt;span class="c1"&gt;# Dynamically chosen Amazon Linux AMI&lt;/span&gt;
  &lt;span class="nx"&gt;ebs_optimized&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                             &lt;span class="c1"&gt;# EBS Optimised instance&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t4g.nano"&lt;/span&gt;                       &lt;span class="c1"&gt;# Using a Graviton based instance here&lt;/span&gt;
  &lt;span class="nx"&gt;disable_api_termination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                             &lt;span class="c1"&gt;# Always good practice to stop pet instances being terminated&lt;/span&gt;

  &lt;span class="c1"&gt;# Networking settings, setting the private IP to the 10th IP in the subnet, and attaching to the right SG and Subnets&lt;/span&gt;
  &lt;span class="nx"&gt;source_dest_check&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;private_ip&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrhost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# This requires that the metadata endpoint on the instance uses the new IMDSv2 secure endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;metadata_options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;http_endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"enabled"&lt;/span&gt;
    &lt;span class="nx"&gt;http_tokens&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"required"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Sets the size of the EBS root volume attached to the instance&lt;/span&gt;
  &lt;span class="nx"&gt;root_block_device&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;volume_size&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8"&lt;/span&gt;      &lt;span class="c1"&gt;# In GB&lt;/span&gt;
    &lt;span class="nx"&gt;volume_type&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gp3"&lt;/span&gt;    &lt;span class="c1"&gt;# Volume Type&lt;/span&gt;
    &lt;span class="nx"&gt;encrypted&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;     &lt;span class="c1"&gt;# Always best practice to encrypt&lt;/span&gt;
    &lt;span class="nx"&gt;delete_on_termination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;     &lt;span class="c1"&gt;# Make sure that the volume is deleted on termination&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Name of the instance, for the console&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sample-EC2-Instance"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Ensures the Internet Gateway has been setup before deploying the instance&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_igw&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice and simple really, but this is where simple can cause an issue in this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue
&lt;/h2&gt;

&lt;p&gt;Without going to too much detail, as you know &lt;a href="https://developer.hashicorp.com/terraform/language/state"&gt;Terraform uses a State&lt;/a&gt; that will store details about the environment it manages, including the settings and configuration of resources. It will use this information to keep track of what it knows, and what has changed - giving it the great ability to see what will change during the next application of your code. This state is generated from the outputs of the providers API's and your code.&lt;/p&gt;

&lt;p&gt;However, the API call for creating an EC2 instance does more than just create a single EC2 resource. This also happens with multiple services, and multiple cloud providers as well, so it isn't specific to AWS on this case. The single API call makes the process a lot simpler to build up the compute for you, but it includes one additional resource: The &lt;em&gt;Elastic Network Interface&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In typical use, this is great, it saves you building instances with no networking and then having to mess about with it later on. I would also point out, that a cloud service with no networking access, is no different to getting a physical computer, disconnecting everything except the power, wrapping it in concrete, and then seeing what you can do. I mean it will still run (albeit very hot and probably melt), but you can't then do anything with it, or see what is happening!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5FTQ5DRa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2Instance.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5FTQ5DRa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2Instance.png" alt="EC2 instance showing the Elastic Network Interface (ENI) attached" width="880" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform does have a resource for the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_interface.html"&gt;Elastic Network Interface (ENI)&lt;/a&gt; however, as the API created this resource itself, attached it to the instance using the parameters specified in the &lt;code&gt;ec2_instance&lt;/code&gt; resource, Terraform doesn't know about this. Herein lies the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding an IPv6 Address using the CLI
&lt;/h3&gt;

&lt;p&gt;Let's say you don't have your Infrastructure defined as code and you needed to assign an IPv6 address to your instance, thankfully the &lt;code&gt;awscli&lt;/code&gt; has such a method. Under the &lt;code&gt;ec2&lt;/code&gt; service type, there is a command to &lt;code&gt;assign-ipv6-addresses&lt;/code&gt; (&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/ec2/assign-ipv6-addresses.html"&gt;Documentation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;To do this, you would run the following command:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;aws ec2 assign-ipv6-addresses --ipv6-addresses &amp;lt;address&amp;gt; --network-interface-id eni-007ed4d597d6df6b7&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The one item you need to know is, the &lt;code&gt;network-interface-id&lt;/code&gt;. While the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#primary_network_interface_id"&gt;ec2_instance&lt;/a&gt; resource, does have this as an output, the only way to get this is after the resource has been created.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens in Terraform?
&lt;/h3&gt;

&lt;p&gt;When using the AWS EC2 API to create the instance, behind the scenes it will create that interface using the settings, and return the Interface ID. As the API's are not supposed to keep the State themselves, it will always consider a change to those settings in the creation block, as a new interface/network settings.&lt;/p&gt;

&lt;p&gt;As we define the network settings in our resource for the EC2 instance, Terraform knows there is a change to the state, talks to the AWS EC2 API, that then will require the network interface to be recreated. The only way to do that would be create a new interface, detach the old interface, attach the new interface, and you are done, except, that isn't possible. &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#eni-basics"&gt;AWS Documentation on Network Interfaces&lt;/a&gt; state that "Each instance has a default network interface, called the primary network interface. You cannot detach a primary network interface from an instance." This comes into play with my workaround in this blog later on. Therefore, the only option, is to re-create the instance.&lt;/p&gt;

&lt;p&gt;If we were to simply use the &lt;code&gt;ipv6_addresses&lt;/code&gt; in the &lt;code&gt;ec2_instance&lt;/code&gt; block, to add a new IPv6 address, Terraform will report the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and &lt;span class="k"&gt;then &lt;/span&gt;create replacement

Terraform will perform the following actions:

&lt;span class="c"&gt;# aws_instance.test must be replaced&lt;/span&gt;
-/+ resource &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;SNIP&lt;/span&gt;&lt;span class="sh"&gt;&amp;gt;&amp;gt;

      ~ instance_initiated_shutdown_behavior = "stop" -&amp;gt; (known after apply)
      ~ instance_state                       = "running" -&amp;gt; (known after apply)
      ~ ipv6_address_count                   = 0 -&amp;gt; (known after apply)
      ~ ipv6_addresses                       = [ # forces replacement
          + "2a05:d01c:b90:ee00::10",
        ]
      + key_name                             = (known after apply)
      ~ monitoring                           = false -&amp;gt; (known after apply)
      + outpost_arn                          = (known after apply)

      &amp;lt;&amp;lt;SNIP&amp;gt;&amp;gt;

    }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, adding the address &lt;code&gt;forces replacement&lt;/code&gt;. Which for our wonderful pet instance here, is a terrifying thought!&lt;/p&gt;

&lt;h2&gt;
  
  
  Workaround - Pseudo Code
&lt;/h2&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;: This is not going to be the best way, in all honesty this is a hack more than a workaround, but it does make it easier to bypass the issue. Always consider backups before doing this, always consider that if you need to do this, why do you need to do it? ⚠️&lt;/p&gt;

&lt;p&gt;Given that the AWS CLI can add an IPv6 address without rebuilding the instance, as well as the console as well, it does mean that a replacement isn't needed to add the address on. For this, we have to play around a bit with Terraform, run it a few times, to get everything up in sync. To this we will need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an &lt;code&gt;aws_network_interface&lt;/code&gt; resource to match the primary ENI with the IPv6 address&lt;/li&gt;
&lt;li&gt;Import the ENI resource into the Terraform State (requires CLI)&lt;/li&gt;
&lt;li&gt;Apply the changes using Terraform, and confirm the IPv6 address has been attached&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;ipv6_addresses&lt;/code&gt; list to the &lt;code&gt;ec2_instance&lt;/code&gt; resource block&lt;/li&gt;
&lt;li&gt;Delete the &lt;code&gt;aws_network_interface&lt;/code&gt; from the Terraform State (requires CLI)&lt;/li&gt;
&lt;li&gt;Delete the &lt;code&gt;aws_network_interface&lt;/code&gt; resource block from the code&lt;/li&gt;
&lt;li&gt;Run a plan to confirm everything is working&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, there are a few steps to do this, but it does mean that if you ever need to re-deploy the code, this will create an identical copy of the instance (minus data) and matches the code.&lt;/p&gt;

&lt;p&gt;This also removes the issue later on if you ever wish to destroy the code. If you were to stop after the Apply when you have the IPv6 address assigned, you will now have an &lt;code&gt;aws_network_interface&lt;/code&gt; resource block managed by Terraform, but also managed by the EC2 Instance itself. As mentioned before, you cant remove the primary network interface from the EC2 instance, and if you try and run a &lt;code&gt;destroy&lt;/code&gt;, Terraform will attempt to call the API to tell AWS to delete the interface, an come back with a 400 error.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Error: detaching EC2 Network Interface (eni-071f55452ccc18997/eni-attach-0a424f74a43a7a0af): OperationNotPermitted: The network interface at device index 0 cannot be detached.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the workaround requires us to complete all the steps to make this work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workaround - Terraform
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create the ENI
&lt;/h3&gt;

&lt;p&gt;First we need to create the &lt;code&gt;aws_network_interface&lt;/code&gt; resource block. We will need to try and match as much as we can to what we have defined in the &lt;code&gt;ec2_instance&lt;/code&gt; block. Below is an example of of this block created using the sample code above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_network_interface"&lt;/span&gt; &lt;span class="s2"&gt;"test_eni"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="c1"&gt;# Uses the same IP from the ec2_instance resource&lt;/span&gt;
  &lt;span class="nx"&gt;private_ips&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cidrhost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="c1"&gt;# The new IPv6 to assign - note that 16 in hexadecimal is 10&lt;/span&gt;
  &lt;span class="nx"&gt;ipv6_addresses&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cidrhost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="c1"&gt;# Same security group from the ec2_instance resource&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# Continue with additional settings from the ec2_instance resource&lt;/span&gt;
  &lt;span class="nx"&gt;source_dest_check&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;# Match the attachment details on the EC2 instance&lt;/span&gt;
  &lt;span class="nx"&gt;attachment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;instance&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;device_index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Import the ENI into State
&lt;/h3&gt;

&lt;p&gt;Next we need to get the ENI information into the state, to do this we need the network interface ID from AWS. This can be done through the console, or by looking at the State file (if you can). In our example, the ENI is &lt;code&gt;eni-071f55452ccc18997&lt;/code&gt;. Terraform at the bottom of all of its documentation will have the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_interface.html#import"&gt;command you need to import the resource in&lt;/a&gt;, in our case we will need to import this ENI into this resource block&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;terraform import aws_network_interface.test_eni eni-071f55452ccc18997&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Apply the changes
&lt;/h3&gt;

&lt;p&gt;As we have already created the block with the IPv6 address in it, we should be able to run the &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; to add the IPv6 address. Do remember to check your plan! Make sure that it is doing what you expect, in my case the plan showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-place&lt;/span&gt;

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_network_interface.test_eni will be updated in-place&lt;/span&gt;
  ~ resource &lt;span class="s2"&gt;"aws_network_interface"&lt;/span&gt; &lt;span class="s2"&gt;"test_eni"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;                        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eni-071f55452ccc18997"&lt;/span&gt;
      ~ ipv6_address_count        &lt;span class="o"&gt;=&lt;/span&gt; 0 -&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      ~ ipv6_address_list         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt; -&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + ipv6_address_list_enabled &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
      ~ ipv6_addresses            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
          + &lt;span class="s2"&gt;"2a05:d01c:b90:ee00::10"&lt;/span&gt;,
        &lt;span class="o"&gt;]&lt;/span&gt;
      + private_ip_list_enabled   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false
        &lt;/span&gt;tags                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
      ~ tags_all                  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          + &lt;span class="s2"&gt;"Environment"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sandbox"&lt;/span&gt;
          + &lt;span class="s2"&gt;"Source"&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;# (15 unchanged attributes hidden)&lt;/span&gt;

        &lt;span class="c"&gt;# (1 unchanged block hidden)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown, the only major change will be that there is an additional IPv6 address assigned to it, but also my default tags are added too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j1ATl3s_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2InstanceWithIPv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j1ATl3s_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2InstanceWithIPv6.png" alt="The EC2 instance now has an IPv6 address associated with it" width="880" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Technically at this point, we have done it - but we still have to sort out our Terraform to prevent future issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the IPv6 address to the instance block
&lt;/h3&gt;

&lt;p&gt;Thankfully the block we created, also has the same line that we can copy back into the &lt;code&gt;aws_instance&lt;/code&gt; block, we can sneak this back in to match the state in AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Build the EC2 instance using Amazon Linux 2&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;SNIP&lt;/span&gt;&lt;span class="sh"&gt;&amp;gt;&amp;gt;

  # Networking settings, setting the private IP to the 10th IP in the subnet, and attaching to the right SG and Subnets
  source_dest_check      = false
  private_ip             = cidrhost(aws_subnet.public_a.cidr_block, 10)
  # IPv6 Address for the instance from the aws_network_interface block
  ipv6_addresses         = [cidrhost(aws_subnet.public_a.ipv6_cidr_block, 16)]
  subnet_id              = aws_subnet.public_a.id
  vpc_security_group_ids = [aws_security_group.test_sg.id]

  &amp;lt;&amp;lt;SNIP&amp;gt;&amp;gt;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete the ENI from the State
&lt;/h3&gt;

&lt;p&gt;Before we remove the block, we need to &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/state/rm"&gt;remove the information from the State&lt;/a&gt;. This will prevent us from accidentally deleting the block, running an apply, and getting the 400 error mentioned before! To do this we will need to run the following command:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;terraform state rm aws_network_interface.test_eni&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you can delete the whole &lt;code&gt;aws_network_interface&lt;/code&gt; block from your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run a plan to check
&lt;/h3&gt;

&lt;p&gt;With all this complete, we just need to run one very last &lt;code&gt;terraform plan&lt;/code&gt; to confirm, if everything has gone right then your output should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Just would like to point out, this is not the ideal way of doing this. I am sure there are other ways, but this is how I got around the issue. Thankfully in my case I had access to the Terraform State, which made the addition of the &lt;code&gt;aws_network_interface&lt;/code&gt; a lot easier. This workaround doesn't work too well when you do not have access.&lt;/p&gt;

&lt;p&gt;This technique can work not just on AWS, but other cloud providers too - its mainly about the logical steps of importing the unmanaged resource, making the changes, and then releasing it back to AWS to manage.&lt;/p&gt;

&lt;p&gt;However, we are reminded here why pet instances are just that, sometimes they can be a bit of a pain, but you nurture them for a reason! In my case, I am just a little bit lazy, and didn't want to have to set everything up again!&lt;/p&gt;

&lt;p&gt;Hopefully I will continue this IPv6 series soon, where I will go over a number of other services - to see if we can't push forward with the IPv6 transition.&lt;/p&gt;

&lt;p&gt;Any comments or queries will be greatly appreciated!&lt;/p&gt;

&lt;h2&gt;
  
  
  Oz The Cat
&lt;/h2&gt;

&lt;p&gt;For anyone that is wondering, this is Oz, he is a lovely cat!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZjjE6C05--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2InstanceOzTheCat.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZjjE6C05--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/03/IPv6EC2InstanceOzTheCat.jpg" alt="This is Oz!" width="880" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>tutorial</category>
      <category>ipv6</category>
    </item>
    <item>
      <title>GitHub Actions using AWS and OpenID</title>
      <dc:creator>Colin Barker</dc:creator>
      <pubDate>Tue, 28 Feb 2023 19:23:56 +0000</pubDate>
      <link>https://forem.com/mystcb/github-actions-using-aws-and-openid-1jlo</link>
      <guid>https://forem.com/mystcb/github-actions-using-aws-and-openid-1jlo</guid>
      <description>&lt;p&gt;Header photo by &lt;a href="https://unsplash.com/@synkevych?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Roman Synkevych 🇺🇦&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/wX2L8L-fGeA?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OpenID?
&lt;/h2&gt;

&lt;p&gt;Always a good start, understanding what the key component to this whole post is! As always though, I will reference &lt;a href="https://en.wikipedia.org/wiki/OpenID"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"OpenID is an open standard and decentralized authentication protocol promoted by the non-profit OpenID Foundation. It allows users to be authenticated by co-operating sites (known as relying parties, or RP) using a third-party identity provider (IDP) service..."&lt;/em&gt; &lt;a href="https://en.wikipedia.org/wiki/OpenID"&gt;Wikipedia - OpenID&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simple right! Well the technical details of how OpenID works is probably for a much more in-depth specific technical blog for this technology, but the one thing that we need to understand here is that OpenID allows users to be authenticated using 3rd party identify providers. In the case of AWS, their OpenID Connect set up would allow a service in GitHub to authenticate to AWS, and through the IAM system, assume a specific role.&lt;/p&gt;

&lt;p&gt;The question is, why go to the effort to set this all up when a simple Access Key/Secret Key combination would work? Well, as you know from &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/sec-iam.html"&gt;AWS Well-Architected best practices&lt;/a&gt;, you should always use temporary credentials over static ones. With the &lt;a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html"&gt;assumption of an AWS role&lt;/a&gt;, it uses temporary credentials. With OpenID Connect-compatible identity providers, such as GitHub, you would need to set this up using a Web Identity source. With this post, I will show you how I set this up for this blog (and for the &lt;a href="https://www.tokonatsu.org.uk"&gt;Tokonatsu&lt;/a&gt; website!)&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions using IAM Access Keys
&lt;/h2&gt;

&lt;p&gt;This is where I started, as you can see below GitHub has my secrets for the &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, statically set quite a while ago!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8FZ8dAaV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsOriginalSecrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8FZ8dAaV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsOriginalSecrets.png" alt="IAM Access Key and Secret Key statically used" width="880" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within my GitHub actions pipeline, I am using the &lt;a href="https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions"&gt;Configure AWS Credentials&lt;/a&gt; action from the &lt;a href="https://github.com/marketplace"&gt;GitHub Marketplace&lt;/a&gt; to configure the secrets for use in the pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-credentials-configure&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEFAULT_REGION }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to see the whole file, then there is a link here to the &lt;a href="https://github.com/mystcb/blog/blob/9909d7cb5dafc560ffb84e235fdf9213d8138f74/.github/workflows/build-and-deploy-site.yaml"&gt;build-and-deploy.yaml&lt;/a&gt; (prior to the changes) file on GitHub.&lt;/p&gt;

&lt;p&gt;As you can see, it is pretty simple, and it worked. The IAM user that owned those keys was happy to sit there and allow the pipeline to access the service. However, that IAM user is still static, and the keys will need to be manually be rotated. From a security stance, anyone that found that key out would be able to do as much as the pipeline could do to my website. As I use &lt;code&gt;aws s3 sync&lt;/code&gt; to copy the website up, while also using the &lt;code&gt;-delete&lt;/code&gt; parameter, it means that I need delete access for this IAM user! Not really the best!&lt;/p&gt;

&lt;p&gt;By using Terraform, I was able to set up all the correct access and switch my pipeline over to using role assumption, thus temporary credentials. GitHub do provide a &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"&gt;walkthrough&lt;/a&gt; to set up the OpenID Connect, which is what I based this configuration on. Along with the Terraform Documentation hopefully, this will help you with your journey!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creation of the OpenID Connect Provider
&lt;/h2&gt;

&lt;p&gt;Setting up the Identity Provider (IdP) will need to be the first step. This action will create a description in IAM of the external IdP and establishes the trust between your account and the organisation, in this case GitHub. This step requires just a few options, of which can be harder to get.&lt;/p&gt;

&lt;p&gt;Using the &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"&gt;walkthrough&lt;/a&gt; documentation, we can see that the following is required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The provider URL - in the case of GitHub this is &lt;code&gt;https://token.actions.githubusercontent.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The "Audience" - which scopes what can use this. Confusingly in Terrraform this is also known as the &lt;code&gt;client_id_list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Thumbprint of the endpoint - This one is the tricker one, as you will need to generate this yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Generating the thumbprint
&lt;/h3&gt;

&lt;p&gt;This part wasn't as clear in the GitHub documentation, but I went over to the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html"&gt;AWS Documentation&lt;/a&gt; which gave me instructions on how to generate the thumbprint. You would need a copy of the &lt;code&gt;openssl&lt;/code&gt; CLI o be able to do this, but the quickest way is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the OpenSSL command to check against the provider URL to get the certificate.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl s_client -servername token.actions.githubusercontent.com -showcerts -connect token.actions.githubusercontent.com:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Grab the certificate shown in the output, you will see this starting with &lt;code&gt;-----BEGIN CERTIFICATE-----&lt;/code&gt;, then place this content into a file. For this demo, I will use &lt;code&gt;github_openid.crt&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the OpenSSL command again to generate the fingerprint from the file created above.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -in github_openid.crt -fingerprint -sha1 -noout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which should output the fingerprint. Strip away all the extra parts, and the &lt;code&gt;:&lt;/code&gt; between each of the pairs of hexadecimal characters, and you should end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6938fd4d98bab03faadb97b34396831e3780aea1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; You will need to make the letters lowercase, as Terraform is case sensitive for the variable we need to put this in, but AWS is not case sensitive, so it can sent Terraform into a bit of a loop. ⚠️&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the resource in Terraform
&lt;/h3&gt;

&lt;p&gt;With all of this, you can now start to put together the Terraform elements. We can use the Terraform resource for setting up the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider"&gt;OpenID Connect Provider&lt;/a&gt; in IAM. Below is a code example, using the information gathered from the documentation and the thumbprint generation, and all placed into a single resource object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_openid_connect_provider"&lt;/span&gt; &lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://token.actions.githubusercontent.com"&lt;/span&gt;
  &lt;span class="nx"&gt;client_id_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;thumbprint_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"6938fd4d98bab03faadb97b34396831e3780aea1"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seems simple enough, but this is just authorising GitHub to be a trusted source, and that identities from GitHub can be used to authenticate against AWS. The IAM role set up is where the main bulk of granting access is completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the IAM Role
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IAM Policy - Bucket Access
&lt;/h3&gt;

&lt;p&gt;Before access can be granted to the GitHub Actions pipeline, we will need to create a policy that defines the access that the role will have. There isn't much in the way of any difference to this part than creating any other policy however, for this blog and the &lt;a href="https://www.tokonatsu.org.uk"&gt;Tokonatsu&lt;/a&gt; website, we need some additional permissions outside of the simple &lt;code&gt;s3:PutObject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the example below, as my S3 bucket is also a resource in Terraform, you can see how I have pulled the bucket ARN for the resource from the outputs of the S3 bucket creation. It is normally best practice to reference variables and other outputs rather than using strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tfsec:ignore:aws-iam-no-policy-wildcards&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"website_colins_blog_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;

  &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
    &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# or "unique-bucket-name-for-site"&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*"&lt;/span&gt;   &lt;span class="c1"&gt;# or "unique-bucket-name-for-site/*"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"s3:GetBucketLocation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; As I use &lt;code&gt;tfsec&lt;/code&gt; to keep an eye on my Terraform, it attempts to look at the policy and look for anything that might be considered an issue. On the first line you can see the &lt;code&gt;tfsec:ignore:aws-iam-no-policy-wildcards&lt;/code&gt; comment, which means &lt;code&gt;tfsec&lt;/code&gt; will ignore that rule when it checks my Terraform. As we need to give this role access to do the listed actions on all objects in the bucket, a wildcard is easier. Hence the rule to stop the error from showing up. ⚠️&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Policy - Role Assumption
&lt;/h3&gt;

&lt;p&gt;As we will be using rule assumption, there needs to be an additional policy document created that will be added to the Role, that can tell it who can assume the role. You might have seen a similar version when using &lt;code&gt;sts:AssumeRole&lt;/code&gt; as an action, for principles that cover other accounts as an example. With the &lt;code&gt;sts:AssumeRoleWithWebIdentity&lt;/code&gt; what we are telling AWS tha the role assumption needs to happen if they have a "web identity", one of the external providers.&lt;/p&gt;

&lt;p&gt;The primary element to ensuring the right IdP is used when looking for which identity to grant access too, we need to reference the &lt;code&gt;ARN&lt;/code&gt; of the OpenID Connect Provider added earlier. As the Terraform resource we used above was identified with &lt;code&gt;aws_iam_openid_connect_provider.github&lt;/code&gt;, we can use one of its attributes to programmatically add the ARN to the principles. We will need to specify the &lt;code&gt;type&lt;/code&gt; in Terraform as "Federated" as well.&lt;/p&gt;

&lt;p&gt;The last two bits are &lt;code&gt;condition&lt;/code&gt; blocks and these are unique to the GitHub OpenID Connect set up. There is a little more detail on the &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws"&gt;GitHub Actions: OpenID Connect in AWS&lt;/a&gt; page. Using this page, I am adding two "StringLike" tests to the role, that looks for two specific variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;token.actions.githubusercontent.com:sub&lt;/code&gt; - Which is used to specify which repo's GitHub actions are allowed access.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;token.actions.githubusercontent.com:aud&lt;/code&gt; - That ensures that AWS's STS service is the one that is requesting the identity type, and no others.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;token.actions.githubusercontent.com:sub&lt;/code&gt; in my actual repo it says that any &lt;code&gt;repo&lt;/code&gt; in my personal space &lt;code&gt;mystcb&lt;/code&gt; called &lt;code&gt;blog&lt;/code&gt; using any branch, can access. While this is very open, my personal blog only really can be edited by myself, and I would want the role to also activate on any test branches as well. This is shown on my personal Terraform as &lt;code&gt;repo:mystcb/blog:*&lt;/code&gt;. The example I have below is more specific, and only allows access if the GitHub action is working from the &lt;code&gt;main&lt;/code&gt; branch. What I wanted to show here is a secure version, but also showing how you can add wildcards to the &lt;code&gt;value&lt;/code&gt; to cover more branches if need be.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"website_colins_blog_role_assumption"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;

  &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;

    &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringLike"&lt;/span&gt;
      &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"repo:mystcb/blog:ref:refs/heads/main"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringLike"&lt;/span&gt;
      &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;principals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Federated"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_openid_connect_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have our two policy documents as Terraform data objects, we can then pull them together to create the role&lt;/p&gt;

&lt;h3&gt;
  
  
  Creation of the IAM Role
&lt;/h3&gt;

&lt;p&gt;For us to create the role, we need to pull together all the bits we have created so far. This will mean doing a few special tricks with the data resources.&lt;/p&gt;

&lt;p&gt;Firstly we need to create an &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy"&gt;IAM Policy&lt;/a&gt; resource, that the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role"&gt;IAM Role&lt;/a&gt; resource can use to &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment"&gt;attach&lt;/a&gt; to the newly created role.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy"&gt;IAM Policy&lt;/a&gt; resource require 3 elements, the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt; , and the &lt;code&gt;policy&lt;/code&gt; in JSON format. While the name and path can be as custom as you need, the policy in JSON format is what might trip a few people up. The &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document"&gt;IAM Policy Document&lt;/a&gt; data object has just one Attribute output tha can be referenced here: &lt;code&gt;json&lt;/code&gt;. This conversion means we can quickly add the three together to make the IAM Policy object in AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"website_colins_blog"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"access_to_website_colins_blog_s3"&lt;/span&gt;    &lt;span class="c1"&gt;# This is my example name!&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;                                   &lt;span class="c1"&gt;# Root path for ease&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to create the role itself which has two key elements, the &lt;code&gt;name&lt;/code&gt; and the &lt;code&gt;assume_role_policy&lt;/code&gt;. The name is as you want this to be however, the &lt;code&gt;assume_role_policy&lt;/code&gt; will be needed to let AWS IAM know what can assume this role. In our case, it is the JSON output from our second IAM Policy Document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"website_colins_blog_github_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"access_to_website_colins_blog_s3_role"&lt;/span&gt;      &lt;span class="c1"&gt;# This is my example name!&lt;/span&gt;
  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog_role_assumption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now we have role, and we can assume it it - well not exactly, one last step. With IAM, you can attach multiple policies to a single role, which if you created in line with the &lt;code&gt;aws_iam_role&lt;/code&gt; resource it can make it a little more complicated, and very long. The AWS provider allows us two methods to manage the role's policies. One is through &lt;code&gt;managed_policy_arns&lt;/code&gt;/&lt;code&gt;inline_policy&lt;/code&gt; or &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment"&gt;aws_iam_policy_attachment&lt;/a&gt;. The former two work very much the same, but takes exclusive authority over the state of the IAM Role itself. This means if you attach policies using the latter resource object, you will find Terraform getting stuck in a cycle. For this example, I am using the policy attachment resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"website_colins_blog"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog_github_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This block is where all the references from before make this easier. This is where the role resource object, and the policy resource object come together to create the role. Now, AWS is aware of GitHub, its OpenID Connect provider, and we have given a specific repo's GitHub actions that run on the main branch access to assume a role in AWS, which give it access to AWS S3. The role assumption will use temporary credentials for each of the runs!&lt;/p&gt;

&lt;p&gt;One last bit, this part will enable you to get the ARN for the role, which will be required for the configuration of GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"website_colins_blog_role_arn"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The role ARN for the Website: Colin's Blog Role"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_colins_blog_github_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Very simple, it just outputs the ARN for the role which will need to be copied to GitHub. For the rest of the blog, I am going to be using an example role in my examples. This will not work, so make sure you are getting your role that matches your account. The ARN we will be using will be&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;arn:aws:iam::12326264843:role/access_to_website_colins_blog_s3_role&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7JhcIDO7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsRoleCreated.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7JhcIDO7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsRoleCreated.png" alt="One IAM Role created, with federation to GitHub" width="880" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions/Workflow Updates
&lt;/h2&gt;

&lt;p&gt;To enable this, we need to update the workflow yaml file, and this is probably the easiest bit of the whole post!&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the Role ARN as a secret
&lt;/h3&gt;

&lt;p&gt;This is where we need to move back to GitHub and grab the ARN from above, and add this as an add the URL to the Repository Secrets. You should be able to find your version at &lt;code&gt;https://github.com/&amp;lt;yourname/org&amp;gt;/&amp;lt;yourrepo&amp;gt;/settings/secrets/actions&lt;/code&gt;. This is under &lt;code&gt;Settings -&amp;gt; Secrets and variables -&amp;gt; Actions&lt;/code&gt; and click &lt;code&gt;New Secret&lt;/code&gt;. Enter in a name for the secret, and its value which I have put in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;code&gt;AWS_ROLE_ARN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: &lt;code&gt;arn:aws:iam::12326264843:role/access_to_website_colins_blog_s3_role&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bHhqbYBx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsAddNewSecret.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bHhqbYBx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsAddNewSecret.png" alt="Entering in the new secret with the role ARN" width="880" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have added that, make sure to remove the two existing Repository Secrets, in my case I called them&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AWS CLI will always use the keys over role assumption in it's priority so always best to remove them. With the two older secrets removed you should now have just the &lt;code&gt;AWS_ROLE_ARN&lt;/code&gt; and &lt;code&gt;AWS_DEFAULT_REGION&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EVaPKFwe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsListSecretsNew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EVaPKFwe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsListSecretsNew.png" alt="Only the final two secrets left" width="880" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the Workflow YAML file
&lt;/h3&gt;

&lt;p&gt;For GitHub actions to be able to assume the role, there are two changes that need to be made to the workflow yaml file. The first one, will be the need to enable the workflow to interact with GitHub's OIDC Token endpoint. Part of the assumption process will require us to identify as a web identity from GitHub to have AWS know who we are. As such you will need to add additional &lt;code&gt;permissions&lt;/code&gt; to the job. Specifically the following &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id-token&lt;/code&gt; to &lt;code&gt;write&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contents&lt;/code&gt; to &lt;code&gt;read&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hugo_build_and_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later on in the pipeline, where we configured the AWS credentials before, you will need to remove the older secret variables, and put the new secrets in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-credentials-configure&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
    &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEFAULT_REGION }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see this whole file in context, I would recommend having a look at &lt;a href="https://github.com/mystcb/blog/blob/main/.github/workflows/build-and-deploy-site.yaml"&gt;this blog's workflow&lt;/a&gt; on GitHub!&lt;/p&gt;

&lt;p&gt;All you need to do now, is run the GitHub Actions workflow, and make sure it works! With my blog workflow, it was pretty much a drop in replacement for the IAM credentials. There were a &lt;a href="https://github.com/mystcb/blog/actions"&gt;few minor issues&lt;/a&gt; with my workflow, but nothing that following this wouldn't have resolved fo me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Some minor issues
&lt;/h2&gt;

&lt;p&gt;As you can see, it wasn't exactly first time running for me! It did take a while, and also I had placed a &lt;code&gt;--acl public-read&lt;/code&gt; as part of the &lt;code&gt;aws s3 sync&lt;/code&gt; command, which the new bucket I created had been set to block public ACLs!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CwjWWhBL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsActionsList.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CwjWWhBL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsActionsList.png" alt="Just a few mistakes!" width="880" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was one other issue, and that was with the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; that is used. In normal operation, without the added permissions, this token worked fine with an additional Marketplace action called &lt;a href="https://github.com/marketplace/actions/github-deployments"&gt;GitHub Deployments&lt;/a&gt;. However, changing this over to allow the OpenID Connect feature, it meant that the token mysteriously stopped working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching to use a Fine-grained PAT
&lt;/h3&gt;

&lt;p&gt;On the 18th of October 2022, GitHub offered up a new service called &lt;a href="https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/"&gt;Fine-grained personal access tokens&lt;/a&gt;. The idea is that rather than creating a very open Personal Access Token (PAT), you could create a token that was very limited in it's reach. It is still in beta as I write this blog (28th Feb 2023). &lt;/p&gt;

&lt;p&gt;Using this beta feature, I was able to create a new token, limiting it to specifically the blog repo, and specific permissions. The screenshot below shows the details about the new PAT. (I am aware I could probably reduce the permissions a little more!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vgVfaBET--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsFineGrainedPAT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vgVfaBET--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.colinbarker.me.uk/img/blog/2023/02/GitHubActionsFineGrainedPAT.png" alt="A new Fine-grained Personal Access Token (beta)" width="880" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, I added a new respository secret called &lt;code&gt;REPO_TOKEN&lt;/code&gt; with the value of the newly generated token, and then updated the part in the workflow that needed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set Deploy Status&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-status-start&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bobheadxi/deployments@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.REPO_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prod&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.ref }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Round up
&lt;/h2&gt;

&lt;p&gt;Hopefully what I have shown you in this post is how to move away from IAM Credentials, and use the OpenID Connect features of both AWS and GitHub to enable role based assumption to gain access to an S3 bucket that stores, in my case, a static website. &lt;/p&gt;

&lt;p&gt;If you do have any questions, comments, please let me know!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>github</category>
      <category>terraform</category>
      <category>openid</category>
    </item>
    <item>
      <title>Enabling IPv6 on AWS using Terraform (Part 1)</title>
      <dc:creator>Colin Barker</dc:creator>
      <pubDate>Thu, 16 Feb 2023 14:49:21 +0000</pubDate>
      <link>https://forem.com/mystcb/enabling-ipv6-on-aws-using-terraform-part-1-1i46</link>
      <guid>https://forem.com/mystcb/enabling-ipv6-on-aws-using-terraform-part-1-1i46</guid>
      <description>&lt;p&gt;Header photo by &lt;a href="https://unsplash.com/@nasa?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;NASA&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/Q1p7bh3SHj8?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is IPv6?
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://en.wikipedia.org/wiki/IPv6" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;, Internet Protocol version 6 (IPv6) was introduced in December 1995 (just over 25 years ago!), based on the original IPv4 protocol that we all know and love today. The &lt;a href="https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force" rel="noopener noreferrer"&gt;Internet Engineering Task Force (IETF)&lt;/a&gt; developed this new protocol to help deal with the (at the time) anticipated exhaustion of the IPv4 address range. This process seemed like it could be simple enough, create a new system to replace and older system and enable the expansion of the Internet to meet modern day standards.&lt;/p&gt;

&lt;p&gt;This is where the problem lie. Adopting IPv6 wasn't as simple as just replacing IPv4. The two protocols are different enough that on top of physical hardware changes (older devices unable to support IPv6), it also meant a different way of thinking when it came to working with networking both on the Internet, and within (Intranets, local networks, home networks). One of the biggest issues that faced a lot of the world, is that ISP adoption rate was surprisingly low.&lt;/p&gt;

&lt;p&gt;However, as the &lt;a href="https://inetcore.com/project/ipv4ec/en-us/index.html" rel="noopener noreferrer"&gt;IPv4 Exhaustion&lt;/a&gt; happened as early as 2011, providers have started very quickly adopting the new standard.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, what is IPv6?
&lt;/h3&gt;

&lt;p&gt;I would recommend reading the &lt;a href="https://en.wikipedia.org/wiki/IPv6" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt; for more details, as much of what I would write here, would essentially be copied just from that page! In summary though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IPv6 uses 128-bit addresses over the IPv4 32-bit addresses, allowing approximately 3.4 x 10^38 addresses, over IPv4's 4,294,967,296 (2 x 10^32)&lt;/li&gt;
&lt;li&gt;Addresses are in 8 groups of hexadecimal digits, separated by colons. For example: &lt;code&gt;fd42:cb::123&lt;/code&gt; in short hand. (which would expand to be &lt;code&gt;fd42:00cb:0000:0000:0000:0000:0000:0123&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Route Aggregation is built into the addressing scheme allowing for the expansion of global route tables with minimal space used.&lt;/li&gt;
&lt;li&gt;Steve Leibson (in a now lost article) one said "[If] we could assign an IPv6 address to every atom on the surface of the earth, [we would] still have enough addresses left to do another 100+ Earths!"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;: I mentioned short hand above, and this comes with a lot of caveats but the primary rule is, you can drop any zero in an IPv6 address, and it is assumed, as long as it is before the hexadecimal character. For example &lt;code&gt;00cb:0000:0001&lt;/code&gt; can be shortened to &lt;code&gt;cb::1&lt;/code&gt;. However, you can only have ONE &lt;code&gt;::&lt;/code&gt; in each address (otherwise how does it know where to shorten it!), so for example &lt;code&gt;00cb:0000:0100:0000:0001&lt;/code&gt; must be shortened to &lt;code&gt;cb:0:100::1&lt;/code&gt;. I'll go into this in more detail in a future post! ⚠️&lt;/p&gt;

&lt;p&gt;This is why it is important to start embracing IPv6, we have a lot more space to make lives easier for the world, and the only way we can ensure the continued adoption of the protocol is to enable this everywhere.&lt;/p&gt;

&lt;p&gt;In this post, I will go through how you can enable, using Terraform, IPv6 on your existing AWS Cloud Networks. When planning IPv6, there is a lot to consider, and there are some architectural changes will need to be considered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting the scene - an existing AWS Cloud Network
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql8s3mphwr2mx77ghyec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql8s3mphwr2mx77ghyec.png" alt="Basic AWS VPC with Networking" width="483" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This should be a very familiar layout to most people, an VPC in AWS with some very basic networking setup. In the diagram above, we have a VPC, with Public and Private Subnets in two availability zones. We have an Internet Gateway for the public subnets, and a NAT Gateway in each Availability Zone for the Private Subnets to talk to the internet. I have placed some sample IP addressing in, just for reference as part of this post.&lt;/p&gt;

&lt;p&gt;If you wish to deploy this yourself, I have place some sample code on my &lt;a href="https://github.com/mystcb" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; that you can use. (&lt;a href="https://github.com/mystcb/ipv6-on-aws/tree/main/01-sample-vpc" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/tree/main/01-sample-vpc&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Throughout this post, you will see me mention the cost of running this using an estimate. I have been using for a while, a tool called &lt;code&gt;infracost&lt;/code&gt; which is an open source (with subscription based additions) cost estimator tool - &lt;a href="https://www.infracost.io/" rel="noopener noreferrer"&gt;https://www.infracost.io/&lt;/a&gt;. For this demonstration, using the sample code listed above, it would cost an estimated $76.65/month - so if you don't want rack up a bill, only deploy when you want to test, and use Terraform to destroy the services when you are done.&lt;/p&gt;

&lt;p&gt;As an example, here it the cost estimate for this deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# infracost breakdown --path=.&lt;/span&gt;

Evaluating Terraform directory at &lt;span class="nb"&gt;.&lt;/span&gt;
  ✔ Downloading Terraform modules
  ✔ Evaluating Terraform directory
  ✔ Retrieving cloud prices to calculate costs

Project: mystcb/ipv6-on-aws/01-sample-vpc

 Name                                     Monthly Qty  Unit            Monthly Cost

 aws_eip.natgwip[1]
 └─ IP address &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;unused&lt;span class="o"&gt;)&lt;/span&gt;                        730  hours                  &lt;span class="nv"&gt;$3&lt;/span&gt;.65

 aws_nat_gateway.sample_natgw_subnet_a
 ├─ NAT gateway                                   730  hours                 &lt;span class="nv"&gt;$36&lt;/span&gt;.50
 └─ Data processed                      Monthly cost depends on usage: &lt;span class="nv"&gt;$0&lt;/span&gt;.05 per GB

 aws_nat_gateway.sample_natgw_subnet_b
 ├─ NAT gateway                                   730  hours                 &lt;span class="nv"&gt;$36&lt;/span&gt;.50
 └─ Data processed                      Monthly cost depends on usage: &lt;span class="nv"&gt;$0&lt;/span&gt;.05 per GB

 OVERALL TOTAL                                                               &lt;span class="nv"&gt;$76&lt;/span&gt;.65
──────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, all costs are estimates! With the cloud, its pay as you use, utility based so the costs will be what you use. The above is just an estimate, so keep that in mind!&lt;/p&gt;

&lt;h2&gt;
  
  
  IPv6 and AWS
&lt;/h2&gt;

&lt;p&gt;As mentioned before, there are some concepts that have to be considered when designing for IPv6. One specific concept for networking can seem a little confusing at first, but with the right security in place, will ensure that no accidental access to the service can happen.&lt;/p&gt;

&lt;p&gt;Within AWS, IPv6 addresses are global unicast addresses. A unicast address is an address that identifies a unique object or node on a network. While the premise of a unicast address is not new (as it was the same in IPv6), with the exhaustion of IPv4 addresses, new methods to give unique addresses to multiple nodes was devised. &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation" rel="noopener noreferrer"&gt;Network Address Translation (NAT)&lt;/a&gt; was one such method for doing this. This allowed the mapping of a single unicast IP address to be used by multiple nodes, by routing traffic through the NAT node and allowing it to re-write the headers to make it seem like it had come from the unique address.&lt;/p&gt;

&lt;p&gt;NAT is a wide subject, and I am sure I will write more about it some day, but a real world example that you see in most places is your home network. You have multiple private nodes with access to the internet, typically using a single public unicast address.&lt;/p&gt;

&lt;p&gt;So how does this relate to IPv6 and AWS? Remember earlier, I mentioned that there are so many addresses available in the IPv6 ecosystem that you could give a unique address to every atom on the planet? Well, we can do just that, but to the nodes that require addresses. This is what AWS does, each IPv6 address you assign to nodes in AWS, are global unicast addresses, unique to each node.&lt;/p&gt;

&lt;p&gt;This means a "private" IPv6 Subnet does sound like it might be complicated to set up, but actually it isn't as bad as you think! However, to start the process off, we will start with adding a IPv6 to the Public Subnets, to set the ground work, and go from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling IPv6 on your VPC
&lt;/h2&gt;

&lt;p&gt;Regardless of what you need IPv6 for, you need to enable this on your VPC. Just like your IPv4 CIDR that you assign to the VPC, you will need to assign a IPv6 CIDR to your VPC.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Difference&lt;/strong&gt;: Private IPv6 addresses do exist, but you can't assign them to AWS VPCs. You must use public CIDR ranges ⚠️&lt;/p&gt;

&lt;p&gt;In a way, the above does make sense - every device is globally unique, so why would you need to make a private address. Personally, I use internal private addresses to make it easier to remember when connecting to servers, but I am very much an old-school person here, and you should be using name resolution to connect to instances!&lt;/p&gt;

&lt;p&gt;Therefore, when you go to assign an IPv6 CIDR range to your VPC, you have one of three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/ipam/what-it-is-ipam.html" rel="noopener noreferrer"&gt;IPAM-allocated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Amazon-provided&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-byoip.html" rel="noopener noreferrer"&gt;An IPv6 CIDR owned by you (BYOIP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simplicity, I will be using the Amazon-provided CIDR ranges. In the future, I will go over the IPAM-allocated and BYOIP options, but for now these are just additional ways to get an IPv6 CIDR on your VPC.&lt;/p&gt;

&lt;p&gt;AWS have access to a fairly large range of IPv6 addresses, this means they can allocate you a unique set of addresses just for you.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Terminology&lt;/strong&gt;: A Network Boarder Group is the name (chosen by AWS) that defines a unique set of Availability Zones or Local Zones where AWS advertises IP addresses ⚠️&lt;/p&gt;

&lt;p&gt;When requesting an IPv6 CIDR from AWS, they will need you to select a Network Border Group. This is to ensure that the IPv6 addresses you are receiving can be routed successfully to your VPC. Back to the IPv6 description above, to make routing simpler in the IPv6 world, AWS will route specific ranges to specific groups, and therefore you have to select the right group. Thankfully, as the groups are quite large, for the majority of regions - there is only a single Network Border Group, and Terraform will select this automatically!&lt;/p&gt;

&lt;p&gt;Lets start with the &lt;code&gt;vpv.tf&lt;/code&gt; that exists in the sample code (&lt;a href="https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc.tf" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc.tf&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creation of the sample VPC for the region&lt;/span&gt;
&lt;span class="c1"&gt;#tfsec:ignore:aws-ec2-require-vpc-flow-logs-for-all-vpcs&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"sample_vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sample-VPC"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty simple, probably a little too simple! But keep in mind that this is just a sample for now!&lt;/p&gt;

&lt;p&gt;Terraform has two resource parameters that will be used to set and assign the IPv6 CIDR to the VPC. &lt;code&gt;assign_generated_ipv6_cidr_block&lt;/code&gt; and &lt;code&gt;ipv6_cidr_block_network_border_group&lt;/code&gt;. The border group is used a lot more with Local Zones, so we don't need to worry about this at the moment, but do keep this in mind for more complex deployments.&lt;/p&gt;

&lt;p&gt;Just setting the &lt;code&gt;assign_generated_ipv6_cidr_block&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; is enough for AWS to assign the VPC a CIDR range. &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc#assign_generated_ipv6_cidr_block" rel="noopener noreferrer"&gt;&lt;em&gt;Terraform documentation&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your file should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creation of the sample VPC for the region&lt;/span&gt;
&lt;span class="c1"&gt;#tfsec:ignore:aws-ec2-require-vpc-flow-logs-for-all-vpcs&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"sample_vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;
  &lt;span class="nx"&gt;assign_generated_ipv6_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sample-VPC"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, your &lt;code&gt;terraform plan&lt;/code&gt; should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_vpc.sample_vpc will be updated in-place
  ~ resource "aws_vpc" "sample_vpc" {
      ~ assign_generated_ipv6_cidr_block     = false -&amp;gt; true
        id                                   = "vpc-0123456789abcdef0"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
        tags                                 = {
            "Name" = "Sample-VPC"
        }
        # (16 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see with the plan, this will grab some additional details to add to the resource object as attributes to reference later on. This will be key to make your terraform portable, and not hard coded!&lt;/p&gt;

&lt;p&gt;Once applied, this will then allocate the IPv6 CIDR to the VPC, and you should then see the following!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttth44ogc6iuloddtz3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttth44ogc6iuloddtz3q.png" alt="Console view of the CIDR allocations on a VPC showing the IPv6 allocation" width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There we go, we have hit the first step! IPv6 is now enabled on the VPC! As you can see, we have received a &lt;code&gt;/56&lt;/code&gt; block of IP's. That gives you the room to have a total of &lt;code&gt;4,722,366,482,869,645,500,000 hosts&lt;/code&gt; in your network. I don't think we will be running out any time soon!&lt;/p&gt;

&lt;p&gt;Next step, lets assign to each of the subnets their own CIDR, so that resources in the subnets can assign their own IPv6 address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding IPv6 CIDRs to the public subnets
&lt;/h2&gt;

&lt;p&gt;Just like IPv4 CIDR's, you can break down the IPv6 range you have into smaller ranges that are all routed internally using the AWS's VPC networking. With a manual set up you would normally do something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IPv4 VPC CIDR&lt;/strong&gt;: 192.168.0.0/20&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Subnet in AZ1&lt;/strong&gt;: 192.168.10.0/24&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Subnet in AZ2&lt;/strong&gt;: 192.168.11.0/24&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is shown in the sample VPC we are using in this post. Simple enough right? Breaking down the CIDR into smaller subnets, and then assigning them to the correct location. It is very much the same with IPv6, but the ranges are just that much larger, that sometimes its best to use an automated method for doing this. What everyone should be doing, is the automatic generation of the ranges for IPv4 as well, which is available in the example!&lt;/p&gt;

&lt;p&gt;To do this, we can use a terraform function called &lt;code&gt;cidrsubnet&lt;/code&gt; (&lt;a href="https://developer.hashicorp.com/terraform/language/functions/cidrsubnet" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/language/functions/cidrsubnet&lt;/a&gt;). This function can calculate the subnet addresses within a given IP network block or CIDR, and then be used as a value for a variable in the &lt;code&gt;aws_subnet&lt;/code&gt; resource block.&lt;/p&gt;

&lt;p&gt;This function takes a bit of getting used to, but here is my trick for understanding how it works!&lt;/p&gt;

&lt;p&gt;The function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cidrsubnet(prefix, newbits, netnum)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For further details, feel free to read the documentation above, but for our sample we will use the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prefix&lt;/code&gt; the CIDR range. Available as an attribute as this is generated by AWS. &lt;code&gt;aws_vpc.sample_vpc.ipv6_cidr_block&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;newbits&lt;/code&gt; this is the number of additional bits you need to add to the CIDR prefix, to break the network down. In our case we will use &lt;code&gt;8&lt;/code&gt; as it is a round number, but you will need to size this to your specifications.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;netnum&lt;/code&gt; this is the network number assigned to the broken down blocks that you will select for this subnet. It can't be more than the &lt;code&gt;newbits&lt;/code&gt; and you can't use the same &lt;code&gt;netnum&lt;/code&gt; on multiple subnets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trick I have used is as follows. &lt;code&gt;newbits&lt;/code&gt; is calculated as the difference between the CIDR's prefix &lt;code&gt;/56&lt;/code&gt; and the network size you need. So in our case I would like to make each subnet a &lt;code&gt;/64&lt;/code&gt; in size. The difference between the two (&lt;code&gt;64 - 56 = 8&lt;/code&gt;) means the bit difference is &lt;code&gt;8&lt;/code&gt;. For the moment, I will leave this here, but I will create an article in the future that explains how this works, and why its the number of bits!&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;netnum&lt;/code&gt; I tend to visualise it in the diagram - I wanted 4 subnets in my sample, (2 public, 2 private), so I am able to reference the networks created above by their counter, with the first network being &lt;code&gt;0&lt;/code&gt;, with the last network being &lt;code&gt;3&lt;/code&gt; (as your counter started at 0).&lt;br&gt;
So for our networks, we can enter the values and get the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Subnet 1 - x:x::0:0/64&lt;/span&gt;
&lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Subnet 2 - x:x::1:0/64&lt;/span&gt;
&lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Subnet 3 - x:x::2:0/64&lt;/span&gt;
&lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Subnet 4 - x:x::3:0/64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is the parameter for the &lt;code&gt;aws_subnet&lt;/code&gt; resource block, which is &lt;code&gt;ipv6_cidr_block&lt;/code&gt;. Essentially the same as the &lt;code&gt;cidr_block&lt;/code&gt; parameter, but for IPv6!&lt;/p&gt;

&lt;p&gt;So for each of our public subnets, lets add this in. In our sample file &lt;code&gt;vpc_subnets.tf&lt;/code&gt; (&lt;a href="https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_subnets.tf" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_subnets.tf&lt;/a&gt;), we have two public subnets, &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. So lets make the change. Below is the example with the new parameters added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Public Subnet A&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public_a"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public-Subnet-A"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Public Subnet B&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public_b"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public-Subnet-B"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, we run our &lt;code&gt;terraform plan&lt;/code&gt; and we should get an output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-place&lt;/span&gt;

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_subnet.public_a will be updated in-place&lt;/span&gt;
  ~ resource &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public_a"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;                                             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"subnet-0123456789abcdefg"&lt;/span&gt;
      + ipv6_cidr_block                                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"xxxx:yyyy:zzzz:nnn1::/64"&lt;/span&gt;
        tags                                           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public-Subnet-A"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;# (15 unchanged attributes hidden)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# aws_subnet.public_b will be updated in-place&lt;/span&gt;
  ~ resource &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public_b"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;                                             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"subnet-gfedcba9876543210"&lt;/span&gt;
      + ipv6_cidr_block                                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"xxxx:yyyy:zzzz:nnn2::/64"&lt;/span&gt;
        tags                                           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public-Subnet-B"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;# (15 unchanged attributes hidden)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 0 to add, 2 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan shows the addition of the new CIDR block to each subnet, noting the network number changing slightly to accommodate the size we requested. Once applied, we should see these details in the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8wgqn3sflbw5rsm029s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8wgqn3sflbw5rsm029s.png" alt="IPv6 CIDR on one of the two sample subnets" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect, now let's launch an EC2 instance and then try and connect to something using the IPv6 address!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qkg3yibhua254v2huy7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qkg3yibhua254v2huy7.png" alt="The EC2 instance can't connect to an IPv6 address" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, almost there - we re missing the routing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding IPv6 Routing to the public subnets
&lt;/h2&gt;

&lt;p&gt;Having a look at our existing route tables, we can see that AWS added in the route for the VPC IPv6 CIDR to target the local VPC in the route table, which means it can find resources in the VPC that have the IPv6 address that is within the CIDR. Great for local traffic, but we need to be able to access the outside world using IPv6!&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; While traffic can still route with IPv4, we need to enable routing for IPv6, otherwise any traffic inbound to the server won't be able to send traffic back. ⚠️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nsiizzx0puyj2hrz3zv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nsiizzx0puyj2hrz3zv.png" alt="The sample Public-Route-Table doesn't have any IPv6 route to the internet" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also see this in the sample code too, in the &lt;code&gt;vpc_public_routing.tf&lt;/code&gt; file (&lt;a href="https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_public_routing.tf" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_public_routing.tf&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Primary Sample Route Table (Public)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"public_rt"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public-Route-Table"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Route entry specifically to allow access to the Internet Gateway&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"public2igw"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;destination_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;gateway_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_igw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we can see the &lt;code&gt;Public-Route-Table&lt;/code&gt; resources, but it only shows traffic to the Internet Gateway (IGW) for IPv4 traffic. We will need to add a route to allow IPv6 traffic to hit the IGW.&lt;/p&gt;

&lt;p&gt;We can do this by creating a new &lt;code&gt;aws_route&lt;/code&gt; resource, but we need to use the &lt;code&gt;destination_ipv6_cidr_block&lt;/code&gt; parameter instead.&lt;/p&gt;

&lt;p&gt;For the destination though, with IPv4 you could use the block &lt;code&gt;0.0.0.0/0&lt;/code&gt; to mean "all traffic". If we were to type the IPv6 version out in full, it would look like &lt;code&gt;0000:0000:0000:0000:0000:0000:0000:0000/0&lt;/code&gt;. Bit of a pain! Thankfully we can apply the short hand rule mentioned at the start, remove all the 0's, shrink down, and you get quite simply &lt;code&gt;::/0&lt;/code&gt;, which suddenly seems much shorter than the IPv4 version! With this we can add this as the destination IPv6 block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"public2igw_ipv6"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;destination_ipv6_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"::/0"&lt;/span&gt;
  &lt;span class="nx"&gt;gateway_id&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_igw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, lets run the &lt;code&gt;terraform plan&lt;/code&gt; and we should get something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_route.public2igw_ipv6 will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"public2igw_ipv6"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + destination_ipv6_cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"::/0"&lt;/span&gt;
      + gateway_id                  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"igw-0123456789abcdefg"&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + instance_id                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + instance_owner_id           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + network_interface_id        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + origin                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + route_table_id              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rtb-0123456789abcdefg"&lt;/span&gt;
      + state                       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 1 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And once applied, we should be able to see the new route in the routing table!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6y2utpr2vy4cq70ilqil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6y2utpr2vy4cq70ilqil.png" alt="::/0 has been added to the route table, pointing to the Internet Gateway (IGW)" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jumping back on our EC2 instance, and we get a connection!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2uobnmbbxbqortz71p6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2uobnmbbxbqortz71p6.png" alt="We are now able to connect using IPv6 to the outside world!" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Attention:&lt;/strong&gt; Inbound traffic to an EC2 instance, for example, will still be protected by its Security Group. The rules in a security group are specific to the IP protocol, so an allow for an IPv4 inbound rule, will only allow that. Make sure that you add any additional IPv6 rules to the security group to permit inbound access to resources in the Public Subnet ⚠️&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding IPv6 outbound routing to the private subnets
&lt;/h2&gt;

&lt;p&gt;We are getting to the last part of this post about enabling IPv6 on AWS, and we do need to cover the private subnets to complete the sample network configuration. This part will come with a few warnings, but if you are keeping in line with the &lt;a href="https://aws.amazon.com/architecture/well-architected/" rel="noopener noreferrer"&gt;AWS Well-Architected Framework&lt;/a&gt;, this will not be an issue at all!&lt;/p&gt;

&lt;p&gt;Thinking back to what we mentioned before, AWS will use global unicast addresses for resources and services in AWS. Meaning, that you do not have a "private" CIDR for your private subnets. As you know, in IPv4 there is the &lt;a href="https://www.rfc-editor.org/rfc/rfc1918" rel="noopener noreferrer"&gt;RFP1918&lt;/a&gt; that defines a number of IP blocks, specifically for "internal connectivity". These IP ranges are not routable on the public internet. This allowed the expansion of devices within a private network, without using up the public internet space. As IPv6 can assign every device on the planet, it makes it easier for us to ensure that the address assigned to a node is unique.&lt;/p&gt;

&lt;p&gt;Next we have to look at the definition of what AWS considers a "public" subnet and a "private" subnet. A "public" subnet, is considered such, whereby the route table for the subnet points its outbound internet traffic directly to an Internet Gateway (IGW), and the IGW allows external traffic to route to the subnet. For a "private" subnet, there is no IGW for it to connect to, and you would use a service such as a NAT Gateway (NGW) to connect through to the internet, and as the NAT Gateway doesn't allow traffic inbound to the network, and the IP address of the node will be a non-internet rotatable IP address, traffic can't reach the service in the VPC.&lt;/p&gt;

&lt;p&gt;This definition sill applies to Private IPv6 subnets, and this is why AWS created the &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html" rel="noopener noreferrer"&gt;&lt;em&gt;IPv6 Egress-Only Internet Gateway&lt;/em&gt;&lt;/a&gt;. Much like the IPv4 Internet Gateway counterpart, the IPv6 Egress-Only Internet Gateway is a horizontally scaled, redundant, and highly available VPC component that allows outbound communication for IPv6 traffic to the internet. As this new IPv6 egress-only internet gateway only permits traffic outbound, and not inbound, this will make a subnet private, as traffic can not reach it.&lt;/p&gt;

&lt;p&gt;As this is a VPC component, and not a service like the NAT Gateway, you technically only need to deploy one, like the Internet Gateway, and point outbound traffic to the egress-only internet gateway, and it will scale as needed.&lt;/p&gt;

&lt;p&gt;With this in mind, lets start by adding in the Egress-only Internet Gateway. This is created by the terraform resource &lt;code&gt;aws_egress_only_internet_gateway&lt;/code&gt; (&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/egress_only_internet_gateway" rel="noopener noreferrer"&gt;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/egress_only_internet_gateway&lt;/a&gt;). This is very similar to the normal Internet Gateway, and even the set up is the same.&lt;/p&gt;

&lt;p&gt;Within the &lt;code&gt;vpc_private_routing.tf&lt;/code&gt; file (&lt;a href="https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_private_routing.tf" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_private_routing.tf&lt;/a&gt;) you will need to add the following resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# IPv6 Egress-only Internet Gateway&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_egress_only_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"sample_ipv6_egress_igw"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sample-VPC-IPv6-Egress-Only-IGW"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, running &lt;code&gt;terraform plan&lt;/code&gt; will output something similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_egress_only_internet_gateway.sample_ipv6_egress_igw will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_egress_only_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"sample_ipv6_egress_igw"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + tags     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          + &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sample-VPC-IPv6-Egress-Only-IGW"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      + tags_all &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          + &lt;span class="s2"&gt;"Environment"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sandbox"&lt;/span&gt;
          + &lt;span class="s2"&gt;"Name"&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sample-VPC-IPv6-Egress-Only-IGW"&lt;/span&gt;
          + &lt;span class="s2"&gt;"Source"&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terrform"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      + vpc_id   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vpc-0123456789abcdefg"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 1 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, I would like to point out - that all the changes we have made so far, have not increased the cost of running! Much like the Internet Gateway, the only cost you have is the outbound traffic. As the Egress-Only Internet Gateway is the same, there is no cost for running this in a VPC other than the outbound traffic you intend to push through it. Unlike the NAT Gateways, which will cost you about $36.50/month, and then for best practice, you would need one in each availability zone, which then adds up. It does make IPv6 only networking in AWS far cheaper than IPv4!&lt;br&gt;
Apply the changes, and the new Egress-only Internet Gateway will be created.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding IPv6 CIDRs to the private subnets
&lt;/h2&gt;

&lt;p&gt;The next step, is that we need to add the CIDR blocks to the private subnets. This is pretty much identical to the public subnet addition, in fact, it is identical. This is due to there being no difference between public blocks and private blocks within the VPC IPv6 range. So for speed, we just repeat the same.&lt;/p&gt;

&lt;p&gt;Head back to our sample file &lt;code&gt;vpc_subnets.tf&lt;/code&gt; (&lt;a href="https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_subnets.tf" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_subnets.tf&lt;/a&gt;), and look for the two "private" subnets. Add in the &lt;code&gt;ipv6_cidr_block&lt;/code&gt; for each of them, based off the next two blocks calculated earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Private Subnet A&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private_a"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Private-Subnet-A"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Private Subnet B&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private_b"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Private-Subnet-B"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the &lt;code&gt;terraform plan&lt;/code&gt; and apply the changes to assign the blocks to the subnets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding IPv6 Routing to the private subnets
&lt;/h2&gt;

&lt;p&gt;This is where the limitations with the IPv4 NAT Gateway makes the changes for a private subnet add additional operational overhead to the work needing to be completed. In our sample code, we created two route tables for the private subnets, one for each availability zone, so that traffic within the subnet routes through the NAT Gateway for that availability zone.&lt;/p&gt;

&lt;p&gt;This means, rather than like the public subnet, where we need to add just a single route in terraform. We have to add two and attach them to both route tables.&lt;/p&gt;

&lt;p&gt;If you were creating an IPv6 only VPC, then you could reduce the work and have a single route table that works for both availability zones! However, we will look at this at a later date.&lt;/p&gt;

&lt;p&gt;This time, we need to head to the &lt;code&gt;vpc_private_routing.tf&lt;/code&gt; file (&lt;a href="https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_private_routing.tf" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/blob/main/01-sample-vpc/vpc_private_routing.tf&lt;/a&gt;) again, and we need to add in the two new routes.&lt;/p&gt;

&lt;p&gt;If you look at the file, you will see that for the IPv4 NAT Gateway, there is a specific parameter that you use to tell terraform to issue the right API command to AWS to add the route, which you can see here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Route for Subnet A to access the NAT Gateway&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"private2natgwa"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_rt_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;destination_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;nat_gateway_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_nat_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_natgw_subnet_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;nat_gateway_id&lt;/code&gt; will be specifically used to pointing to the ID of the NAT Gateway within the availability zone. Much like in the public subnet you would use &lt;code&gt;gateway_id&lt;/code&gt; for the Internet Gateway, you can use the &lt;code&gt;egress_only_gateway_id&lt;/code&gt; for the IPv6 traffic.&lt;/p&gt;

&lt;p&gt;Therefore, we will need to add 2 new blocks to the terraform file, to add in the route &lt;code&gt;::/0&lt;/code&gt; to point to the Egress-only Internet Gateway.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Attention:&lt;/strong&gt; Remember, for IPv6 routes, you will need to use the &lt;code&gt;destination_ipv6_cidr_block&lt;/code&gt; as part of the route table resource ⚠️&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Route for Subnet A to access the Egress-Only Internet Gateway&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"private2ipv6egressigwa"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_rt_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;destination_ipv6_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"::/0"&lt;/span&gt;
  &lt;span class="nx"&gt;egress_only_gateway_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_egress_only_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_ipv6_egress_igw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Route for Subnet B to access the Egress-Only Internet Gateway&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"private2ipv6egressigwb"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_rt_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;destination_ipv6_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"::/0"&lt;/span&gt;
  &lt;span class="nx"&gt;egress_only_gateway_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_egress_only_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sample_ipv6_egress_igw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For one final time, run the &lt;code&gt;terraform plan&lt;/code&gt; and you should see an output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_route.private2ipv6egressigwa will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"private2ipv6egressigwa"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + destination_ipv6_cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"::/0"&lt;/span&gt;
      + egress_only_gateway_id      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eigw-0123456789abcdefg"&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + instance_id                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + instance_owner_id           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + network_interface_id        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + origin                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + route_table_id              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rtb-0123456789abcdef1"&lt;/span&gt;
      + state                       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# aws_route.private2ipv6egressigwb will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"private2ipv6egressigwb"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + destination_ipv6_cidr_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"::/0"&lt;/span&gt;
      + egress_only_gateway_id      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eigw-0123456789abcdefg"&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + instance_id                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + instance_owner_id           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + network_interface_id        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + origin                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + route_table_id              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rtb-0123456789abcdef2"&lt;/span&gt;
      + state                       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 2 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, that we are adding two routes, as we have two route tables to make the change to. Apply the changes, and you should see the new route in the route tables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44woarstafwms24kmrl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44woarstafwms24kmrl6.png" alt="New route pointing to the new Egress-Only Internet Gateway VPC resource" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, jumping on an EC2 instance in the private subnet (you can see on the command line that the instance is in the &lt;code&gt;192.168.12.0/24&lt;/code&gt; subnet), you can see we have complete outbound access on IPv6!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffah6iljkfrxs0vzz8zvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffah6iljkfrxs0vzz8zvu.png" alt="Private Subnet EC2 instance with connectivity through the Egress-Only Internet Gateway" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The final outcome
&lt;/h2&gt;

&lt;p&gt;So you finally did it, you have enabled IPv6 on your VPC. If all went to plan, you should have something that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc9zui9d5zc34kxzod05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc9zui9d5zc34kxzod05.png" alt="Sample VPC with IPv6 enabled" width="525" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make things a little more simpler, you can also check out the &lt;code&gt;02-vpc-with-ipv6&lt;/code&gt; folder in the sample code (&lt;a href="https://github.com/mystcb/ipv6-on-aws/tree/main/02-vpc-with-ipv6" rel="noopener noreferrer"&gt;https://github.com/mystcb/ipv6-on-aws/tree/main/02-vpc-with-ipv6&lt;/a&gt;), which will produce the same output&lt;br&gt;
as the diagram, and if you followed the changes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary and round up
&lt;/h2&gt;

&lt;p&gt;Thank you for getting this far! There is a lot here, but hopefully I have been able to show you how to add IPv6 to an AWS VPC using Terraform. However, this is only the beginning. Throughout my time working with IPv6, there will always be different issues to trip you up, and there are far more features than just a simple VPC!&lt;/p&gt;

&lt;p&gt;In a future post I hope to show you the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding IPv6 to an existing EC2 instance inside a VPC without rebuilding it. A little harder than I expected with Terraform, but I did find a way around it!&lt;/li&gt;
&lt;li&gt;IPv6 only VPCs! Much cheaper to run that a normal IPv4 VPC, but at what "cost", adoption of IPv6 is still quite low, but there are a number of design patterns that mean an IPv6 VPC might work better for some situations.&lt;/li&gt;
&lt;li&gt;Going into IPv6 in more detail. This is only the very basic information on IPv6, and a lot of what I have mentioned, does come with caveats! I will hope to explain these in more details later on&lt;/li&gt;
&lt;li&gt;Bits? Why does one number mean something else, and why on earth do we count in bits? - Hope you have learnt binary for this one!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you again, and feel free to send me any questions, or help me with any corrections to this post as well! Hopefully, I will see you in a future post!&lt;/p&gt;

&lt;p&gt;P.S. The final cost of me running this for the creation of the post, was mainly the NAT Gateways! Everything else was free! It only cost me $1.50 total! Always make sure you run &lt;code&gt;terraform destroy&lt;/code&gt; at the end!&lt;/p&gt;

&lt;p&gt;Further Reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/vpc/ipv6/" rel="noopener noreferrer"&gt;&lt;em&gt;IPv6 on AWS&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/IPv6" rel="noopener noreferrer"&gt;&lt;em&gt;IPv6 - Wikipedia&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mystcb/ipv6-on-aws" rel="noopener noreferrer"&gt;&lt;em&gt;Sample Code for this Post&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>promptengineering</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
