AWS Networking with Terraform: VPC Gateway Endpoint to connect to S3 over AWS PrivateLink
Tags: aws, awscommunitybuilder, terraform, upskilling
Published: 2024-02-22 18:22:16

In our previous post, we explained how to use VPC Interface Endpoints to establish secure connections with various AWS services via the AWS Private Network. 

Now, let's advance our AWS networking knowledge by exploring the implementation of VPC Gateway Endpoints, particularly focusing on connecting to Amazon S3. We'll walk through the process of setting up Terraform modules to streamline the creation of distinct components within this architecture.

A VPC endpoint enables customers to privately connect to supported AWS services and VPC endpoint services powered by AWS PrivateLink. Amazon VPC instances do not require public IP addresses to communicate with resources of the service. Traffic between an Amazon VPC and a service does not leave the Amazon network.

There are two types of VPC endpoints:
1. interface endpoints
2. gateway endpoints

## Gateway endpoints

A gateway endpoint targets specific IP routes in an Amazon VPC route table, in the form of a prefix-list, used for traffic destined to Amazon DynamoDB or Amazon Simple Storage Service (Amazon S3). Gateway endpoints do not enable AWS PrivateLink. Instances in an Amazon VPC do not require public IP addresses to communicate with VPC endpoints, as interface endpoints use local IP addresses within the consumer Amazon VPC. Gateway endpoints are destinations that are reachable from within an Amazon VPC through prefix-lists within the Amazon VPC’s route table. There is no additional charge for using gateway endpoints.


## Architecture Overview:

Before we embark on our journey, let's grasp the architectural layout we'll be working within:

![VPC Gateway Endpoint Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5uxaodk89xefj4082x7.png)

## Step 1: Create a VPC
Create a VPC with a public and private subnet. Please refer to my github repo in resources section below.

## Step 2: Establish IAM Role with Policy and Instance Profile for S3 Access
Facilitate secure access to Amazon S3 by configuring an IAM role with a corresponding policy and instance profile tailored for S3 interactions.
```terraform
####################################################
# Create the IAM role for EC2 assumption
####################################################
resource "aws_iam_role" "ec2_s3_role" {
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "sts:AssumeRole"
        ],
        "Principal" : {
          "Service" : [
            "ec2.amazonaws.com"
          ]
        }
      },
    ]
  })
  tags = merge(var.common_tags, {
    Name = "${var.naming_prefix}-ec2-iam-role"
  })
}

####################################################
# Create the IAM policy to allow all s3.* actions
####################################################
resource "aws_iam_policy" "ec2_s3_policy" {
  name = "ec2-iam-s3-policy"
  path = "/"
  policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Action" : [
            "s3:*"
          ],
          "Effect" : "Allow",
          "Resource" : "*"
        },
      ]
    }
  )
  tags = merge(var.common_tags, {
    Name = "${var.naming_prefix}-ec2-s3-policy"
  })

}

####################################################
# Attach IAM policy to the role
####################################################
resource "aws_iam_policy_attachment" "ec2_s3_role_policy" {
  policy_arn = aws_iam_policy.ec2_s3_policy.arn
  roles      = [aws_iam_role.ec2_s3_role.name]
  name       = "${var.naming_prefix}-ec2-s3-policy-att"
}

####################################################
# Create an EC2 instance profile with the role
####################################################
resource "aws_iam_instance_profile" "ec2_s3_instance_profile" {
  role = aws_iam_role.ec2_s3_role.name
  tags = merge(var.common_tags, {
    Name = "${var.naming_prefix}-ec2-s3-instance-profile"
  })
}

```

## Step 3: Deploy Bastion and Private Host with Instance Profile

Instantiate a bastion host and a private host, private host equipped with the requisite instance profile. Also amend security group to connect private host from bastion host.

