From 2a62e3961e20e4e58d4eaa8e0e3e64ebbe2116d2 Mon Sep 17 00:00:00 2001 From: Brian Manifold Date: Thu, 18 Jan 2024 15:38:55 -0500 Subject: [PATCH] feat(devops): Add AWS terraform (#3298) Why: * Previously the terraform for all of the AWS infra was created and run outside of the mono repo. While this was very quick to setup and work with, keeping the gateway up to date was easy to forget about. Moving all of the AWS infra TF into the mono repo will allow everything to stay up to date and will make sure everyone has easy access to update any of the infra as needed. --------- Co-authored-by: Jamil --- terraform/environments/staging/aws.tf | 209 +++++++++++++++++- terraform/environments/staging/variables.tf | 6 + terraform/modules/aws/bastion/main.tf | 18 ++ terraform/modules/aws/bastion/outputs.tf | 55 +++++ .../modules/aws/bastion/scripts/setup.sh | 34 +++ terraform/modules/aws/bastion/variables.tf | 82 +++++++ terraform/modules/aws/gateway/main.tf | 51 +++++ terraform/modules/aws/gateway/outputs.tf | 55 +++++ .../aws/gateway/templates/cloud-init.yaml | 40 ++++ terraform/modules/aws/gateway/variables.tf | 150 +++++++++++++ terraform/modules/aws/httpbin/main.tf | 19 ++ terraform/modules/aws/httpbin/outputs.tf | 55 +++++ .../modules/aws/httpbin/scripts/setup.sh | 19 ++ terraform/modules/aws/httpbin/variables.tf | 82 +++++++ terraform/modules/aws/iperf/main.tf | 19 ++ terraform/modules/aws/iperf/outputs.tf | 55 +++++ terraform/modules/aws/iperf/scripts/setup.sh | 20 ++ terraform/modules/aws/iperf/variables.tf | 82 +++++++ terraform/modules/aws/nat/main.tf | 19 ++ terraform/modules/aws/nat/outputs.tf | 55 +++++ terraform/modules/aws/nat/scripts/setup.sh | 18 ++ terraform/modules/aws/nat/variables.tf | 82 +++++++ 22 files changed, 1215 insertions(+), 10 deletions(-) create mode 100644 terraform/modules/aws/bastion/main.tf create mode 100644 terraform/modules/aws/bastion/outputs.tf create mode 100644 terraform/modules/aws/bastion/scripts/setup.sh create mode 100644 terraform/modules/aws/bastion/variables.tf create mode 100644 terraform/modules/aws/gateway/main.tf create mode 100644 terraform/modules/aws/gateway/outputs.tf create mode 100644 terraform/modules/aws/gateway/templates/cloud-init.yaml create mode 100644 terraform/modules/aws/gateway/variables.tf create mode 100644 terraform/modules/aws/httpbin/main.tf create mode 100644 terraform/modules/aws/httpbin/outputs.tf create mode 100644 terraform/modules/aws/httpbin/scripts/setup.sh create mode 100644 terraform/modules/aws/httpbin/variables.tf create mode 100644 terraform/modules/aws/iperf/main.tf create mode 100644 terraform/modules/aws/iperf/outputs.tf create mode 100644 terraform/modules/aws/iperf/scripts/setup.sh create mode 100644 terraform/modules/aws/iperf/variables.tf create mode 100644 terraform/modules/aws/nat/main.tf create mode 100644 terraform/modules/aws/nat/outputs.tf create mode 100644 terraform/modules/aws/nat/scripts/setup.sh create mode 100644 terraform/modules/aws/nat/variables.tf diff --git a/terraform/environments/staging/aws.tf b/terraform/environments/staging/aws.tf index 57d07d253..504e55428 100644 --- a/terraform/environments/staging/aws.tf +++ b/terraform/environments/staging/aws.tf @@ -3,7 +3,8 @@ provider "aws" { } locals { - aws_region = "us-east-1" + aws_region = "us-east-1" + environment = "staging" vpc_name = "Staging" vpc_cidr = "10.0.0.0/16" @@ -14,7 +15,7 @@ locals { tags = { Terraform = true - Environment = "staging" + Environment = local.environment } } @@ -28,16 +29,204 @@ module "vpc" { name = local.vpc_name cidr = local.vpc_cidr - enable_ipv6 = true - public_subnet_assign_ipv6_address_on_creation = true + azs = local.azs + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k + local.num_azs)] - private_subnet_enable_dns64 = false - private_subnet_enable_resource_name_dns_aaaa_record_on_launch = false + enable_ipv6 = true + public_subnet_assign_ipv6_address_on_creation = true + private_subnet_assign_ipv6_address_on_creation = true - azs = local.azs - public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] - private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k + local.num_azs)] - public_subnet_ipv6_prefixes = range(0, local.num_azs) + public_subnet_ipv6_prefixes = [0, 1] + private_subnet_ipv6_prefixes = [2, 3] tags = local.tags } + +resource "aws_route" "private_nat_instance" { + count = local.num_azs + + route_table_id = element(module.vpc.private_route_table_ids, count.index) + destination_cidr_block = "0.0.0.0/0" + network_interface_id = module.aws_nat.primary_network_interface_id + + timeouts { + create = "5m" + } +} + + +################################################################################ +# Compute +################################################################################ + +module "aws_bastion" { + source = "../../modules/aws/bastion" + + ami = data.aws_ami.ubuntu.id + name = "bastion - ${local.environment}" + + associate_public_ip_address = true + instance_type = "t3.micro" + key_name = local.ssh_keypair_name + vpc_security_group_ids = [ + module.sg_allow_all_egress.security_group_id, + module.sg_allow_ssh_ingress.security_group_id + ] + subnet_id = element(module.vpc.public_subnets, 0) + + tags = local.tags +} + +module "aws_nat" { + source = "../../modules/aws/nat" + + ami = data.aws_ami.ubuntu.id + name = "nat - ${local.environment}" + + associate_public_ip_address = true + instance_type = "t3.micro" + key_name = local.ssh_keypair_name + subnet_id = element(module.vpc.public_subnets, 0) + + vpc_security_group_ids = [ + module.sg_allow_all_egress.security_group_id, + module.sg_allow_subnet_ingress.security_group_id + ] + + tags = local.tags +} + +module "aws_httpbin" { + source = "../../modules/aws/httpbin" + + ami = data.aws_ami.ubuntu.id + name = "httpbin - ${local.environment}" + + associate_public_ip_address = false + instance_type = "t3.micro" + key_name = local.ssh_keypair_name + subnet_id = element(module.vpc.private_subnets, 0) + private_ip = cidrhost(element(module.vpc.private_subnets_cidr_blocks, 0), 100) + + vpc_security_group_ids = [ + module.sg_allow_all_egress.security_group_id, + module.sg_allow_subnet_ingress.security_group_id + ] + + tags = local.tags +} + +module "aws_iperf" { + source = "../../modules/aws/iperf" + + ami = data.aws_ami.ubuntu.id + name = "iperf - ${local.environment}" + + associate_public_ip_address = false + instance_type = "t3.micro" + key_name = local.ssh_keypair_name + subnet_id = element(module.vpc.private_subnets, 0) + private_ip = cidrhost(element(module.vpc.private_subnets_cidr_blocks, 0), 101) + + vpc_security_group_ids = [ + module.sg_allow_all_egress.security_group_id, + module.sg_allow_subnet_ingress.security_group_id + ] + + tags = local.tags +} + +module "aws_gateway" { + source = "../../modules/aws/gateway" + + ami = data.aws_ami.ubuntu.id + name = "gateway - ${local.environment}" + + associate_public_ip_address = false + instance_type = "t3.micro" + key_name = local.ssh_keypair_name + subnet_id = element(module.vpc.private_subnets, 0) + private_ip = cidrhost(element(module.vpc.private_subnets_cidr_blocks, 0), 50) + + vpc_security_group_ids = [ + module.sg_allow_all_egress.security_group_id, + module.sg_allow_subnet_ingress.security_group_id + ] + + # Gateway specific vars + container_registry = module.google-artifact-registry.url + image_repo = module.google-artifact-registry.repo + image = "gateway" + image_tag = var.image_tag + observability_log_level = "firezone_gateway=trace,wire=trace,connlib_gateway_shared=trace,firezone_tunnel=trace,connlib_shared=trace,warn" + application_name = "gateway" + application_version = replace(var.image_tag, ".", "-") + api_url = "wss://api.${local.tld}" + token = var.aws_gateway_token + + tags = local.tags +} + +################################################################################ +# Security Groups +################################################################################ + +module "sg_allow_all_egress" { + source = "terraform-aws-modules/security-group/aws" + + name = "allow all egress" + description = "Security group to allow all egress" + vpc_id = module.vpc.vpc_id + + egress_with_cidr_blocks = [ + { + rule = "all-all" + cidr_blocks = "0.0.0.0/0" + }, + ] + + egress_with_ipv6_cidr_blocks = [ + { + rule = "all-all" + ipv6_cidr_blocks = "::/0" + }, + ] +} + +module "sg_allow_subnet_ingress" { + source = "terraform-aws-modules/security-group/aws" + + name = "allow ingress from subnet" + description = "Security group to allow all ingress from other machines on the subnet" + vpc_id = module.vpc.vpc_id + + ingress_with_cidr_blocks = [ + { + rule = "all-all" + cidr_blocks = join(",", module.vpc.public_subnets_cidr_blocks) + }, + { + rule = "all-all", + cidr_blocks = join(",", module.vpc.private_subnets_cidr_blocks) + } + ] +} + +module "sg_allow_ssh_ingress" { + source = "terraform-aws-modules/security-group/aws" + + name = "allow SSH ingress from the internet" + description = "Security group to allow SSH ingress from the internet" + vpc_id = module.vpc.vpc_id + + ingress_with_cidr_blocks = [ + { + from_port = 22 + to_port = 22 + protocol = "tcp" + description = "SSH access from the internet" + cidr_blocks = "0.0.0.0/0" + } + ] +} diff --git a/terraform/environments/staging/variables.tf b/terraform/environments/staging/variables.tf index 0694c4e93..0cf8428a7 100644 --- a/terraform/environments/staging/variables.tf +++ b/terraform/environments/staging/variables.tf @@ -1,3 +1,9 @@ +variable "aws_gateway_token" { + type = string + description = "Firezone Gateway token for AWS gateway" + default = null +} + variable "image_tag" { type = string description = "Image tag for all services. Notice: we assume all services are deployed with the same version" diff --git a/terraform/modules/aws/bastion/main.tf b/terraform/modules/aws/bastion/main.tf new file mode 100644 index 000000000..3280c1e85 --- /dev/null +++ b/terraform/modules/aws/bastion/main.tf @@ -0,0 +1,18 @@ +resource "aws_instance" "this" { + ami = var.ami + instance_type = var.instance_type + monitoring = var.monitoring + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + associate_public_ip_address = var.associate_public_ip_address + + key_name = var.key_name + user_data = file("${path.module}/scripts/setup.sh") + + root_block_device { + volume_type = "gp3" + volume_size = 20 + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) +} diff --git a/terraform/modules/aws/bastion/outputs.tf b/terraform/modules/aws/bastion/outputs.tf new file mode 100644 index 000000000..11677e76d --- /dev/null +++ b/terraform/modules/aws/bastion/outputs.tf @@ -0,0 +1,55 @@ +output "id" { + description = "The ID of the instance" + value = try( + aws_instance.this.id, + null, + ) +} + +output "arn" { + description = "The ARN of the instance" + value = try( + aws_instance.this.arn, + null, + ) +} + +output "instance_state" { + description = "The state of the instance" + value = try( + aws_instance.this.instance_state, + null, + ) +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = try( + aws_instance.this.primary_network_interface_id, + null, + ) +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = try( + aws_instance.this.public_ip, + null, + ) +} + +output "private_ip" { + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this.private_ip, + null, + ) +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this.ipv6_addresses, + [], + ) +} diff --git a/terraform/modules/aws/bastion/scripts/setup.sh b/terraform/modules/aws/bastion/scripts/setup.sh new file mode 100644 index 000000000..07c9ed52b --- /dev/null +++ b/terraform/modules/aws/bastion/scripts/setup.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -xe + +# Install fail2ban +sudo apt-get update +sudo apt-get install -y fail2ban + +ORIG_CONF="/etc/fail2ban/jail.conf" +LOCAL_CONF="/etc/fail2ban/jail.local" + +if [ -f "${ORIG_CONF}" ]; then + # Configure fail2ban + sudo cp "${ORIG_CONF}" "${LOCAL_CONF}" + sudo sed -i 's/^bantime\s*= 10m$/bantime = 30m/' "${LOCAL_CONF}" + sudo sed -i 's/^findtime\s*= 10m/findtime = 30m/' "${LOCAL_CONF}" + sudo sed -i 's/maxretry\s*= 5/maxretry = 3/' "${LOCAL_CONF}" + + # Enable and Start fail2ban + sudo systemctl enable --now fail2ban +else + # If fail2ban is not on the sysytem, something has gone wrong + echo "Fail2Ban was not found on the system! Exiting..." +fi + +# Turn on automatic upgrades/reboots +UPGRADE_CONF_FILE="/etc/apt/apt.conf.d/50unattended-upgrades" + +sudo cp $UPGRADE_CONF_FILE /tmp/unattended-upgrades.conf +sudo sed -i 's/\/\/\(\s*"\${distro_id}:\${distro_codename}-updates";\)/ \1/' "${UPGRADE_CONF_FILE}" +sudo sed -i 's/\/\/\(Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";\)/\1/' "${UPGRADE_CONF_FILE}" +sudo sed -i 's/\/\/\(Unattended-Upgrade::Automatic-Reboot \)"false";/\1 "true";/' "${UPGRADE_CONF_FILE}" +sudo sed -i 's/\/\/\(Unattended-Upgrade::Automatic-Reboot-Time \)"02:00";/\1 "07:00;"/' "${UPGRADE_CONF_FILE}" +sudo sed -i 's/\/\/\(Unattended-Upgrade::Automatic-Reboot-WithUsers "true";\)/\1/' "${UPGRADE_CONF_FILE}" diff --git a/terraform/modules/aws/bastion/variables.tf b/terraform/modules/aws/bastion/variables.tf new file mode 100644 index 000000000..5eb91483f --- /dev/null +++ b/terraform/modules/aws/bastion/variables.tf @@ -0,0 +1,82 @@ +variable "ami" { + type = string + description = "AMI ID for the EC2 instance" + default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1 + + validation { + condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-" + error_message = "Please provide a valid value for variable AMI." + } +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = true +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + +variable "key_name" { + description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = false +} + +variable "name" { + description = "Name to be used on EC2 instance created" + type = string + default = "" +} + +variable "private_ip" { + description = "Private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "root_block_device" { + description = "Customize details about the root block device of the instance. See Block Devices below for details" + type = list(any) + default = [] +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate with" + type = list(string) + default = null +} diff --git a/terraform/modules/aws/gateway/main.tf b/terraform/modules/aws/gateway/main.tf new file mode 100644 index 000000000..e2364eb0d --- /dev/null +++ b/terraform/modules/aws/gateway/main.tf @@ -0,0 +1,51 @@ +locals { + application_name = var.application_name != null ? var.application_name : var.image + application_version = var.application_version != null ? var.application_version : var.image_tag + + environment_variables = concat([ + { + name = "RUST_LOG" + value = var.observability_log_level + }, + { + name = "RUST_BACKTRACE" + value = "full" + }, + { + name = "FIREZONE_TOKEN" + value = var.token + }, + { + name = "FIREZONE_API_URL" + value = var.api_url + }, + { + name = "FIREZONE_ENABLE_MASQUERADE" + value = "1" + } + ], var.application_environment_variables) +} + +resource "aws_instance" "this" { + ami = var.ami + instance_type = var.instance_type + monitoring = var.monitoring + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + key_name = var.key_name + + user_data = templatefile("${path.module}/templates/cloud-init.yaml", { + container_name = local.application_name != null ? local.application_name : var.image + container_image = "${var.container_registry}/${var.image_repo}/${var.image}:${var.image_tag}" + container_environment = local.environment_variables + }) + + root_block_device { + volume_type = "gp3" + volume_size = 20 + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) +} diff --git a/terraform/modules/aws/gateway/outputs.tf b/terraform/modules/aws/gateway/outputs.tf new file mode 100644 index 000000000..11677e76d --- /dev/null +++ b/terraform/modules/aws/gateway/outputs.tf @@ -0,0 +1,55 @@ +output "id" { + description = "The ID of the instance" + value = try( + aws_instance.this.id, + null, + ) +} + +output "arn" { + description = "The ARN of the instance" + value = try( + aws_instance.this.arn, + null, + ) +} + +output "instance_state" { + description = "The state of the instance" + value = try( + aws_instance.this.instance_state, + null, + ) +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = try( + aws_instance.this.primary_network_interface_id, + null, + ) +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = try( + aws_instance.this.public_ip, + null, + ) +} + +output "private_ip" { + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this.private_ip, + null, + ) +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this.ipv6_addresses, + [], + ) +} diff --git a/terraform/modules/aws/gateway/templates/cloud-init.yaml b/terraform/modules/aws/gateway/templates/cloud-init.yaml new file mode 100644 index 000000000..5de9728cc --- /dev/null +++ b/terraform/modules/aws/gateway/templates/cloud-init.yaml @@ -0,0 +1,40 @@ +#cloud-config + +write_files: + - path: /etc/firezone-gateway/env + permissions: "0644" + owner: root + content: | + %{ for env in container_environment ~} + ${env.name}=${env.value} + %{ endfor ~} + + - path: /etc/systemd/system/gateway.service + permissions: "0644" + owner: root + content: | + [Unit] + Description=Start an Firezone Gateway container + + [Service] + TimeoutStartSec=0 + Restart=always + ExecStartPre=/usr/bin/docker pull ${container_image} + ExecStart=/bin/sh -c 'docker run --rm --name=${container_name} --cap-add=NET_ADMIN --volume /etc/firezone --device="/dev/net/tun:/dev/net/tun" --env FIREZONE_NAME=$(hostname) --env FIREZONE_ID=$(echo $RANDOM$(hostname) | md5sum | head -c 20; echo;) --env-file="/etc/firezone-gateway/env" ${container_image}' + ExecStop=/usr/bin/docker stop gateway + ExecStopPost=/usr/bin/docker rm gateway + +runcmd: + - sudo apt-get update + - sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common + - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + - sudo apt-get update + - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + - echo '{"experimental":true,"ip6tables":true,"ipv6":true,"fixed-cidr-v6":"fd00::/80"}' | sudo tee -a /etc/docker/daemon.json + - sudo usermod -aG docker ubuntu + - sudo systemctl enable docker + - sudo systemctl stop docker + - sudo systemctl start docker + - sudo systemctl daemon-reload + - sudo systemctl start gateway.service diff --git a/terraform/modules/aws/gateway/variables.tf b/terraform/modules/aws/gateway/variables.tf new file mode 100644 index 000000000..988023734 --- /dev/null +++ b/terraform/modules/aws/gateway/variables.tf @@ -0,0 +1,150 @@ +variable "ami" { + description = "AMI ID for the EC2 instance" + type = string + default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1 + + validation { + condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-" + error_message = "Please provide a valid value for variable AMI." + } +} + +variable "api_url" { + description = "URL of the control plane endpoint." + type = string + default = null +} + +variable "application_environment_variables" { + description = "List of environment variables to set for all application containers." + type = list(object({ + name = string + value = string + })) + default = [] + nullable = false +} + +variable "application_name" { + description = "Name of the application. Defaults to value of `var.image_name` with `_` replaced to `-`." + type = string + nullable = true + default = null +} + +variable "application_version" { + description = "Version of the application. Defaults to value of `var.image_tag`." + type = string + nullable = true + default = null +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = true +} + +variable "container_registry" { + description = "Container registry URL to pull the image from." + type = string + nullable = false +} + +variable "image" { + description = "Container image used to deploy the application." + type = string + nullable = false +} + +variable "image_repo" { + description = "Repo of a container image used to deploy the application." + type = string + nullable = false +} + +variable "image_tag" { + description = "Container image used to deploy the application." + type = string + nullable = false +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + +variable "key_name" { + description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = null +} + +variable "name" { + description = "Name to be used on EC2 instance created" + type = string + default = "" +} + +variable "observability_log_level" { + description = "Sets RUST_LOG environment variable which applications should use to configure Rust Logger. Default: 'info'." + type = string + nullable = false + default = "info" + +} + +variable "private_ip" { + description = "Private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "root_block_device" { + description = "Customize details about the root block device of the instance. See Block Devices below for details" + type = list(any) + default = [] +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "token" { + description = "Portal token to use for authentication." + type = string + default = null +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate with" + type = list(string) + default = null +} diff --git a/terraform/modules/aws/httpbin/main.tf b/terraform/modules/aws/httpbin/main.tf new file mode 100644 index 000000000..f2a427774 --- /dev/null +++ b/terraform/modules/aws/httpbin/main.tf @@ -0,0 +1,19 @@ +resource "aws_instance" "this" { + ami = var.ami + instance_type = var.instance_type + monitoring = var.monitoring + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + + key_name = var.key_name + user_data = file("${path.module}/scripts/setup.sh") + + root_block_device { + volume_type = "gp3" + volume_size = 20 + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) +} diff --git a/terraform/modules/aws/httpbin/outputs.tf b/terraform/modules/aws/httpbin/outputs.tf new file mode 100644 index 000000000..11677e76d --- /dev/null +++ b/terraform/modules/aws/httpbin/outputs.tf @@ -0,0 +1,55 @@ +output "id" { + description = "The ID of the instance" + value = try( + aws_instance.this.id, + null, + ) +} + +output "arn" { + description = "The ARN of the instance" + value = try( + aws_instance.this.arn, + null, + ) +} + +output "instance_state" { + description = "The state of the instance" + value = try( + aws_instance.this.instance_state, + null, + ) +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = try( + aws_instance.this.primary_network_interface_id, + null, + ) +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = try( + aws_instance.this.public_ip, + null, + ) +} + +output "private_ip" { + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this.private_ip, + null, + ) +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this.ipv6_addresses, + [], + ) +} diff --git a/terraform/modules/aws/httpbin/scripts/setup.sh b/terraform/modules/aws/httpbin/scripts/setup.sh new file mode 100644 index 000000000..adace5390 --- /dev/null +++ b/terraform/modules/aws/httpbin/scripts/setup.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -xe + +sudo apt-get update +sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +sudo apt-get update +sudo apt-get install -y docker-ce +sudo usermod -aG docker ubuntu + +docker run \ + --restart=unless-stopped \ + --name=httpbin \ + -p "80:80" \ + kong/httpbin diff --git a/terraform/modules/aws/httpbin/variables.tf b/terraform/modules/aws/httpbin/variables.tf new file mode 100644 index 000000000..6ccc0678a --- /dev/null +++ b/terraform/modules/aws/httpbin/variables.tf @@ -0,0 +1,82 @@ +variable "ami" { + type = string + description = "AMI ID for the EC2 instance" + default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1 + + validation { + condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-" + error_message = "Please provide a valid value for variable AMI." + } +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = false +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + +variable "key_name" { + description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = false +} + +variable "name" { + description = "Name to be used on EC2 instance created" + type = string + default = "" +} + +variable "private_ip" { + description = "Private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "root_block_device" { + description = "Customize details about the root block device of the instance. See Block Devices below for details" + type = list(any) + default = [] +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate with" + type = list(string) + default = null +} diff --git a/terraform/modules/aws/iperf/main.tf b/terraform/modules/aws/iperf/main.tf new file mode 100644 index 000000000..f2a427774 --- /dev/null +++ b/terraform/modules/aws/iperf/main.tf @@ -0,0 +1,19 @@ +resource "aws_instance" "this" { + ami = var.ami + instance_type = var.instance_type + monitoring = var.monitoring + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + + key_name = var.key_name + user_data = file("${path.module}/scripts/setup.sh") + + root_block_device { + volume_type = "gp3" + volume_size = 20 + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) +} diff --git a/terraform/modules/aws/iperf/outputs.tf b/terraform/modules/aws/iperf/outputs.tf new file mode 100644 index 000000000..11677e76d --- /dev/null +++ b/terraform/modules/aws/iperf/outputs.tf @@ -0,0 +1,55 @@ +output "id" { + description = "The ID of the instance" + value = try( + aws_instance.this.id, + null, + ) +} + +output "arn" { + description = "The ARN of the instance" + value = try( + aws_instance.this.arn, + null, + ) +} + +output "instance_state" { + description = "The state of the instance" + value = try( + aws_instance.this.instance_state, + null, + ) +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = try( + aws_instance.this.primary_network_interface_id, + null, + ) +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = try( + aws_instance.this.public_ip, + null, + ) +} + +output "private_ip" { + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this.private_ip, + null, + ) +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this.ipv6_addresses, + [], + ) +} diff --git a/terraform/modules/aws/iperf/scripts/setup.sh b/terraform/modules/aws/iperf/scripts/setup.sh new file mode 100644 index 000000000..b01eeb5d6 --- /dev/null +++ b/terraform/modules/aws/iperf/scripts/setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -xe + +sudo apt-get update +sudo apt-get install -y iperf3 + +sudo tee -a /etc/systemd/system/iperf3.service << EOF +[Unit] +Description=iperf3 server +After=syslog.target network.target auditd.service + +[Service] +ExecStart=/usr/bin/iperf3 -s + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl enable --now iperf3 diff --git a/terraform/modules/aws/iperf/variables.tf b/terraform/modules/aws/iperf/variables.tf new file mode 100644 index 000000000..6ccc0678a --- /dev/null +++ b/terraform/modules/aws/iperf/variables.tf @@ -0,0 +1,82 @@ +variable "ami" { + type = string + description = "AMI ID for the EC2 instance" + default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1 + + validation { + condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-" + error_message = "Please provide a valid value for variable AMI." + } +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = false +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + +variable "key_name" { + description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = false +} + +variable "name" { + description = "Name to be used on EC2 instance created" + type = string + default = "" +} + +variable "private_ip" { + description = "Private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "root_block_device" { + description = "Customize details about the root block device of the instance. See Block Devices below for details" + type = list(any) + default = [] +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate with" + type = list(string) + default = null +} diff --git a/terraform/modules/aws/nat/main.tf b/terraform/modules/aws/nat/main.tf new file mode 100644 index 000000000..3c159988c --- /dev/null +++ b/terraform/modules/aws/nat/main.tf @@ -0,0 +1,19 @@ +resource "aws_instance" "this" { + ami = var.ami + instance_type = var.instance_type + monitoring = var.monitoring + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + associate_public_ip_address = var.associate_public_ip_address + source_dest_check = false + + key_name = var.key_name + user_data = file("${path.module}/scripts/setup.sh") + + root_block_device { + volume_type = "gp3" + volume_size = 15 + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) +} diff --git a/terraform/modules/aws/nat/outputs.tf b/terraform/modules/aws/nat/outputs.tf new file mode 100644 index 000000000..11677e76d --- /dev/null +++ b/terraform/modules/aws/nat/outputs.tf @@ -0,0 +1,55 @@ +output "id" { + description = "The ID of the instance" + value = try( + aws_instance.this.id, + null, + ) +} + +output "arn" { + description = "The ARN of the instance" + value = try( + aws_instance.this.arn, + null, + ) +} + +output "instance_state" { + description = "The state of the instance" + value = try( + aws_instance.this.instance_state, + null, + ) +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = try( + aws_instance.this.primary_network_interface_id, + null, + ) +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" + value = try( + aws_instance.this.public_ip, + null, + ) +} + +output "private_ip" { + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this.private_ip, + null, + ) +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this.ipv6_addresses, + [], + ) +} diff --git a/terraform/modules/aws/nat/scripts/setup.sh b/terraform/modules/aws/nat/scripts/setup.sh new file mode 100644 index 000000000..bc7a9d76b --- /dev/null +++ b/terraform/modules/aws/nat/scripts/setup.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -xe + +sudo apt-get update + +# Enable IP forwarding +echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf +sudo sysctl -p + +# Setup iptables NAT +sudo iptables -t nat -A POSTROUTING -o ens5 -s 0.0.0.0/0 -j MASQUERADE + +# Save iptables rules in case of reboot +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent +sudo systemctl enable --now netfilter-persistent.service +sudo mkdir -p /etc/iptables +sudo /usr/bin/iptables-save | sudo tee -a /etc/iptables/rules.v4 diff --git a/terraform/modules/aws/nat/variables.tf b/terraform/modules/aws/nat/variables.tf new file mode 100644 index 000000000..5eb91483f --- /dev/null +++ b/terraform/modules/aws/nat/variables.tf @@ -0,0 +1,82 @@ +variable "ami" { + type = string + description = "AMI ID for the EC2 instance" + default = "ami-0b2a9065573b0a9c9" # Ubuntu 22.04 in us-east-1 + + validation { + condition = length(var.ami) > 4 && substr(var.ami, 0, 4) == "ami-" + error_message = "Please provide a valid value for variable AMI." + } +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = true +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + +variable "key_name" { + description = "Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = false +} + +variable "name" { + description = "Name to be used on EC2 instance created" + type = string + default = "" +} + +variable "private_ip" { + description = "Private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "root_block_device" { + description = "Customize details about the root block device of the instance. See Block Devices below for details" + type = list(any) + default = [] +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "vpc_security_group_ids" { + description = "A list of security group IDs to associate with" + type = list(string) + default = null +}