From ddf83c628c2b359a93cdc7215e0a17779242d6d1 Mon Sep 17 00:00:00 2001 From: J Cole Morrison Date: Thu, 9 Apr 2020 19:37:14 -0700 Subject: [PATCH] initial commit --- .gitignore | 6 ++ aws-data.tf | 3 + dynamodb.tf | 23 +++++++ iam-policies.tf | 77 +++++++++++++++++++++ iam-roles.tf | 29 ++++++++ kms.tf | 16 +++++ main.tf | 0 providers.tf | 4 ++ variables.tf | 92 +++++++++++++++++++++++++ vpc.tf | 174 ++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 424 insertions(+) create mode 100644 .gitignore create mode 100644 aws-data.tf create mode 100644 dynamodb.tf create mode 100644 iam-policies.tf create mode 100644 iam-roles.tf create mode 100644 kms.tf create mode 100644 main.tf create mode 100644 providers.tf create mode 100644 variables.tf create mode 100644 vpc.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a807ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.terraform +terraform.tfstate +terraform.tfstate.backup +terraform.tfvars +todos.md +.DS_Store \ No newline at end of file diff --git a/aws-data.tf b/aws-data.tf new file mode 100644 index 0000000..7284d68 --- /dev/null +++ b/aws-data.tf @@ -0,0 +1,3 @@ +data "aws_availability_zones" "available" { + state = "available" +} \ No newline at end of file diff --git a/dynamodb.tf b/dynamodb.tf new file mode 100644 index 0000000..d72fda4 --- /dev/null +++ b/dynamodb.tf @@ -0,0 +1,23 @@ +# DynamoDB + +resource "aws_dynamodb_table" "vault_storage" { + name = var.dynamodb_table_name + billing_mode = "PAY_PER_REQUEST" + hash_key = "Path" + range_key = "Key" + + attribute { + name = "Path" + type = "S" + } + + attribute { + name = "Key" + type = "S" + } + + tags = { + Name = var.dynamodb_table_name + Project = var.main_project_tag + } +} \ No newline at end of file diff --git a/iam-policies.tf b/iam-policies.tf new file mode 100644 index 0000000..5c294c4 --- /dev/null +++ b/iam-policies.tf @@ -0,0 +1,77 @@ +# IAM Policies + +## KMS Policy +data "aws_iam_policy_document" "kms_vault_policy" { + statement { + sid = "EncryptDecryptAndDescribe" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:Encrypt", + "kms:DescribeKey" + ] + resources = [ + aws_kms_key.seal.arn + ] + } +} + +## DynamoDB Policy +data "aws_iam_policy_document" "dynamodb_vault_policy" { + statement { + sid = "ManageTable" + effect = "Allow" + actions = [ + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:DescribeTable", + "dynamodb:DeleteItem", + "dynamodb:GetItem", + "dynamodb:ListTagsOfResource", + "dynamodb:UpdateItem", + "dynamodb:DescribeTimeToLive" + ] + resources = [ + aws_dynamodb_table.vault_storage.arn + ] + } + + statement { + sid = "GetStreamRecords" + effect = "Allow" + actions = [ + "dynamodb:GetRecords" + ] + resources = [ + "${aws_dynamodb_table.vault_storage.arn}/stream/*" + ] + } + + statement { + sid = "QueryAndScanTable" + effect = "Allow" + actions = [ + "dynamodb:Scan", + "dynamodb:Query" + ] + resources = [ + "${aws_dynamodb_table.vault_storage.arn}/index/*", + aws_dynamodb_table.vault_storage.arn + ] + } +} + +## AutoScalingGroup Instance Trust Policy +data "aws_iam_policy_document" "asg_trust_policy" { + statement { + effect = "Allow" + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + actions = [ + "sts:AssumeRole" + ] + } +} diff --git a/iam-roles.tf b/iam-roles.tf new file mode 100644 index 0000000..fd220d8 --- /dev/null +++ b/iam-roles.tf @@ -0,0 +1,29 @@ +# IAM Roles + +## Role for Vault EC2 Instances via AutoScalingGroup + +resource "aws_iam_role" "vault_instance" { + name_prefix = "${var.main_project_tag}-instance-role-" + assume_role_policy = data.aws_iam_policy_document.asg_trust_policy.json +} + +## Policy Attachments + +resource "aws_iam_role_policy" "vault_instance_kms_policy" { + name_prefix = "${var.main_project_tag}-instance-kms-policy-" + role = aws_iam_role.vault_instance.id + policy = data.aws_iam_policy_document.kms_vault_policy.json +} + +resource "aws_iam_role_policy" "vault_instance_dynamodb_policy" { + name_prefix = "${var.main_project_tag}-instance-dynamodb-policy-" + role = aws_iam_role.vault_instance.id + policy = data.aws_iam_policy_document.dynamodb_vault_policy.json +} + +## Instance Profile + +resource "aws_iam_instance_profile" "vault_instance_profile" { + name_prefix = "${var.main_project_tag}-instance-profile-" + role = aws_iam_role.vault_instance.name +} \ No newline at end of file diff --git a/kms.tf b/kms.tf new file mode 100644 index 0000000..6d6cbc0 --- /dev/null +++ b/kms.tf @@ -0,0 +1,16 @@ +# AWS KMS Key +resource "aws_kms_key" "seal" { + description = "The KMS key to unseal Vault." + enable_key_rotation = true + + tags = merge( + { "Name" = "${var.main_project_tag}-seal-key" }, + { "Project" = var.main_project_tag }, + var.kms_tags + ) +} + +resource "aws_kms_alias" "seal" { + name = "alias/${var.main_project_tag}-seal-key" + target_key_id = aws_kms_key.seal.key_id +} \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/providers.tf b/providers.tf new file mode 100644 index 0000000..52fa9e7 --- /dev/null +++ b/providers.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = var.aws_default_region + profile = var.aws_profile +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..0eb4630 --- /dev/null +++ b/variables.tf @@ -0,0 +1,92 @@ +# Organization + +variable "main_project_tag" { + description = "Tag that will be attached to all resources." + type = string + default = "vault-deployment" +} + +# AWS Provider + +variable "aws_profile" { + description = "The AWS Profile to use for this project." + type = string + default = "default" +} + +variable "aws_default_region" { + description = "The default region to deploy this." + type = string + default = "us-east-1" +} + +# AWS VPC + +variable "vpc_cidr" { + description = "Cidr block for the VPC. Using a /16 or /20 Subnet Mask is recommended." + type = string + default = "10.255.0.0/20" +} + +variable "vpc_instance_tenancy" { + description = "Tenancy for instances launched into the VPC" + type = string + default = "default" +} + +variable "vpc_enable_dns_support" { + description = "Whether the DNS resolution is supported." + type = bool + default = true +} + +variable "vpc_enable_dns_hostnames" { + description = "Whether instances with public IP addresses get corresponding public DNS hostnames." + type = bool + default = true +} + +variable "vpc_tags" { + description = "Additional tags to add to the VPC and its resources." + type = map(string) + default = {} +} + +# VPC Subnets + +variable "vpc_public_subnet_count" { + description = "The number of public subnets to create. Cannot exceed the number of AZs in your selected region. 2 is more than enough." + type = number + default = 2 +} + +variable "vpc_private_subnet_count" { + description = "The number of private subnets to create. Cannot exceed the number of AZs in your selected region." + type = number + default = 2 +} + +# KMS + +variable "kms_tags" { + description = "Tags for the KMS key used to seal and unseal the Vault." + type = map(string) + default = {} +} + +# DynamoDB + +variable "dynamodb_table_name" { + description = "Name of the DynamoDB Table used for the Vault Storage Backend." + type = string + default = "vault_storage" +} + +# Operator Mode +## Turning this on will enable NAT and Bastion to access the Vault Instances + +variable "operator_mode" { + description = "Enable a NAT Gateway and Bastion for operator access into the Vault Instances." + type = bool + default = true +} \ No newline at end of file diff --git a/vpc.tf b/vpc.tf new file mode 100644 index 0000000..bebb9d3 --- /dev/null +++ b/vpc.tf @@ -0,0 +1,174 @@ +# VPC +resource "aws_vpc" "vault" { + cidr_block = var.vpc_cidr + instance_tenancy = var.vpc_instance_tenancy + enable_dns_support = var.vpc_enable_dns_support + enable_dns_hostnames = var.vpc_enable_dns_hostnames + + tags = merge( + { "Name" = "${var.main_project_tag}-vpc" }, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) +} + + + + + + + + + + +# Gateways + +## Internet Gateway +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.vault.id + + tags = merge( + { "Name" = "${var.main_project_tag}-igw"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) +} + +## NAT Gateway + +#### The NAT Elastic IP +resource "aws_eip" "nat" { + count = var.operator_mode ? 1 : 0 + + vpc = true + + tags = merge( + { "Name" = "${var.main_project_tag}-nat-eip"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) + + depends_on = [aws_internet_gateway.igw] +} + +#### The NAT Gateway +resource "aws_nat_gateway" "nat" { + count = var.operator_mode ? 1 : 0 + + allocation_id = aws_eip.nat[0].id + subnet_id = aws_subnet.public.0.id + + tags = merge( + { "Name" = "${var.main_project_tag}-nat"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) + + depends_on = [ + aws_internet_gateway.igw, + aws_eip.nat + ] +} + + + + + + + + + + +# Route Tables +# NOTE: Routing to the VPC's CIDR is allowed by default, so no route is needed + +## Public Route Table +resource "aws_route_table" "public" { + vpc_id = aws_vpc.vault.id + tags = merge( + { "Name" = "${var.main_project_tag}-public-rtb"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) +} + +#### Public routes +resource "aws_route" "public_internet_access" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id +} + +## Private Route Table +resource "aws_route_table" "private" { + vpc_id = aws_vpc.vault.id + tags = merge( + { "Name" = "${var.main_project_tag}-private-rtb"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) +} + +#### Private Routes +resource "aws_route" "private_internet_access" { + count = var.operator_mode ? 1 : 0 + + route_table_id = aws_route_table.private.id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.nat[0].id +} + + + + + + + + + + +# Subnets + +## Public Subnets +resource "aws_subnet" "public" { + count = var.vpc_public_subnet_count + + vpc_id = aws_vpc.vault.id + cidr_block = cidrsubnet(aws_vpc.vault.cidr_block, 4, count.index) + availability_zone = data.aws_availability_zones.available.names[count.index] + map_public_ip_on_launch = true + + tags = merge( + { "Name" = "${var.main_project_tag}-public-${data.aws_availability_zones.available.names[count.index]}"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) +} + +## Private Subnets +resource "aws_subnet" "private" { + count = var.vpc_private_subnet_count + + vpc_id = aws_vpc.vault.id + + # Increment the netnum by the number of public subnets to avoid overlap + cidr_block = cidrsubnet(aws_vpc.vault.cidr_block, 4, count.index + var.vpc_public_subnet_count) + availability_zone = data.aws_availability_zones.available.names[count.index] + + tags = merge( + { "Name" = "${var.main_project_tag}-private-${data.aws_availability_zones.available.names[count.index]}"}, + { "Project" = var.main_project_tag }, + var.vpc_tags + ) +} + + + + + + + + + +# VPC Endpoints +# Make safe calls to KMS and DynamoDB without leaving the VPC.