```terraform
####################################################
# Create EC2 Server Instances
####################################################

module "vpc_a_bastion_host" {
  source           = "./modules/web"
  instance_type    = var.instance_type
  instance_key     = var.instance_key
  subnet_id        = module.vpc_a.public_subnets[0]
  vpc_id           = module.vpc_a.vpc_id
  ec2_name         = "Bastion Host A"
  sg_ingress_ports = var.sg_ingress_public
  common_tags      = local.common_tags
  naming_prefix    = local.naming_prefix
}

module "vpc_a_private_host" {
  source           = "./modules/web"
  instance_type    = var.instance_type
  instance_key     = var.instance_key
  subnet_id        = module.vpc_a.private_subnets[0]
  vpc_id           = module.vpc_a.vpc_id
  ec2_name         = "Private Host A"
  sg_ingress_ports = var.sg_ingress_private
  common_tags      = local.common_tags
  naming_prefix    = local.naming_prefix
  instance_profile = module.iam_ec2_s3.instance_profile_name
}

####################################################
# Amend Private Host SG to allow traffic from Bastion Host SG
####################################################
resource "aws_security_group_rule" "public_in_ssh" {
  type                     = "ingress"
  from_port                = 22
  to_port                  = 22
  protocol                 = "tcp"
  security_group_id        = module.vpc_a_private_host.security_group_id
  source_security_group_id = module.vpc_a_bastion_host.security_group_id
}
```

## Step 4: Provision an S3 Bucket

Set up an Amazon S3 bucket to serve as the repository for your data, enabling scalable and durable object storage.

```terraform
####################################################
# Create an S3 Bucket
####################################################
resource "aws_s3_bucket" "s3_bucket" {
  bucket = "s3-test-bucket-ct"

  tags = merge(local.common_tags, {
    Name = "${local.naming_prefix}-s3-bucket"
  })
}
```

## Step 5: Create a VPC Gateway Endpoint Connected to the Private Route Table

Establish a VPC Gateway Endpoint intricately linked to the private route table, thus enabling secure and efficient communication between your VPC and Amazon S3.

```terraform
####################################################
# Create Security Group and Gateway Endpoint
####################################################
resource "aws_vpc_endpoint" "s3_vpc_ep_gateway" {
  vpc_id            = module.vpc_a.vpc_id
  vpc_endpoint_type = "Gateway"
  service_name      = "com.amazonaws.${var.aws_region}.s3"
  route_table_ids   = "${[module.vpc_a.private_route_table_id]}"

  tags = merge(local.common_tags, {
    Name = "${local.naming_prefix}-gateway-endpoint"
  })
}

```

## Steps to Run Terraform
Follow these steps to execute the Terraform configuration:
```terraform
terraform init
terraform plan 
terraform apply -auto-approve
```

Upon successful completion, Terraform will provide relevant outputs.
```terraform
Apply complete! Resources: 18 added, 0 changed, 0 destroyed.

Outputs:

s3_bucket_name = "s3-test-bucket-ct"
vpc_a_bastion_host_IP = "54.173.10.180"
```

## Testing the outcome
VPC Interface Endpoint:

![VPC Interface Endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w94i8crgy6rq5tmb4yqg.png)

Prefix list association added to private route table:

![Prefix List associated to Route Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u612bu9484cdz1ty15nv.png)

S3 Bucket:

![s3 bucket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0g1624z99mrzpjbc61d2.png)


Accessing S3 bucket from private host via gateway endpoint:

![Accessing S3 via Gateway Endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/241xrx3y2167olg0xvhp.png)

![s3 objects](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3yjtd7c9n1if3g76exuy.png)

## Cleanup
Remember to stop AWS components to avoid large bills.
```terraform
terraform destroy -auto-approve
```

With this we've successfully established a VPC Gateway Endpoint to establish secure connections with Amazon S3 over the AWS private network.

In our upcoming module, we'll understand S3 Simple Storage Service to host a static website.

## Resources:
Github Link: https://github.com/chinmayto/terraform-aws-networking-vpc-gateway-endpoint
VPC Endpoints: https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html