From: Justin Wind Date: Mon, 11 Sep 2017 22:30:37 +0000 (-0700) Subject: initial commit of replacement infrastructure automation X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=8576668075ca95e44481d9c9ed29d7e6af024bdc;p=awsible initial commit of replacement infrastructure automation --- diff --git a/infrastructure/BOOTSTRAP.md b/infrastructure/BOOTSTRAP.md new file mode 100644 index 0000000..b9bae27 --- /dev/null +++ b/infrastructure/BOOTSTRAP.md @@ -0,0 +1,44 @@ +# AWSible Infrastructure via Terraform + +Ensure the correct profile will be used: + + export AWS_PROFILE=profile + +Initialize the shared infrastructure needed by terraform: + + pushd terraform-infrastructure + terraform init + terraform apply + ../generate-backend-configs.sh > backend.tf + echo yes | terraform init + popd + +Create the VPC: + + pushd vpc + ../generate-backend-configs.sh > backend.tf + terraform init + terraform apply + popd + +Create the management stack: + + pushd management-stack + ../generate-backend-configs.sh > backend.tf + terraform init + terraform apply + popd + +Create the vpcaccess stack: + + pushd vpcaccess-stack + ../generate-backend-configs.sh > backend.tf + terraform init + terraform apply + popd + +Run Ansible by hand to configure the vpcaccess server, then connect to the VPN. +Populate the management EFS. +Run Ansible by hand to configure a management server, then scale up the management ASG. + +Create and deploy any other stacks. diff --git a/infrastructure/generate-backend-configs.sh b/infrastructure/generate-backend-configs.sh new file mode 100755 index 0000000..f44ef5d --- /dev/null +++ b/infrastructure/generate-backend-configs.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# this is a rough hack at the moment, it needs to be better + +set -e +set -o pipefail + +tf_infra=$(pushd ../terraform-infrastructure >/dev/null && terraform output -json && popd >/dev/null) +function infra_value(){ + jq -er ".${1}.value"<<<"${tf_infra}" +} + +cat< 0 ? aws_elb.management.name : ""}"] + lifecycle { + create_before_destroy = true + } + tag { + propagate_at_launch = true + key = "module" + value = "${var.management_service_name}" + } + tag { + propagate_at_launch = true + key = "phase" + value = "${var.phase}" + } +} + +resource "aws_autoscaling_notification" "management" { + group_names = ["${aws_autoscaling_group.management.name}"] + topic_arn = "${aws_sns_topic.management-events.arn}" + notifications = [ + "autoscaling:EC2_INSTANCE_LAUNCH", + "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", + "autoscaling:EC2_INSTANCE_TERMINATE", + "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" + ] +} + +data "aws_subnet" "management" { + count = "${length(var.management_subnet_ids)}" + id = "${element(var.management_subnet_ids, count.index)}" +} + +resource "aws_ebs_volume" "management-data" { + count = "${length(var.management_subnet_ids) * var.management_data_efs}" + availability_zone = "${element(data.aws_subnet.management.*.availability_zone, count.index)}" + size = "${var.management_data_volume_size}" + type = "gp2" + tags { + module = "${var.management_service_name}" + } +} + +resource "aws_efs_file_system" "management-data" { + count = "${var.management_data_efs}" + creation_token = "${var.management_service_name}-data" + tags { + Name = "${var.management_service_name}-data" + } +} + +resource "aws_efs_mount_target" "management-data" { + count = "${length(var.management_subnet_ids) * var.management_data_efs}" + file_system_id = "${aws_efs_file_system.management-data.id}" + subnet_id = "${element(var.management_subnet_ids, count.index)}" + security_groups = ["${aws_security_group.management.id}"] +} diff --git a/infrastructure/modules/management-stack/outputs.tf b/infrastructure/modules/management-stack/outputs.tf new file mode 100644 index 0000000..e03c6eb --- /dev/null +++ b/infrastructure/modules/management-stack/outputs.tf @@ -0,0 +1,4 @@ +output "alerts_topic_arn" { value = "${aws_sns_topic.management-alerts.arn}" } +output "events_topic_arn" { value = "${aws_sns_topic.management-events.arn}" } +output "events_queue_name" { value = "${aws_sqs_queue.management-events-queue.name}" } +output "management_data_efs_id" { value = "${var.management_data_efs > 0 ? aws_efs_file_system.management-data.id : ""}" } diff --git a/infrastructure/modules/management-stack/queues.tf b/infrastructure/modules/management-stack/queues.tf new file mode 100644 index 0000000..b44f69f --- /dev/null +++ b/infrastructure/modules/management-stack/queues.tf @@ -0,0 +1,48 @@ +resource "aws_sqs_queue" "management-events-dlq" { + name = "${length(var.sqs_events_name) > 0 ? var.sqs_events_name : var.management_service_name}${length(var.sqs_events_name) > 0 ? "" : "-events"}-failed" + visibility_timeout_seconds = 30 + message_retention_seconds = 1209600 + max_message_size = 262144 + receive_wait_time_seconds = 0 +} +resource "aws_sqs_queue" "management-events-queue" { + name = "${length(var.sqs_events_name) > 0 ? var.sqs_events_name : var.management_service_name}${length(var.sqs_events_name) > 0 ? "" : "-events"}" + visibility_timeout_seconds = 60 + message_retention_seconds = 1209600 + max_message_size = 262144 + receive_wait_time_seconds = 20 + redrive_policy = "{\"deadLetterTargetArn\":\"${aws_sqs_queue.management-events-dlq.arn}\",\"maxReceiveCount\":5}" +} +resource "aws_sns_topic" "management-events" { + name = "${length(var.sns_events_name) > 0 ? var.sns_events_name : var.management_service_name}${length(var.sns_events_name) > 0 ? "" : "-events"}" +} +data "aws_iam_policy_document" "management-queue" { + statement { + effect = "Allow" + sid = "TopicPublish" + actions = ["SQS:SendMessage"] + resources = ["${aws_sqs_queue.management-events-queue.arn}"] + condition { + test = "ForAnyValue:ArnEquals" + variable = "aws:SourceArn" + values = ["${aws_sns_topic.management-events.arn}"] + } + principals { + type = "AWS" + identifiers = ["*"] + } + } +} +resource "aws_sqs_queue_policy" "management-events" { + queue_url = "${aws_sqs_queue.management-events-queue.id}" + policy = "${data.aws_iam_policy_document.management-queue.json}" +} +resource "aws_sns_topic_subscription" "management-events-subscription" { + topic_arn = "${aws_sns_topic.management-events.arn}" + endpoint = "${aws_sqs_queue.management-events-queue.arn}" + protocol = "sqs" +} + +resource "aws_sns_topic" "management-alerts" { + name = "${length(var.sns_alerts_name) > 0 ? var.sns_alerts_name : var.management_service_name}${length(var.sns_alerts_name) > 0 ? "" : "-alerts"}" +} diff --git a/infrastructure/modules/management-stack/user-data.tpl b/infrastructure/modules/management-stack/user-data.tpl new file mode 100644 index 0000000..72caca9 --- /dev/null +++ b/infrastructure/modules/management-stack/user-data.tpl @@ -0,0 +1,8 @@ +#!/bin/bash +export EC2_REGION="${region}" +export CLOUD_APP="${app_name}" +export CLOUD_STACK="${stack}" +export CLOUD_DEV_PHASE="${phase}" +export CLOUD_COUNTRIES="${country}" +export CLOUD_ENVIRONMENT="${acct_name}" +export CLOUD_CLUSTER="${cluster}" diff --git a/infrastructure/modules/management-stack/variables.tf b/infrastructure/modules/management-stack/variables.tf new file mode 100644 index 0000000..c47e8b6 --- /dev/null +++ b/infrastructure/modules/management-stack/variables.tf @@ -0,0 +1,74 @@ +variable "vpc_id" { + description = "Which VPC to build this in." +} + +variable "acct_name" { + description = "Name of AWS account." +} + +variable "instance_type" { + default = "t2.small" +} + +variable "key_name" {} + +variable "management_subnet_ids" { + type = "list" + description = "Which subnets the management servers will be in. (Typically private.)" +} + +variable "phase" { + description = "Release phase of this environment. (Such as dev, stage, or prod.)" + default = "dev" +} + +variable "ami" { + description = "Specify an AMI to use; if empty, use most recent amazon linux." + default = "" +} + +variable "security_group_ids" { + type = "list" + description = "Additional security groups the management servers will belong to. (Typically the general-access SG.)" + default = [] +} + +variable "policy_arns" { + type = "list" + description = "Additional policy arns the management role will have. (Typically the base policy.)" + default = [] +} + +variable "management_elb" { + default = false + description = "Whether to place management servers behind an ELB." +} + +variable "management_data_efs" { + default = true + description = "Management instances share a common EFS filesystem. If false, each has its own EBS volume." +} + +variable "management_data_volume_size" { + default = 20 + description = "Size of individual data volumes, if used." +} + +variable "management_service_name" { + default = "management" +} + +variable "sns_events_name" { + default = "" + description = "Name of the SNS topic to which ASGs send notifications. Defaults to {management_service_name}-events." +} + +variable "sqs_events_name" { + default = "" + description = "Name of the SQS queue the events topic feeds, and the management system listens to. Defaults to {management_service_name}-events." +} + +variable "sns_alerts_name" { + default = "" + description = "Name of the SNS topic to which informational messages are sent, to be mailed to interested meat-based parties. Defaults to {management_service_name}-alerts." +} diff --git a/infrastructure/modules/management-stack/version.tf b/infrastructure/modules/management-stack/version.tf new file mode 100644 index 0000000..353643f --- /dev/null +++ b/infrastructure/modules/management-stack/version.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.10.2" +} diff --git a/infrastructure/modules/terraform-infrastructure/main.tf b/infrastructure/modules/terraform-infrastructure/main.tf new file mode 100644 index 0000000..1da303d --- /dev/null +++ b/infrastructure/modules/terraform-infrastructure/main.tf @@ -0,0 +1,19 @@ +resource "aws_s3_bucket" "tf_state" { + bucket = "${var.remote_state_bucket}" + region = "${var.region}" + acl = "private" + versioning { + enabled = true + } +} + +resource "aws_dynamodb_table" "tf_lock" { + name = "${var.remote_state_table}" + read_capacity = 1 + write_capacity = 1 + hash_key = "LockID" + attribute { + name = "LockID" + type = "S" + } +} diff --git a/infrastructure/modules/terraform-infrastructure/outputs.tf b/infrastructure/modules/terraform-infrastructure/outputs.tf new file mode 100644 index 0000000..54b2cf9 --- /dev/null +++ b/infrastructure/modules/terraform-infrastructure/outputs.tf @@ -0,0 +1,3 @@ +output "region" { value = "${var.region}" } +output "remote_state_bucket" { value = "${var.remote_state_bucket}" } +output "remote_state_table" { value = "${var.remote_state_table}" } diff --git a/infrastructure/modules/terraform-infrastructure/variables.tf b/infrastructure/modules/terraform-infrastructure/variables.tf new file mode 100644 index 0000000..b1df2ff --- /dev/null +++ b/infrastructure/modules/terraform-infrastructure/variables.tf @@ -0,0 +1,3 @@ +variable "region" {} +variable "remote_state_bucket" {} +variable "remote_state_table" {} diff --git a/infrastructure/modules/tf_aws_asg_stack/README.md b/infrastructure/modules/tf_aws_asg_stack/README.md new file mode 100644 index 0000000..bfe033b --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/README.md @@ -0,0 +1,3 @@ +# AutoScalingGroup Stack + +Creates an ASG, LC, self-allowing SG, and an IAM role for a certain style of application stack. diff --git a/infrastructure/modules/tf_aws_asg_stack/iam.tf b/infrastructure/modules/tf_aws_asg_stack/iam.tf new file mode 100644 index 0000000..1c257c4 --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/iam.tf @@ -0,0 +1,49 @@ +data "aws_iam_policy_document" "instance_trust" { + statement { + effect = "Allow" + actions = [ + "sts:AssumeRole" + ] + principals { + type = "Service" + identifiers = [ + "ec2.amazonaws.com" + ] + } + } +} + +resource "aws_iam_role" "default" { + name = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}-role" + assume_role_policy = "${data.aws_iam_policy_document.instance_trust.json}" +} + +data "aws_iam_policy_document" "default" { + statement { + effect = "Allow" + actions = ["${var.iam_allow_actions}"] + resources = ["*"] + } +} + +resource "aws_iam_policy" "default" { + name = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}" + description = "specific policy for ${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}" + policy = "${data.aws_iam_policy_document.default.json}" +} + +resource "aws_iam_role_policy_attachment" "default" { + role = "${aws_iam_role.default.id}" + policy_arn = "${aws_iam_policy.default.arn}" +} + +resource "aws_iam_role_policy_attachment" "extra" { + count = "${length(var.iam_policy_arns)}" + role = "${aws_iam_role.default.id}" + policy_arn = "${element(var.iam_policy_arns, count.index)}" +} + +resource "aws_iam_instance_profile" "default" { + name = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}-instance-profile" + role = "${aws_iam_role.default.name}" +} diff --git a/infrastructure/modules/tf_aws_asg_stack/main.tf b/infrastructure/modules/tf_aws_asg_stack/main.tf new file mode 100644 index 0000000..6a50ea3 --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/main.tf @@ -0,0 +1,125 @@ +resource "aws_security_group" "default" { + vpc_id = "${var.vpc_id}" + name = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}-self" + description = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack} self-access" +} +resource "aws_security_group_rule" "default-out-all" { + security_group_id = "${aws_security_group.default.id}" + type = "egress" + from_port = 0 + to_port = 0 + protocol = "all" + cidr_blocks = [ "0.0.0.0/0" ] +} +resource "aws_security_group_rule" "default-in-self" { + security_group_id = "${aws_security_group.default.id}" + type = "ingress" + from_port = 0 + to_port = 0 + protocol = "all" + self = true +} +resource "aws_security_group_rule" "default-in-elb" { + count = "${length(var.elb_sg_ids)}" + security_group_id = "${aws_security_group.default.id}" + type = "ingress" + from_port = 0 + to_port = 0 + protocol = "all" + source_security_group_id = "${element(var.elb_sg_ids, count.index)}" +} + +data "aws_ami" "amazon_linux" { + count = "${length(var.ami) > 0 ? 0 : 1}" + most_recent = true + owners = ["amazon"] + filter { + name = "name" + values = ["amzn-ami-hvm-*-gp2"] + } + filter { + name = "root-device-type" + values = ["ebs"] + } +} + +data "aws_region" "current" { + current = true +} +data "template_file" "user_data" { + template = "${file("${path.module}/user-data.tpl")}" + vars { + region = "${data.aws_region.current.name}" + app_name = "${var.module}" + stack = "${var.stack}" + phase = "${var.phase}" + country = "${var.country}" + cluster = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}${length(var.country) > 0 ? "-c0" : ""}${var.country}${length(var.phase) > 0 ? "-d0" : ""}${var.phase}${length(var.suffix) > 0 ? "-" : ""}${var.suffix}" + acct_name = "${var.acct_name}" + } +} + +resource "aws_launch_configuration" "default" { + name_prefix = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}${length(var.country) > 0 ? "-c0" : ""}${var.country}${length(var.phase) > 0 ? "-d0" : ""}${var.phase}${length(var.suffix) > 0 ? "-" : ""}${var.suffix}-" + image_id = "${length(var.ami) > 0 ? var.ami : data.aws_ami.amazon_linux.image_id}" + instance_type = "${var.instance_type}" + iam_instance_profile = "${aws_iam_instance_profile.default.name}" + key_name = "${var.key_name}" + security_groups = ["${concat(var.security_group_ids, list(aws_security_group.default.id))}"] + associate_public_ip_address = "${var.public_ips}" + user_data = "${data.template_file.user_data.rendered}" + ephemeral_block_device { + virtual_name = "ephemeral0" + device_name = "/dev/sdb" + } + lifecycle { + create_before_destroy = true + } +} + +resource "aws_autoscaling_group" "default" { + name = "${var.module}${length(var.stack) > 0 ? "-" : ""}${var.stack}${length(var.country) > 0 ? "-c0" : ""}${var.country}${length(var.phase) > 0 ? "-d0" : ""}${var.phase}${length(var.suffix) > 0 ? "-" : ""}${var.suffix}" + launch_configuration = "${aws_launch_configuration.default.name}" + vpc_zone_identifier = ["${var.subnet_ids}"] + min_size = "${var.min_size}" + max_size = "${var.max_size > 0 ? var.max_size : length(var.subnet_ids)}" + default_cooldown = 10 + health_check_type = "EC2" + health_check_grace_period = "${var.health_check_grace_period}" + load_balancers = ["${var.elbs}"] + lifecycle { + create_before_destroy = true + } + tag { + propagate_at_launch = true + key = "module" + value = "${var.module}" + } + tag { + propagate_at_launch = true + key = "stack" + value = "${var.stack}" + } + tag { + propagate_at_launch = true + key = "country" + value = "${var.country}" + } + tag { + propagate_at_launch = true + key = "phase" + value = "${var.phase}" + } +} + +resource "aws_autoscaling_notification" "default" { + count = "${length(var.notification_arns)}" + group_names = ["${aws_autoscaling_group.default.name}"] + topic_arn = "${element(var.notification_arn, count.index)}" + notifications = [ + "autoscaling:EC2_INSTANCE_LAUNCH", + "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", + "autoscaling:EC2_INSTANCE_TERMINATE", + "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" + ] +} diff --git a/infrastructure/modules/tf_aws_asg_stack/outputs.tf b/infrastructure/modules/tf_aws_asg_stack/outputs.tf new file mode 100644 index 0000000..fa65ab8 --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/outputs.tf @@ -0,0 +1,4 @@ +output "sg_id" { value = "${aws_security_group.default.id}" } +output "asg_id" { value = "${aws_autoscaling_group.default.id}" } +output "iam_role_id" { value = "${aws_iam_role.default.id}" } +output "iam_role_name" { value = "${aws_iam_role.default.name}" } diff --git a/infrastructure/modules/tf_aws_asg_stack/user-data.tpl b/infrastructure/modules/tf_aws_asg_stack/user-data.tpl new file mode 100644 index 0000000..72caca9 --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/user-data.tpl @@ -0,0 +1,8 @@ +#!/bin/bash +export EC2_REGION="${region}" +export CLOUD_APP="${app_name}" +export CLOUD_STACK="${stack}" +export CLOUD_DEV_PHASE="${phase}" +export CLOUD_COUNTRIES="${country}" +export CLOUD_ENVIRONMENT="${acct_name}" +export CLOUD_CLUSTER="${cluster}" diff --git a/infrastructure/modules/tf_aws_asg_stack/variables.tf b/infrastructure/modules/tf_aws_asg_stack/variables.tf new file mode 100644 index 0000000..e8a8b0d --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/variables.tf @@ -0,0 +1,97 @@ +variable "vpc_id" { + description = "Which VPC to build this in." +} + +variable "acct_name" { + description = "Name of AWS account." +} + +variable "notification_arns" { + type = "list" + description = "ARNs of SNS topics to send ASG event notifications to." + default = "" +} + +variable "module" { + description = "Name of this application." +} + +variable "stack" { + description = "Subtype of module. (Such as 'master' or 'data'.)" + default = "" +} + +variable "country" { + description = "'Country' code, which actually represents a specific AZ for us." + default = "" +} + +variable "phase" { + description = "Release phase of this environment. (Such as dev, stage, or prod.)" + default = "dev" +} + +variable "suffix" { + description = "Extra stuff tacked on to end of name." + default = "" +} + +variable "instance_type" {} + +variable "key_name" {} + +variable "public_ips" { + default = false +} + +variable "subnet_ids" { + type = "list" + description = "Which subnets the servers will be in." +} + +variable "iam_allow_actions" { + type = "list" + default = [] + description = "Allowed actions to associate with the IAM role." +} + +variable "iam_policy_arns" { + type = "list" + default = [] + description = "Additional policies to attach to IAM role." +} + +variable "max_size" { + description = "Defaults to 1 instance per subnet. (Cannot be zero.)" + default = 0 +} + +variable "min_size" { + default = 0 +} + +variable "health_check_grace_period" { + default = 600 +} + +variable "ami" { + description = "Specify an AMI to use; if empty, use most recent amazon linux." + default = "" +} + +variable "elb_sg_ids" { + type = "list" + description = "ID of elb security group, to allow access from." + default = [] +} + +variable "elbs" { + type = "list" + default = [] +} + +variable "security_group_ids" { + type = "list" + description = "Additional security groups the servers will belong to." + default = [] +} diff --git a/infrastructure/modules/tf_aws_asg_stack/version.tf b/infrastructure/modules/tf_aws_asg_stack/version.tf new file mode 100644 index 0000000..a56eb0c --- /dev/null +++ b/infrastructure/modules/tf_aws_asg_stack/version.tf @@ -0,0 +1,4 @@ +terraform { + required_version = ">= 0.10.2" +} + diff --git a/infrastructure/modules/tf_aws_vpc/README.md b/infrastructure/modules/tf_aws_vpc/README.md new file mode 100644 index 0000000..8b31cad --- /dev/null +++ b/infrastructure/modules/tf_aws_vpc/README.md @@ -0,0 +1,10 @@ +Creates a basic AWS VPC environment, with a public and private subnet per AZ, NAT gateways, and routing tables, as well as a base iam role and security group. + +Required variables: + + region + project + environment + cidr + public_azs + private_azs diff --git a/infrastructure/modules/tf_aws_vpc/main.tf b/infrastructure/modules/tf_aws_vpc/main.tf new file mode 100644 index 0000000..4b36310 --- /dev/null +++ b/infrastructure/modules/tf_aws_vpc/main.tf @@ -0,0 +1,239 @@ +resource "aws_vpc_dhcp_options" "default" { + count = "${var.enable_domain_name}" + domain_name = "ec2.internal ${var.r53_domain_name}" + domain_name_servers = ["AmazonProvidedDNS"] + tags { + Name = "${var.project}-${var.environment}-dhcp_options_set" + service = "${var.project}-${var.environment}-dhcp_options_set" + project = "${var.project}" + environment = "${var.environment}" + role = "dhcp_options_set" + } +} + +resource "aws_vpc_dhcp_options_association" "default" { + count = "${var.enable_domain_name}" + vpc_id = "${aws_vpc.default.id}" + dhcp_options_id = "${aws_vpc_dhcp_options.default.id}" +} + +resource "aws_vpc" "default" { + cidr_block = "${var.cidr}" + enable_dns_hostnames = "${var.enable_dns_hostnames}" + enable_dns_support = "${var.enable_dns_support}" + instance_tenancy = "default" + tags { + Name = "${var.project}-${var.environment}-vpc" + service = "${var.project}-${var.environment}-vpc" + project = "${var.project}" + environment = "${var.environment}" + role = "vpc" + } +} + +resource "aws_internet_gateway" "default" { + vpc_id = "${aws_vpc.default.id}" + tags { + Name = "${var.project}-${var.environment}-igw" + service = "${var.project}-${var.environment}-igw" + project = "${var.project}" + environment = "${var.environment}" + role = "igw" + } +} + +data "aws_vpc_peering_connection" "peer" { + count = "${length(var.peering_connection_ids)}" + id = "${element(var.peering_connection_ids, count.index)}" +} + +resource "aws_default_route_table" "default" { + default_route_table_id = "${aws_vpc.default.default_route_table_id}" +} + +resource "aws_route" "default_gateway" { + route_table_id = "${aws_default_route_table.default.id}" + destination_cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.default.id}" +} + +resource "aws_route" "default_peer" { + count = "${length(var.peering_connection_ids)}" + route_table_id = "${aws_default_route_table.default.id}" + destination_cidr_block = "${element(data.aws_vpc_peering_connection.peer.*.cidr_block, count.index)}" + vpc_peering_connection_id = "${element(data.aws_vpc_peering_connection.peer.*.id, count.index)}" +} + +resource "aws_route_table" "public" { + vpc_id = "${aws_vpc.default.id}" + tags { + Name = "${var.project}-${var.environment}-public" + service = "${var.project}-${var.environment}-route-table" + project = "${var.project}" + environment = "${var.environment}" + role = "route-table" + } +} + +resource "aws_route" "public_gateway" { + route_table_id = "${aws_route_table.public.id}" + destination_cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.default.id}" +} + +resource "aws_route" "public_peer" { + count = "${length(var.peering_connection_ids)}" + route_table_id = "${aws_route_table.public.id}" + destination_cidr_block = "${element(data.aws_vpc_peering_connection.peer.*.cidr_block, count.index)}" + vpc_peering_connection_id = "${element(data.aws_vpc_peering_connection.peer.*.id, count.index)}" +} + +resource "aws_subnet" "public" { + count = "${length(var.public_azs)}" + vpc_id = "${aws_vpc.default.id}" + cidr_block = "${cidrsubnet(var.cidr, 8, count.index + var.subnets_offset_public)}" + availability_zone = "${element(var.public_azs, count.index)}" + tags { + Name = "${var.project}-${var.environment}-public-${element(var.public_azs, count.index)}" + project = "${var.project}" + environment = "${var.environment}" + service = "${var.project}-${var.environment}-subnet-public" + role = "subnet" + zone = "pub" + } + map_public_ip_on_launch = true +} + +resource "aws_route_table_association" "public" { + count = "${length(var.public_azs)}" + subnet_id = "${element(aws_subnet.public.*.id, count.index)}" + route_table_id = "${element(aws_route_table.public.*.id, count.index)}" +} + +resource "aws_subnet" "private" { + count = "${length(var.private_azs)}" + vpc_id = "${aws_vpc.default.id}" + cidr_block = "${cidrsubnet(var.cidr, 8, count.index + var.subnets_offset_private)}" + availability_zone = "${element(var.private_azs, count.index)}" + tags { + Name = "${var.project}-${var.environment}-private-${element(var.private_azs, count.index)}" + project = "${var.project}" + environment = "${var.environment}" + service = "${var.project}-${var.environment}-subnet-private" + role = "subnet" + zone = "priv" + } + map_public_ip_on_launch = false +} + +resource "aws_route_table_association" "private" { + count = "${length(var.private_azs)}" + subnet_id = "${element(aws_subnet.private.*.id, count.index)}" + route_table_id = "${element(aws_route_table.private.*.id, count.index)}" +} + +resource "aws_route_table" "private" { + count = "${length(var.private_azs)}" + vpc_id = "${aws_vpc.default.id}" + tags { + Name = "${var.project}-${var.environment}-private${format("%02d", count.index + 1)}" + project = "${var.project}" + environment = "${var.environment}" + service = "${var.project}-${var.environment}-route-table-private" + role = "route-table" + } +} + +resource "aws_route" "private_gateway" { + count = "${length(var.private_azs)}" + route_table_id = "${element(aws_route_table.private.*.id, count.index)}" + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = "${element(aws_nat_gateway.default.*.id, count.index)}" +} + +resource "aws_route" "private_peer" { + count = "${length(var.peering_connection_ids) * length(var.private_azs)}" + route_table_id = "${element(aws_route_table.private.*.id, count.index / length(var.private_azs))}" + destination_cidr_block = "${element(data.aws_vpc_peering_connection.peer.*.cidr_block, count.index % length(var.private_azs))}" + vpc_peering_connection_id = "${element(data.aws_vpc_peering_connection.peer.*.id, count.index % length(var.private_azs))}" +} + +resource "aws_eip" "nat" { + count = "${length(var.private_azs)}" + vpc = true +} + +resource "aws_nat_gateway" "default" { + count = "${length(var.private_azs)}" + allocation_id = "${element(aws_eip.nat.*.id, count.index)}" + subnet_id = "${element(aws_subnet.public.*.id, count.index)}" +} + +data "aws_iam_policy_document" "base" { + statement { + sid = "aws-read" + resources = ["*"] + actions = [ + "autoscaling:Describe*", + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricsStatistics", + "cloudwatch:Describe*", + "ec2:Describe*", + "elasticloadbalancing:Describe*", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:Describe*", + "logs:PutLogEvents", + "logs:PutMetricFilter" + ] + } +} + +resource "aws_iam_policy" "base" { + name = "base-policy" + path = "/" + description = "base-policy" + policy = "${data.aws_iam_policy_document.base.json}" +} + +resource "aws_security_group" "general-access" { + name = "general-access" + description = "Allow all ICMP and intra-vpc SSH traffic" + vpc_id = "${aws_vpc.default.id}" +} + +resource "aws_security_group_rule" "ga_out_all" { + security_group_id = "${aws_security_group.general-access.id}" + type = "egress" + from_port = 0 + to_port = 0 + protocol = "all" + cidr_blocks = ["0.0.0.0/0"] + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "ga_in_icmp" { + security_group_id = "${aws_security_group.general-access.id}" + type = "ingress" + from_port = -1 + to_port = -1 + protocol = "icmp" + cidr_blocks = ["0.0.0.0/0"] + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "ga_in_ssh" { + security_group_id = "${aws_security_group.general-access.id}" + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["${concat(list(var.cidr), var.ssh_allowed_cidr)}"] + lifecycle { + create_before_destroy = true + } +} diff --git a/infrastructure/modules/tf_aws_vpc/outputs.tf b/infrastructure/modules/tf_aws_vpc/outputs.tf new file mode 100644 index 0000000..ca68bbc --- /dev/null +++ b/infrastructure/modules/tf_aws_vpc/outputs.tf @@ -0,0 +1,35 @@ +output "vpc_id" { + value = "${aws_vpc.default.id}" +} + +output "vpc_name" { + value = "${var.project}-${var.environment}-vpc" +} + +output "public_subnets" { + value = "${aws_subnet.public.*.id}" +} + +output "private_subnets" { + value = "${aws_subnet.private.*.id}" +} + +output "nat_gateway_ips" { + value = "${aws_eip.nat.*.public_ip}" +} + +output "private_route_table_ids" { + value = "${aws_route_table.private.*.id}" +} + +output "public_route_table_id" { + value = "${aws_route_table.public.id}" +} + +output "base_policy_arn" { + value = "${aws_iam_policy.base.arn}" +} + +output "general_access_sg_id" { + value = "${aws_security_group.general-access.id}" +} diff --git a/infrastructure/modules/tf_aws_vpc/variables.tf b/infrastructure/modules/tf_aws_vpc/variables.tf new file mode 100644 index 0000000..0b30d3e --- /dev/null +++ b/infrastructure/modules/tf_aws_vpc/variables.tf @@ -0,0 +1,66 @@ +variable "project" { + description = "topmost classification name" +} + +variable "environment" { + description = "deployment environment of this vpc, e.g. prod, stage, dev" + default = "dev" +} + +variable "cidr" { + description = "cidr block for this vpc" +} + +variable "public_azs" { + type = "list" + description = "list of azs to use for public subnets in this vpc (full specification, such as us-east-1a)" + default = [] +} + +variable "private_azs" { + type = "list" + description = "list of azs to use for private subnets in this vpc (full specification, such as us-east-1a)" + default = [] +} + +variable "r53_domain_name" { + description = "domain name for everything in this vpc" + default = "" +} + +variable "enable_dns_hostnames" { + description = "should be true if you want to use private DNS within the VPC" + default = true +} + +variable "enable_dns_support" { + description = "should be true if you want to use private DNS within the VPC" + default = true +} + +variable "enable_domain_name" { + description = "configure dhcp option with r53_domain_name" + default = false +} + +variable "subnets_offset_public" { + description = "start numbering public subnets with this value" + default = 0 +} + +variable "subnets_offset_private" { + description = "start numbering private subnets with this value" + default = 128 +} + +variable "peering_connection_ids" { + type = "list" + description = "pcx ids of accepted vpc peerings" + default = [] +} + +variable "ssh_allowed_cidr" { + type = "list" + description = "list of additional cidr blocks to allow SSH traffic from" + default = [] +} \ No newline at end of file diff --git a/infrastructure/modules/tf_aws_vpc/version.tf b/infrastructure/modules/tf_aws_vpc/version.tf new file mode 100644 index 0000000..cc0789e --- /dev/null +++ b/infrastructure/modules/tf_aws_vpc/version.tf @@ -0,0 +1,4 @@ +terraform { + required_version = ">= 0.9.11" +} + diff --git a/infrastructure/modules/vpcaccess-stack/main.tf b/infrastructure/modules/vpcaccess-stack/main.tf new file mode 100644 index 0000000..30f4375 --- /dev/null +++ b/infrastructure/modules/vpcaccess-stack/main.tf @@ -0,0 +1,94 @@ +resource "aws_eip" "vpn" { + count = 1 + vpc = true +} + +resource "aws_security_group" "vpn" { + vpc_id = "${var.vpc_id}" + name = "${var.name}-vpn" + description = "Allow VPN traffic." +} + +resource "aws_security_group_rule" "vpn-out-all" { + security_group_id = "${aws_security_group.vpn.id}" + type = "egress" + from_port = 0 + to_port = 0 + protocol = "all" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "vpn-in-user" { + security_group_id = "${aws_security_group.vpn.id}" + type = "ingress" + from_port = 1195 + to_port = 1195 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "vpn-in-bridge" { + security_group_id = "${aws_security_group.vpn.id}" + type = "ingress" + from_port = 1194 + to_port = 1194 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "vpn-in-bastion" { + security_group_id = "${aws_security_group.vpn.id}" + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_elb" "default" { + count = "${var.vpcaccess_elb}" + name = "${var.name}-int-elb" + subnets = ["${var.subnet_ids}"] + internal = true + listener { + lb_port = 22 + lb_protocol = "tcp" + instance_port = 22 + instance_protocol = "tcp" + } + health_check { + healthy_threshold = 3 + unhealthy_threshold = 2 + interval = 30 + timeout = 5 + target = "TCP:1195" + } + idle_timeout = 600 + tags { + module = "${var.name}" + phase = "${var.environment}" + } +} + +module "asg-stack" { + source = "../modules/tf_aws_asg_stack" + vpc_id = "${var.vpc_id}" + acct_name = "${var.acct_name}" + notification_arns = ["${var.notification_arns}"] + module = "${var.name}" + phase = "${var.environment}" + instance_type = "${var.instance_type}" + key_name = "${var.key_name}" + public_ips = true + subnet_ids = ["${var.subnet_ids}"] + iam_policy_arns = ["${var.role_policy_arns}"] + security_group_ids = ["${concat(var.security_group_ids, list(aws_security_group.vpn.id))}"] + max_size = 1 + min_size = 0 + iam_allow_actions = [ + "ec2:AssociateAddress", + "ec2:ModifyInstanceAttribute", + "ec2:ModifyNetworkInterfaceAttribute" + ] + elbs = ["${var.vpcaccess_elb ? aws_elb.default.id : ""}"] +} diff --git a/infrastructure/modules/vpcaccess-stack/outputs.tf b/infrastructure/modules/vpcaccess-stack/outputs.tf new file mode 100644 index 0000000..5bd8ad7 --- /dev/null +++ b/infrastructure/modules/vpcaccess-stack/outputs.tf @@ -0,0 +1,2 @@ +output "vpn_eip_id" { value = "${aws_eip.vpn.id}" } +output "vpn_eip_ip" { value = "${aws_eip.vpn.public_ip}"} diff --git a/infrastructure/modules/vpcaccess-stack/variables.tf b/infrastructure/modules/vpcaccess-stack/variables.tf new file mode 100644 index 0000000..609ff9d --- /dev/null +++ b/infrastructure/modules/vpcaccess-stack/variables.tf @@ -0,0 +1,11 @@ +variable "vpc_id" {} +variable "name" { default = "vpcaccess" } +variable "subnet_ids" { type="list" } +variable "security_group_ids" { type="list" } +variable "role_policy_arns" { type="list" } +variable "key_name" {} +variable "environment" {} +variable "acct_name" {} +variable "notification_arns" { type="list"} +variable "instance_type" { default = "t2.small" } +varaible "vpcaccess_elb" { default = false } diff --git a/infrastructure/terraform-infrastructure/main.tf b/infrastructure/terraform-infrastructure/main.tf new file mode 100644 index 0000000..903f99b --- /dev/null +++ b/infrastructure/terraform-infrastructure/main.tf @@ -0,0 +1,10 @@ +provider "aws" { + region = "${var.region}" +} + +module "terraform-infrastructure" { + source = "../modules/terraform-infrastructure" + region = "${var.region}" + remote_state_bucket = "${var.remote_state_bucket}" + remote_state_table = "${var.remote_state_table}" +} diff --git a/infrastructure/terraform-infrastructure/outputs.tf b/infrastructure/terraform-infrastructure/outputs.tf new file mode 100644 index 0000000..95a89f0 --- /dev/null +++ b/infrastructure/terraform-infrastructure/outputs.tf @@ -0,0 +1,4 @@ +output "region" { value = "${var.region}" } +output "remote_state_bucket" { value = "${var.remote_state_bucket}" } +output "remote_state_table" { value = "${var.remote_state_table}" } +output "environment" { value = "${var.environment}" } diff --git a/infrastructure/terraform-infrastructure/terraform.tfvars b/infrastructure/terraform-infrastructure/terraform.tfvars new file mode 120000 index 0000000..ac9c2a0 --- /dev/null +++ b/infrastructure/terraform-infrastructure/terraform.tfvars @@ -0,0 +1 @@ +../terraform.tfvars \ No newline at end of file diff --git a/infrastructure/terraform-infrastructure/variables.tf b/infrastructure/terraform-infrastructure/variables.tf new file mode 100644 index 0000000..c6794b0 --- /dev/null +++ b/infrastructure/terraform-infrastructure/variables.tf @@ -0,0 +1,4 @@ +variable "region" {} +variable "remote_state_bucket" {} +variable "remote_state_table" {} +variable "environment" {} diff --git a/infrastructure/terraform.tfvars b/infrastructure/terraform.tfvars new file mode 100644 index 0000000..3ec9dbb --- /dev/null +++ b/infrastructure/terraform.tfvars @@ -0,0 +1,10 @@ +region = "us-east-1" +remote_state_bucket = "test-terraform-state" +remote_state_table = "terraform-state" +acct_name = "testAcct" +project = "test" +environment = "dev" +vpc_cidr = "10.83.0.0/16" +vpc_public_azs = ["us-east-1e"] +vpc_private_azs = ["us-east-1c"] +key_name = "somekey" diff --git a/infrastructure/vpc/main.tf b/infrastructure/vpc/main.tf new file mode 100644 index 0000000..87a1b5c --- /dev/null +++ b/infrastructure/vpc/main.tf @@ -0,0 +1,13 @@ +provider "aws" { + region = "${var.region}" +} + +module "vpc" { + source = "../modules/tf_aws_vpc" + project = "${var.project}" + environment = "${var.environment}" + cidr = "${var.vpc_cidr}" + public_azs = "${var.vpc_public_azs}" + private_azs = "${var.vpc_private_azs}" + ssh_allowed_cidr = "${var.ssh_allowed_cidr}" +} diff --git a/infrastructure/vpc/outputs.tf b/infrastructure/vpc/outputs.tf new file mode 100644 index 0000000..4f98b3c --- /dev/null +++ b/infrastructure/vpc/outputs.tf @@ -0,0 +1,23 @@ +output "vpc_id" { + value = "${module.vpc.vpc_id}" +} + +output "public_subnets" { + value = "${module.vpc.public_subnets}" +} + +output "private_subnets" { + value = "${module.vpc.private_subnets}" +} + +output "nat_gateway_ips" { + value = "${module.vpc.nat_gateway_ips}" +} + +output "base_policy_arn" { + value = "${module.vpc.base_policy_arn}" +} + +output "general_access_sg_id" { + value = "${module.vpc.general_access_sg_id}" +} diff --git a/infrastructure/vpc/terraform.tfvars b/infrastructure/vpc/terraform.tfvars new file mode 120000 index 0000000..ac9c2a0 --- /dev/null +++ b/infrastructure/vpc/terraform.tfvars @@ -0,0 +1 @@ +../terraform.tfvars \ No newline at end of file diff --git a/infrastructure/vpc/variables.tf b/infrastructure/vpc/variables.tf new file mode 100644 index 0000000..f8d5281 --- /dev/null +++ b/infrastructure/vpc/variables.tf @@ -0,0 +1,7 @@ +variable "region" {} +variable "project" {} +variable "environment" {} +variable "vpc_cidr" {} +variable "vpc_public_azs" {type="list"} +variable "vpc_private_azs" {type="list"} +variable "ssh_allowed_cidr" {type="list", default=[], description="List of extra cidrs to allow SSH traffic from"} diff --git a/infrastructure/vpcaccess-stack/data_management-stack.tf b/infrastructure/vpcaccess-stack/data_management-stack.tf new file mode 100644 index 0000000..9e75357 --- /dev/null +++ b/infrastructure/vpcaccess-stack/data_management-stack.tf @@ -0,0 +1,9 @@ +data "terraform_remote_state" "management" { + backend = "s3" + config { + bucket = "${var.remote_state_bucket}" + region = "${var.region}" + key = "${var.environment}/management-stack.tfstate" + dynamodb_table = "${var.remote_state_table}" + } +} diff --git a/infrastructure/vpcaccess-stack/data_vpc.tf b/infrastructure/vpcaccess-stack/data_vpc.tf new file mode 100644 index 0000000..12f8be7 --- /dev/null +++ b/infrastructure/vpcaccess-stack/data_vpc.tf @@ -0,0 +1,9 @@ +data "terraform_remote_state" "vpc" { + backend = "s3" + config { + bucket = "${var.remote_state_bucket}" + region = "${var.region}" + key = "${var.environment}/vpc.tfstate" + dynamodb_table = "${var.remote_state_table}" + } +} diff --git a/infrastructure/vpcaccess-stack/main.tf b/infrastructure/vpcaccess-stack/main.tf new file mode 100644 index 0000000..df66995 --- /dev/null +++ b/infrastructure/vpcaccess-stack/main.tf @@ -0,0 +1,15 @@ +provider "aws" { + region = "${var.region}" +} + +module "vpcaccess-stack" { + source = "../modules/vpcaccess-stack" + vpc_id = "${data.terraform_remote_state.vpc.vpc_id}" + subnet_ids = "${data.terraform_remote_state.vpc.public_subnets}" + security_group_ids = ["${data.terraform_remote_state.vpc.general_access_sg_id}"] + role_policy_arns = ["${data.terraform_remote_state.vpc.role_policy_arn}"] + key_name = "${var.key_name}" + environment = "${var.environment}" + acct_name = "${var.acct_name}" + notification_arns = ["${data.terraform_remote_state.management.events_topic_arn}"] +} diff --git a/infrastructure/vpcaccess-stack/outputs.tf b/infrastructure/vpcaccess-stack/outputs.tf new file mode 100644 index 0000000..e94880f --- /dev/null +++ b/infrastructure/vpcaccess-stack/outputs.tf @@ -0,0 +1 @@ +output "vpn_eip_ip" { value = "${module.vpcaccess-stack.vpn_eip_ip}"} diff --git a/infrastructure/vpcaccess-stack/variables.tf b/infrastructure/vpcaccess-stack/variables.tf new file mode 100644 index 0000000..851f8fe --- /dev/null +++ b/infrastructure/vpcaccess-stack/variables.tf @@ -0,0 +1,6 @@ +variable "region" {} +variable "environment" {} +variable "remote_state_bucket" {} +variable "remote_state_table" {} +variable "acct_name" {} +variable "key_name" {}