diff --git a/terraform/compute.tf b/terraform/compute.tf new file mode 100644 index 0000000..b3c857f --- /dev/null +++ b/terraform/compute.tf @@ -0,0 +1,31 @@ +module "ec2_ssh_key" { + source = "terraform-aws-modules/key-pair/aws" + key_name = join("-", [var.tags.builder, var.tags.project, var.tags.environment]) + public_key = var.ssh_public_key + + tags = var.tags +} + +module "standalone_ec2" { + for_each = var.ec2_template[*] + source = "terraform-aws-modules/ec2-instance/aws" + + name = each.value["hostname"] + ami = each.value["ami"] + + instance_type = each.value["family"] + key_name = module.ec2_ssh_key.key_pair_name + monitoring = true + + vpc_security_group_ids = [module.ec2_rdp_sg.security_group_id] + subnet_id = each.value["subnet"] + + root_block_device = [ + { + volume_size = each.value["disksize"] + encrypted = true + } + ] + + tags = var.tags +} \ No newline at end of file diff --git a/terraform/data.tf b/terraform/data.tf new file mode 100644 index 0000000..3049a18 --- /dev/null +++ b/terraform/data.tf @@ -0,0 +1,20 @@ +# Attempt to programmatically fetch AMI ARNs + +# data "aws_ami" "compute_ami" { +# most_recent = true + +# filter { +# name = "name" +# values = var.compute_ami +# } + +# filter { +# name = "virtualization-type" +# values = ["hvm"] +# } + +# filter { +# name = "architecture" +# values = ["x86_64"] +# } +# } diff --git a/terraform/database.tf b/terraform/database.tf new file mode 100644 index 0000000..200c18f --- /dev/null +++ b/terraform/database.tf @@ -0,0 +1,11 @@ +module "db_psql" { + source = "terraform-aws-modules/rds/aws" + for_each = var.db_template[*] + identifier = each.value["name"] + + engine = each.value["engine"] + engine_version = each.value["engine_version"] + instance_class = each.value["family"] + + tags = var.tags +} \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..46d553c --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,38 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.3" + } + } +} + +provider "aws" { + + region = var.region + + + # Configure Terraform to plan + # against localstack Docker container + # instead of AWS + access_key = "ak" + secret_key = "sk" + skip_credentials_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true + + endpoints { + iam = "http://localstack:4566" + sts = "http://localstack:4566" + s3 = "http://localstack:4566" + ec2 = "http://localstack:4566" + ssm = "http://localstack:4566" + rds = "http://localstack:4566" + } + + # Just in case I want to use a service account + #assume_role { + # role_arn = "arn:aws:iam::AWSACCOUNT:role/ROLE" + # session_name = "Temporary IaC provisioning role" + #} +} diff --git a/terraform/networking.tf b/terraform/networking.tf new file mode 100644 index 0000000..716c59a --- /dev/null +++ b/terraform/networking.tf @@ -0,0 +1,85 @@ +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + name = join("-", [var.tags.builder, var.tags.project, var.tags.environment]) + cidr = var.vpc_cidr + + azs = var.disaster_zones + private_subnets = var.private_cidrs + public_subnets = var.public_cidrs + + enable_nat_gateway = true + one_nat_gateway_per_az = true + + tags = var.tags +} + +module "loadbalancer" { + source = "terraform-aws-modules/alb/aws" + name = join("-", [var.tags.builder, var.tags.project, var.tags.environment]) + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.private_subnets + + security_groups = [ + module.ec2_web_sg.security_group_id + ] + + http_tcp_listeners = [ + { + port = var.exposed_ports[0] + protocol = "HTTP" + } + ] + + target_groups = [ + { + target_type = "ip" + backend_protocol = "TCP" + backend_port = var.exposed_ports[0] + } + ] + + tags = var.tags +} + +module "ec2_web_sg" { + source = "terraform-aws-modules/security-group/aws//modules/http-80" + name = join("-", [var.tags.builder, var.tags.project, var.tags.environment, "http"]) + description = "The primary security group for EC2s serving HTTP." + + vpc_id = module.vpc.vpc_id + ingress_cidr_blocks = var.private_cidrs + + tags = var.tags +} + +module "ec2_rdp_sg" { + source = "terraform-aws-modules/security-group/aws//modules/ssh" + name = join("-", [var.tags.builder, var.tags.project, var.tags.environment, "ssh"]) + description = "This security group allows remote desktop access." + + vpc_id = module.vpc.vpc_id + ingress_cidr_blocks = var.public_cidrs + + tags = var.tags +} + +module "db_psql_sg" { + source = "terraform-aws-modules/security-group/aws//modules/postgresql" + name = join("-", [var.tags.builder, var.tags.project, var.tags.environment, "db"]) + description = "This security group helps our compute access the database(s)." + vpc_id = module.vpc.vpc_id + + ingress_with_cidr_blocks = [ + { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + description = "PostgreSQL access from within VPC" + cidr_blocks = module.vpc.vpc_cidr_block + } + ] + + tags = var.tags +} \ No newline at end of file diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..fde326b --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,9 @@ +# output "compute_ami" { +# description = "Prints the image AMI installed on our EC2s." +# value = data.aws_ami.compute_ami +# } + +output "public_ec2" { + description = "Prints the ARN for the publicly accessible EC2" + value = module.standalone_ec2.arn +} \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 0000000..e0fade7 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,55 @@ +region = "us-gov-west-1" +disaster_zones = ["us-gov-west-1a", "us-gov-west-1b"] + +vpc_cidr = "10.1.0.0/16" +private_cidrs = ["10.1.2.0/24", "10.1.3.0/24", "10.1.4.0/24", "10.1.5.0/24"] +public_cidrs = ["10.1.0.0/24", "10.1.1.0/24"] +exposed_ports = ["80", "22"] + +ec2_template = [ + { + hostname = "bastion1" + ami = "winami" + elastic_ip = false + family = "t3a.medium" + disksize = "50" + subnet = "10.1.0.0/24" + }, + { + hostname = "wpserver1" + ami = "rhelami" + elastic_ip = false + family = "t3a.micro" + disksize = "20" + subnet = "10.1.2.0/24" + }, + { + hostname = "wpserver2" + ami = "rhelami" + elastic_ip = false + family = "t3a.micro" + disksize = "20" + subnet = "10.1.3.0/24" + } +] + +db_template = [ + { + name = "RDS1" + engine = "postgres" + engine_version = 11 + family = "db.t3.micro" + subnet = "10.1.5.0/24" + } +] + +ssh_public_key = "sooper sekrit" + +tags = { + lob = "cloud" + owner = "matt@coalfire.com" + environment = "dev" + open_to_internet = "true" + builder = "terraform" + project = "coalfire-3tier" +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..0cdd320 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,70 @@ +variable "tags" { + description = "Helpful tags for resource organization." + type = map(any) + default = {} +} + +variable "region" { + description = "Where you want to deploy your resources." + type = string + default = "us-east-1" +} + +variable "disaster_zones" { + description = "A list of availability zones for resource redundancy." + type = list(string) + default = [] +} + +variable "vpc_cidr" { + description = "The VPC subnet that supports the entire application plane." + type = string + default = "" +} + +variable "public_cidrs" { + description = "This subnet will support VMs with a route to the public internet." + type = list(string) + default = [] +} + +variable "private_cidrs" { + description = "This subnet will support VMs with DB or internal access." + type = list(string) + default = [] +} + +variable "exposed_ports" { + description = "A list of ports that are punched through the firewall." + type = list(number) + default = [] +} + +variable "ssh_public_key" { + description = "A public key signature for secure shell access." + type = string + default = "" +} + +variable "ec2_template" { + description = "A structured template for mostly uniform EC2s." + type = list(object({ + hostname = string + ami = string + elastic_ip = bool + family = string + disksize = number + subnet = string + })) +} + +variable "db_template" { + description = "A structured template for mostly uniform Amazon RDSes." + type = list(object({ + name = string + engine = string + engine_version = number + family = string + subnet = string + })) +} \ No newline at end of file