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 +}