diff --git a/modules/compute/ec2_default_tags/README.md b/modules/compute/ec2_default_tags/README.md new file mode 100644 index 0000000..ce21816 --- /dev/null +++ b/modules/compute/ec2_default_tags/README.md @@ -0,0 +1,45 @@ +# ec2 module +This module deploys EC2 instance. + +# Input +Below is a sample config in the root module, which shows all of the inputs +``` +module "deployer-ec2" { + source = "../../../../whk1-bea-sys-ss-dev-codecommit-sharedmodules/Compute/ec2" + + ami-id = data.aws_ami.al2-ami.id + asso-eip = false + asso-public-ip = false + default-tags = local.default_tags + ebs-encrypted = true + instance-name = "whk1-bea-sys-ss-${var.environment}-test" + instance-type = "t3.micro" + key-name = aws_key_pair.deployer-sshkey.key_name + kms-key-id = var.kms-key-arn + root-volume-size = "15" + security-groups = [aws_security_group.deployer-sg.id] + subnet-id = var.subnet-id + instance-profile = "example-instanec-profile" + additional_tags = { + "AwsBackup" : "Daily14" + "ssm-patching" : "group1" + } + data-volumes = { + volume1 = { + size : "10" + type : "gp3" + } + } +} +``` + +# Outputs +| Name | Value | +| - | - | +| instance-id | Instance ID | +| private-ip | Private IP of instance | + +# Limitation +Up to 26 data volumes can be attached to the ec2 instance. To attach even more volumes, please do it in +your root module + diff --git a/modules/compute/ec2_default_tags/main.tf b/modules/compute/ec2_default_tags/main.tf new file mode 100644 index 0000000..e65ebe9 --- /dev/null +++ b/modules/compute/ec2_default_tags/main.tf @@ -0,0 +1,79 @@ +resource "aws_instance" "ec2-instance" { + ami = var.ami-id + instance_type = var.instance-type + associate_public_ip_address = var.asso-public-ip + // availability_zone = var.az + iam_instance_profile = var.instance-profile + key_name = var.key-name + private_ip = var.private-ip + root_block_device { + encrypted = var.ebs-encrypted + volume_size = var.root-volume-size + volume_type = var.root-volume-type + kms_key_id = var.kms-key-id + delete_on_termination = var.delete-on-termination + } + ebs_optimized = true + subnet_id = var.subnet-id + vpc_security_group_ids = var.security-groups + + # IMDSv2 requirement + dynamic "metadata_options" { + for_each = var.disable_secure_idmsv2 == false ? { set_idmsv2 : true } : {} + content { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + } + } + + disable_api_termination = var.enable-termination-protection + user_data = var.user-data + monitoring = var.enable-detail-monitoring + + tags = merge(var.additional-tags, { "Name" : var.instance-name }) + volume_tags = merge({ "Name" : var.instance-name }, data.aws_default_tags.this.tags) + + # do not redeploy instance when a new ami is released + lifecycle { + ignore_changes = [ami] + } +} + +resource "aws_ebs_volume" "data-volumes" { + for_each = var.data-volumes + availability_zone = aws_instance.ec2-instance.availability_zone + size = each.value["size"] + type = each.value["type"] + kms_key_id = aws_instance.ec2-instance.root_block_device[0].kms_key_id + encrypted = aws_instance.ec2-instance.root_block_device[0].encrypted +} + +locals { + a_to_z = split(",", "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z") +} + + +resource "aws_volume_attachment" "data-volume-attachments" { + count = length(aws_ebs_volume.data-volumes) + # for_each = aws_ebs_volume.data-volumes.id + volume_id = [for v in aws_ebs_volume.data-volumes : v.id][count.index] + instance_id = aws_instance.ec2-instance.id + device_name = "/dev/xvda${element(local.a_to_z, count.index)}" +} + + +resource "aws_eip" "ec2-eip" { + count = var.asso-eip ? 1 : 0 + instance = aws_instance.ec2-instance.id + domain = "vpc" +} + +data "aws_default_tags" "this" { + lifecycle { + postcondition { + condition = length(self.tags) >= 1 + error_message = "Validation failed: Provider default_tags not set." + } + } +} \ No newline at end of file diff --git a/modules/compute/ec2_default_tags/outputs.tf b/modules/compute/ec2_default_tags/outputs.tf new file mode 100644 index 0000000..8c6a9aa --- /dev/null +++ b/modules/compute/ec2_default_tags/outputs.tf @@ -0,0 +1,13 @@ +output ec2-id-ip { + value = { + instance-id = aws_instance.ec2-instance.id + private-ip = aws_instance.ec2-instance.private_ip + } +} +output instance-id { + value = aws_instance.ec2-instance.id +} + +output private-ip { + value = aws_instance.ec2-instance.private_ip +} \ No newline at end of file diff --git a/modules/compute/ec2_default_tags/provider.tf b/modules/compute/ec2_default_tags/provider.tf new file mode 100644 index 0000000..29409e2 --- /dev/null +++ b/modules/compute/ec2_default_tags/provider.tf @@ -0,0 +1,10 @@ +# aws plugin 5.0.0 or above is required to support domain attribute in aws_eip +terraform { + required_version = ">= 1.3.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0.0" + } + } +} diff --git a/modules/compute/ec2_default_tags/variable.tf b/modules/compute/ec2_default_tags/variable.tf new file mode 100644 index 0000000..eb62fe2 --- /dev/null +++ b/modules/compute/ec2_default_tags/variable.tf @@ -0,0 +1,53 @@ +variable "instance-type" {} +variable "ami-id" {} +variable "asso-public-ip" {} +// variable az {} +variable "instance-profile" { + type = string + default = "" +} +variable "key-name" {} +variable "ebs-encrypted" {} +variable "root-volume-size" {} +variable "root-volume-type" { + type = string + default = "gp3" +} +variable "kms-key-id" {} +variable "delete-on-termination" { + type = bool + default = true +} +variable "subnet-id" {} +variable "security-groups" { + type = list(any) +} +variable "instance-name" {} +variable "asso-eip" { + type = bool +} +variable "data-volumes" {} +variable "private-ip" { + type = string + default = null +} +variable "additional-tags" {} +variable "disable_secure_idmsv2" { + type = bool + default = false +} + +variable "enable-termination-protection" { + type = bool + default = false +} + +variable "user-data" { + type = string + default = "" +} + +variable "enable-detail-monitoring" { + type = bool + default = false +} \ No newline at end of file diff --git a/modules/compute/sg_default_tags/README.md b/modules/compute/sg_default_tags/README.md new file mode 100644 index 0000000..2164266 --- /dev/null +++ b/modules/compute/sg_default_tags/README.md @@ -0,0 +1,43 @@ +# security-groups-gen2 +This module create security groups from a map + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:-----:| +| tags | tags | List | n/a | yes | +| vpc-id | VPC id | string | n/a | yes | +| security-groups | See example below | map | n/a | yes | + +### security-groups input +Below is a sample security-groups map this module ingests. The rule list needs to have +the id column to prevent list from being randomly sorted. + +```hcl +module "endpoint_sg" { + source = "../../../../whk1-bea-sys-ss-prd-codecommit-sharedmodules/Compute/sg_default_tags" + name = "vwhk1-bea-sys-ss-prd-vpc01-ep-sg" + description = "vpc endpoint security group" + egress-sg-rules = { + name = "outbound access" + rules = [ + [1, "-1", "0.0.0.0/0", "0", "0", "outbound access"] + ] + } + + ingress-sg-rules = { + name = "HttpsAccessToVpcEndpoints" + rules = [ + [1, "tcp", data.aws_vpc.vpc1.cidr_block, "443", "443", "TLS from VPC"] + ] + } + vpc-id = var.vpc-id +} +``` + +## Outputs + +| Name | Description | +|------|-------------| +| sg-id-name | A map of SG id and their names | + diff --git a/modules/compute/sg_default_tags/main.tf b/modules/compute/sg_default_tags/main.tf new file mode 100644 index 0000000..4e2a3a7 --- /dev/null +++ b/modules/compute/sg_default_tags/main.tf @@ -0,0 +1,71 @@ +data "aws_default_tags" "this" { + lifecycle { + postcondition { + condition = length(self.tags) >= 1 + error_message = "Validation failed: Provider default_tags not set." + } + } +} + +resource "aws_security_group" "sg" { + name = var.name + description = var.description + vpc_id = var.vpc-id +} + +// see https://www.terraform.io/docs/configuration/functions/flatten.html + +locals { + ingress_rules = flatten([ + for rule_key, rule in var.ingress-sg-rules.rules : { + sg_key = "ingress" + rule_key = rule[0] + protocol = rule[1] + cidr_blocks = rule[2] + from_port = rule[3] + to_port = rule[4] + description = rule[5] + } + ]) + + egress_rules = flatten([ + for rule_key, rule in var.egress-sg-rules.rules : { + sg_key = "egress" + rule_key = rule[0] + protocol = rule[1] + cidr_blocks = rule[2] + from_port = rule[3] + to_port = rule[4] + description = rule[5] + } + ]) + +} + +resource "aws_vpc_security_group_egress_rule" "egress_rules" { + for_each = { + for rule in local.egress_rules : "${rule.sg_key}.${rule.rule_key}" => rule + } + + security_group_id = aws_security_group.sg.id + ip_protocol = each.value.protocol + referenced_security_group_id = substr(each.value.cidr_blocks, 0, 2) == "sg" ? each.value.cidr_blocks : null + cidr_ipv4 = substr(each.value.cidr_blocks, 0, 2) != "sg" ? each.value.cidr_blocks : null + from_port = each.value.protocol == "-1" ? null : each.value.from_port + to_port = each.value.protocol == "-1" ? null : each.value.to_port + description = each.value.description +} + +resource "aws_vpc_security_group_ingress_rule" "ingress_rules" { + for_each = { + for rule in local.ingress_rules : "${rule.sg_key}.${rule.rule_key}" => rule + } + + security_group_id = aws_security_group.sg.id + ip_protocol = each.value.protocol + referenced_security_group_id = substr(each.value.cidr_blocks, 0, 2) == "sg" ? each.value.cidr_blocks : null + cidr_ipv4 = substr(each.value.cidr_blocks, 0, 2) != "sg" ? each.value.cidr_blocks : null + from_port = each.value.protocol == "-1" ? null : each.value.from_port + to_port = each.value.protocol == "-1" ? null : each.value.to_port + description = each.value.description +} diff --git a/modules/compute/sg_default_tags/outputs.tf b/modules/compute/sg_default_tags/outputs.tf new file mode 100644 index 0000000..cf52a14 --- /dev/null +++ b/modules/compute/sg_default_tags/outputs.tf @@ -0,0 +1,14 @@ +/* +output sg-id-name { + value = [ + for id, name in zipmap( + sort(aws_security_group.sg.*.id), + sort(aws_security_group.sg.*.name)) : + tomap(id, name) + ] +} +*/ + +output sg-ids { + value = aws_security_group.sg.*.id +} \ No newline at end of file diff --git a/modules/compute/sg_default_tags/variables.tf b/modules/compute/sg_default_tags/variables.tf new file mode 100644 index 0000000..acd469d --- /dev/null +++ b/modules/compute/sg_default_tags/variables.tf @@ -0,0 +1,6 @@ +# variable security-groups {} +variable vpc-id {} +variable ingress-sg-rules {} +variable egress-sg-rules {} +variable name {} +variable description {} \ No newline at end of file