From 3e3ad0d7b15e773159267d3fad257a3b01ac3e9f Mon Sep 17 00:00:00 2001 From: Magicloud <1886157+Magicloud@users.noreply.github.com> Date: Fri, 3 Apr 2020 08:20:02 +0800 Subject: [PATCH 1/8] Support multiple data volumes in single-node-asg module. Tested in examples/single-node-asg-tester The change in all three modules is compatible with old code that defines just one data volume. --- examples/single-node-asg-tester/main.tf | 49 ++++++++++++++ .../instance_id.tpl | 10 +++ .../init-snippet-attach-ebs-volume/main.tf | 67 +++++-------------- .../snippet.tpl | 37 +++------- .../variables.tf | 63 +++++++++++++++++ modules/persistent-ebs/data.tf | 6 +- modules/persistent-ebs/locals.tf | 20 ++++++ modules/persistent-ebs/main.tf | 16 +++-- modules/persistent-ebs/outputs.tf | 4 +- modules/persistent-ebs/variables.tf | 19 ++++++ modules/single-node-asg/main.tf | 50 ++++++++++---- modules/single-node-asg/outputs.tf | 4 +- modules/single-node-asg/variables.tf | 18 +++-- 13 files changed, 254 insertions(+), 109 deletions(-) create mode 100644 examples/single-node-asg-tester/main.tf create mode 100644 modules/init-snippet-attach-ebs-volume/instance_id.tpl create mode 100644 modules/init-snippet-attach-ebs-volume/variables.tf create mode 100644 modules/persistent-ebs/locals.tf diff --git a/examples/single-node-asg-tester/main.tf b/examples/single-node-asg-tester/main.tf new file mode 100644 index 00000000..336dfb64 --- /dev/null +++ b/examples/single-node-asg-tester/main.tf @@ -0,0 +1,49 @@ +provider "aws" { + region = "ap-northeast-1" +} + +data "aws_availability_zones" "azs" {} + +module "vpc" { + source = "fpco/foundation/aws//modules/vpc-scenario-2" + cidr = "192.168.0.0/16" + public_subnet_cidrs = ["192.168.0.0/24", "192.168.1.0/24"] + private_subnet_cidrs = ["192.168.100.0/24", "192.168.101.0/24"] + azs = data.aws_availability_zones.azs.names + name_prefix = "ebs-test" + region = "ap-northeast-1" +} + +module "ubuntu" { + source = "fpco/foundation/aws//modules/ami-ubuntu" +} + +resource "aws_security_group" "ssh" { + vpc_id = module.vpc.vpc_id + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +module "tester" { + source = "../../modules/single-node-asg" + name_prefix = "ebs" + name_suffix = "test" + key_name = "tokyo" + ami = module.ubuntu.id + instance_type = "t2.micro" + subnet_id = module.vpc.public_subnet_ids[0] + security_group_ids = [aws_security_group.ssh.id] + region = "ap-northeast-1" + compatible_with_single_volume = false + data_volumes = [{ name = "a", device = "/dev/xvdm", size = 50 }, { name = "b", device = "/dev/xvdn" }] +} diff --git a/modules/init-snippet-attach-ebs-volume/instance_id.tpl b/modules/init-snippet-attach-ebs-volume/instance_id.tpl new file mode 100644 index 00000000..0e3e8663 --- /dev/null +++ b/modules/init-snippet-attach-ebs-volume/instance_id.tpl @@ -0,0 +1,10 @@ +if which wget; then + INSTANCE_ID="$(wget -O- http://169.254.169.254/latest/meta-data/instance-id)" +elif which curl; then + INSTANCE_ID="$(curl http://169.254.169.254/latest/meta-data/instance-id)" +fi + +if [ "x$${INSTANCE_ID}" == "x" ]; then + echo 'There is no wget or curl tool installed. Hence bootstrap cannot get instance ID.' + exit 1 +fi diff --git a/modules/init-snippet-attach-ebs-volume/main.tf b/modules/init-snippet-attach-ebs-volume/main.tf index 71d16726..3f9d8d17 100644 --- a/modules/init-snippet-attach-ebs-volume/main.tf +++ b/modules/init-snippet-attach-ebs-volume/main.tf @@ -9,70 +9,37 @@ * */ -# variables used by this snippet of init shellcode -variable "device_path" { - default = "/dev/xvdf" - description = "path, to the device's path in /dev/" - type = string -} - -variable "init_prefix" { - default = "" - description = "initial init (shellcode) to prefix this snippet with" - type = string -} - -variable "init_suffix" { - default = "" - description = "init (shellcode) to append to the end of this snippet" - type = string -} - -variable "log_level" { - default = "info" - description = "default log level verbosity for apps that support it" - type = string -} - -variable "log_prefix" { - default = "OPS: " - description = "string to prefix log messages with" - type = string -} - -variable "region" { - description = "AWS region the volume is in" - type = string -} - -variable "wait_interval" { - default = "5" - description = "time (in seconds) to wait when looping to find the device" - type = number -} - -variable "volume_id" { - description = "ID of the EBS volume to attach" - type = string +locals { + device_paths = var.compatible_with_single_volume ? [var.device_path] : var.device_paths + volume_ids = var.compatible_with_single_volume ? [var.volume_id] : var.volume_ids } # render init script for a cluster using our generic template data "template_file" "init_snippet" { + count = length(local.volume_ids) + template = file("${path.module}/snippet.tpl") vars = { - device_path = var.device_path - init_prefix = var.init_prefix - init_suffix = var.init_suffix + device_path = local.device_paths[count.index] log_prefix = var.log_prefix log_level = var.log_level region = var.region - volume_id = var.volume_id + volume_id = local.volume_ids[count.index] wait_interval = var.wait_interval } } +data "template_file" "instance_id" { + template = file("${path.module}/instance_id.tpl") +} + output "init_snippet" { - value = data.template_file.init_snippet.rendered + value = < /tmp/init.log -# exec 2> /tmp/init-err.log +exec 2>&1 # set -x +apt update +${module.install-awscli.init_snippet} ${var.init_prefix} ${module.init-attach-ebs.init_snippet} ${var.init_suffix} @@ -91,7 +107,13 @@ END_INIT # Render init snippet - boxed module to attach the EBS volume to the node module "init-attach-ebs" { - source = "../init-snippet-attach-ebs-volume" - region = var.region - volume_id = module.service-data.volume_id + source = "../init-snippet-attach-ebs-volume" + region = var.region + volume_ids = module.service-data.volume_ids + device_paths = [for x in local.data_volumes_default : x.device] + compatible_with_single_volume = false +} + +module "install-awscli" { + source = "../init-snippet-install-awscli" } diff --git a/modules/single-node-asg/outputs.tf b/modules/single-node-asg/outputs.tf index d2bbbbdc..29b2030c 100644 --- a/modules/single-node-asg/outputs.tf +++ b/modules/single-node-asg/outputs.tf @@ -9,6 +9,6 @@ output "asg_iam_role_name" { } output "data_volume_name_tag" { - value = "${local.data_volume_name_prefix}-${local.az}" - description = "Name tag value for attached data volume" + value = "${local.name_prefix_with_az}-default" + description = "Name tag value for attached data volume. This is for compatible with old code." } diff --git a/modules/single-node-asg/variables.tf b/modules/single-node-asg/variables.tf index a5ea120c..23b66a88 100644 --- a/modules/single-node-asg/variables.tf +++ b/modules/single-node-asg/variables.tf @@ -36,9 +36,9 @@ variable "root_volume_type" { } variable "root_volume_size" { - default = "8" + default = 8 description = "Size (in GB) of EBS volume to use for the root block device" - type = string + type = number } variable "data_volume_type" { @@ -48,9 +48,9 @@ variable "data_volume_type" { } variable "data_volume_size" { - default = "10" + default = 10 description = "Size (in GB) of EBS volume to use for the EBS volume" - type = string + type = number } variable "data_volume_encrypted" { @@ -115,3 +115,13 @@ variable "load_balancers" { description = "The list of load balancers names to pass to the ASG module" type = list(string) } + +variable "data_volumes" { + type = list(map(any)) + description = "Definition of the data volumes. `name` and `device` are required." +} + +variable "compatible_with_single_volume" { + default = true + description = "Using variables for single volumes or not." +} From 8e9e53e907b5b283e9b90184007dd727bd6a3d55 Mon Sep 17 00:00:00 2001 From: Magicloud <1886157+Magicloud@users.noreply.github.com> Date: Tue, 10 Dec 2019 19:03:41 +0800 Subject: [PATCH 2/8] New function: single-node-asg module supports binding EIP by itself. Since it is single node, binding an EIP to the instance is possible. And it eases other things since the public interface is constant. Add assign_eip variable to single-node-asg. If turns it on, an EIP will be allocated, and assocated with the instance. Scope VPC is specified in case the account does not have a default VPC. --- modules/single-node-asg/main.tf | 42 +++++++++++++++++++++++++--- modules/single-node-asg/outputs.tf | 4 +++ modules/single-node-asg/variables.tf | 10 +++++-- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/modules/single-node-asg/main.tf b/modules/single-node-asg/main.tf index d99b37ae..b7d7f6f4 100644 --- a/modules/single-node-asg/main.tf +++ b/modules/single-node-asg/main.tf @@ -52,6 +52,32 @@ module "service-data" { iam_instance_profile_role_name = module.instance_profile.iam_role_name } +resource "aws_eip" "eip" { + count = var.assign_eip ? 1 : 0 + vpc = true +} + +resource "aws_iam_role_policy_attachment" "associate_eip" { + role = module.instance_profile.iam_role_name + policy_arn = aws_iam_policy.associate_eip_policy.arn +} + +resource "aws_iam_policy" "associate_eip_policy" { + name = "associate_address" + policy = data.aws_iam_policy_document.associate_eip_policy_doc.json +} + +data "aws_iam_policy_document" "associate_eip_policy_doc" { + statement { + sid = "" + effect = "Allow" + actions = [ + "ec2:AssociateAddress" + ] + resources = ["*"] + } +} + # Create an ASG with just 1 EC2 instance module "server" { source = "../asg" @@ -66,12 +92,11 @@ module "server" { max_nodes = 1 min_nodes = 1 placement_group = var.placement_group - public_ip = var.public_ip + public_ip = var.assign_eip ? false : var.public_ip # the prefix and suffix names are combined in # the `asg` module to create the full name - name_prefix = var.name_prefix - name_suffix = "${var.name_suffix}-${local.az}" - + name_prefix = var.name_prefix + name_suffix = "${var.name_suffix}-${local.az}" root_volume_type = var.root_volume_type root_volume_size = var.root_volume_size security_group_ids = var.security_group_ids @@ -84,7 +109,12 @@ module "server" { # exec > /tmp/init.log # exec 2> /tmp/init-err.log # set -x +apt update ${var.init_prefix} +${module.init-install-awscli.init_snippet} +while ! ${var.assign_eip ? "aws ec2 associate-address --instance-id \"$(ec2metadata --instance-id)\" --region \"${var.region}\" --allocation-id \"${element(aws_eip.eip.*.id, 0)}\"" : "true"}; do + sleep 1 +done ${module.init-attach-ebs.init_snippet} ${var.init_suffix} END_INIT @@ -97,3 +127,7 @@ module "init-attach-ebs" { region = var.region volume_id = module.service-data.volume_id } + +module "init-install-awscli" { + source = "../init-snippet-install-awscli" +} diff --git a/modules/single-node-asg/outputs.tf b/modules/single-node-asg/outputs.tf index d2bbbbdc..79106a09 100644 --- a/modules/single-node-asg/outputs.tf +++ b/modules/single-node-asg/outputs.tf @@ -12,3 +12,7 @@ output "data_volume_name_tag" { value = "${local.data_volume_name_prefix}-${local.az}" description = "Name tag value for attached data volume" } + +output "eip_address" { + value = var.assign_eip ? aws_eip.eip.*[0].public_ip : "" +} diff --git a/modules/single-node-asg/variables.tf b/modules/single-node-asg/variables.tf index 97de3191..b06b9093 100644 --- a/modules/single-node-asg/variables.tf +++ b/modules/single-node-asg/variables.tf @@ -62,7 +62,7 @@ variable "data_volume_size" { variable "data_volume_encrypted" { default = true description = "Boolean, whether or not to encrypt the EBS block device" - type = string + type = bool } variable "data_volume_kms_key_id" { @@ -98,7 +98,7 @@ variable "init_suffix" { variable "public_ip" { default = true description = "Boolean flag to enable/disable `map_public_ip_on_launch` in the launch configuration" - type = string + type = bool } variable "subnet_id" { @@ -121,3 +121,9 @@ variable "load_balancers" { description = "The list of load balancers names to pass to the ASG module" type = list(string) } + +variable "assign_eip" { + default = false + description = "Whether or not associating an EIP with the node." + type = bool +} From 98a15b1be992f49b8c6c9064935dc0ee16752be6 Mon Sep 17 00:00:00 2001 From: Kirill Zaborsky Date: Tue, 14 Apr 2020 15:02:26 +0300 Subject: [PATCH 3/8] Tunnel using EC2 instance connect --- modules/ec2-connect-tunnel/README.md | 6 ++++ modules/ec2-connect-tunnel/iam.tf | 5 +++ modules/ec2-connect-tunnel/main.tf | 20 ++++++++++++ modules/ec2-connect-tunnel/outputs.tf | 4 +++ modules/ec2-connect-tunnel/sg.tf | 21 +++++++++++++ modules/ec2-connect-tunnel/variables.tf | 41 +++++++++++++++++++++++++ 6 files changed, 97 insertions(+) create mode 100644 modules/ec2-connect-tunnel/README.md create mode 100644 modules/ec2-connect-tunnel/iam.tf create mode 100644 modules/ec2-connect-tunnel/main.tf create mode 100644 modules/ec2-connect-tunnel/outputs.tf create mode 100644 modules/ec2-connect-tunnel/sg.tf create mode 100644 modules/ec2-connect-tunnel/variables.tf diff --git a/modules/ec2-connect-tunnel/README.md b/modules/ec2-connect-tunnel/README.md new file mode 100644 index 00000000..6a031201 --- /dev/null +++ b/modules/ec2-connect-tunnel/README.md @@ -0,0 +1,6 @@ +# EC2 Instance Connect tunnel + +Creates a s single node ASG (using the `singe-node-asg` module) allowing SSH +connections using [EC2 Instance Connect](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Connect-using-EC2-Instance-Connect.html). +Assumes Ubuntu AMI to be used (`ec2-instance-connect` gets installed using +`apt`). Use `ec2-connect-role` to setup an IAM role for SSH access. diff --git a/modules/ec2-connect-tunnel/iam.tf b/modules/ec2-connect-tunnel/iam.tf new file mode 100644 index 00000000..5c3eb5e3 --- /dev/null +++ b/modules/ec2-connect-tunnel/iam.tf @@ -0,0 +1,5 @@ +# allows connecting with SSM manager +resource "aws_iam_role_policy_attachment" "ssm_instance" { + role = module.asg.asg_iam_role_name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} diff --git a/modules/ec2-connect-tunnel/main.tf b/modules/ec2-connect-tunnel/main.tf new file mode 100644 index 00000000..4b37d798 --- /dev/null +++ b/modules/ec2-connect-tunnel/main.tf @@ -0,0 +1,20 @@ +module "asg" { + source = "../single-node-asg" + + region = var.region + ami = var.ami + key_name = "" + instance_type = var.instance_type + name_prefix = var.name_prefix + name_suffix = var.name_suffix + + security_group_ids = [module.tunnel-sg.id] + subnet_id = var.subnet_id + data_volumes = [] + assign_eip = true + + init_suffix = < Date: Wed, 22 Apr 2020 12:39:00 +0300 Subject: [PATCH 4/8] Add ec-connect-role module --- modules/ec2-connect-role/README.md | 4 +++ modules/ec2-connect-role/main.tf | 47 +++++++++++++++++++++++++++ modules/ec2-connect-role/outputs.tf | 7 ++++ modules/ec2-connect-role/variables.tf | 26 +++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 modules/ec2-connect-role/README.md create mode 100644 modules/ec2-connect-role/main.tf create mode 100644 modules/ec2-connect-role/outputs.tf create mode 100644 modules/ec2-connect-role/variables.tf diff --git a/modules/ec2-connect-role/README.md b/modules/ec2-connect-role/README.md new file mode 100644 index 00000000..19230a88 --- /dev/null +++ b/modules/ec2-connect-role/README.md @@ -0,0 +1,4 @@ +## EC2 Instance Connect Role + +Creates an IAM role that can be used to connect to EC2 instances using +EC2 Instance Connect e.g. created using the `ec2-connect-tunnel` module. diff --git a/modules/ec2-connect-role/main.tf b/modules/ec2-connect-role/main.tf new file mode 100644 index 00000000..3197da8e --- /dev/null +++ b/modules/ec2-connect-role/main.tf @@ -0,0 +1,47 @@ +data "aws_caller_identity" "current" { +} + +data "aws_iam_policy_document" "ec2-instance-connect" { + statement { + actions = [ + "ec2:DescribeInstances", + ] + + resources = ["*"] + } + + statement { + actions = [ + "ec2-instance-connect:SendSSHPublicKey", + ] + + resources = [for i in var.instance_ids : "arn:aws:ec2:${var.region}:${var.account_id}:instance/${i}"] + + condition { + test = "StringEquals" + variable = "ec2:osuser" + + values = [ + "ubuntu", + ] + } + } +} + +resource "aws_iam_policy" "ec2-instance-connect" { + name = "ec2-instance-connect" + description = "grants permissions to connect to an instance using EC2 Instance Connect" + policy = data.aws_iam_policy_document.ec2-instance-connect.json +} + +module "role" { + source = "../cross-account-role" + name = var.name + trust_account_ids = concat([data.aws_caller_identity.current.account_id], + var.trust_account_ids) +} + +resource "aws_iam_role_policy_attachment" "role_ec2-instance-connect" { + role = module.role.name + policy_arn = aws_iam_policy.ec2-instance-connect.arn +} diff --git a/modules/ec2-connect-role/outputs.tf b/modules/ec2-connect-role/outputs.tf new file mode 100644 index 00000000..1f252c69 --- /dev/null +++ b/modules/ec2-connect-role/outputs.tf @@ -0,0 +1,7 @@ +output "arn" { + value = module.role.arn +} + +output "name" { + value = module.role.name +} diff --git a/modules/ec2-connect-role/variables.tf b/modules/ec2-connect-role/variables.tf new file mode 100644 index 00000000..db2df982 --- /dev/null +++ b/modules/ec2-connect-role/variables.tf @@ -0,0 +1,26 @@ +variable "name" { + description = "Name to give the role" + type = string +} + +variable "trust_account_ids" { + description = "List of other accounts to trust to assume the role" + default = [] + type = list(string) +} + +variable "region" { + description = "The AWS region to deploy to" + type = string +} + +variable "account_id" { + description = "ID of the account which instances to connect to" + type = string +} + +variable "instance_ids" { + description = "IDs of instances to connect to" + type = list(string) + default = ["*"] +} From 3c17169a22ea1f0e18f8e5036ddcb02e839c9d8e Mon Sep 17 00:00:00 2001 From: Kirill Zaborsky Date: Tue, 28 Apr 2020 12:57:54 +0300 Subject: [PATCH 5/8] Changelog entries for EC2 Instance Connect modules --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5a8926..5ba7f627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Modules +* `ec2-connect-tunnel`: singe node ASG to allow SSH using EC2 Instance Connect +* `ec2-connect-role`: IAM role to allow EC2 Instance Connect + ### Examples # v0.9.16 From 9faa472d8b66b535b9f8e8e237bd17e46760f6d6 Mon Sep 17 00:00:00 2001 From: Kirill Zaborsky Date: Tue, 5 May 2020 15:05:34 +0300 Subject: [PATCH 6/8] Add init_prefix and init_suffix to ec2-instance-connect --- modules/ec2-connect-tunnel/main.tf | 2 ++ modules/ec2-connect-tunnel/variables.tf | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/ec2-connect-tunnel/main.tf b/modules/ec2-connect-tunnel/main.tf index 4b37d798..d5a02a40 100644 --- a/modules/ec2-connect-tunnel/main.tf +++ b/modules/ec2-connect-tunnel/main.tf @@ -13,8 +13,10 @@ module "asg" { data_volumes = [] assign_eip = true + init_prefix = var.init_prefix init_suffix = < Date: Mon, 11 May 2020 23:41:45 -0500 Subject: [PATCH 7/8] remove sg rule and exporting values --- modules/ec2-connect-tunnel/outputs.tf | 24 ++++++++++++++++++++++++ modules/ec2-connect-tunnel/sg.tf | 8 -------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/modules/ec2-connect-tunnel/outputs.tf b/modules/ec2-connect-tunnel/outputs.tf index ee2e83bb..57ccfa3b 100644 --- a/modules/ec2-connect-tunnel/outputs.tf +++ b/modules/ec2-connect-tunnel/outputs.tf @@ -2,3 +2,27 @@ output "public_ip" { value = module.asg.eip_address description = "Public IP of the tunnel" } + +output "sg_id" { + value = module.tunnel-sg.id + description = "Security group id of the tunnel" +} + +output "asg_name" { + value = module.asg.name + description = "`name` exported from the Server `aws_autoscaling_group`" +} + +output "asg_iam_role_name" { + value = module.asg.asg_iam_role_name + description = "`name` exported from the Service Data `aws_iam_role`" +} + +output "data_volume_name_tag" { + value = module.asg.data_volume_name_tag + description = "Name tag value for attached data volume." +} + +output "eip_address" { + value = module.asg.eip_address +} diff --git a/modules/ec2-connect-tunnel/sg.tf b/modules/ec2-connect-tunnel/sg.tf index 74f4d25a..937fed71 100644 --- a/modules/ec2-connect-tunnel/sg.tf +++ b/modules/ec2-connect-tunnel/sg.tf @@ -6,14 +6,6 @@ module "tunnel-sg" { extra_tags = var.extra_tags } -module "ssh-port-sg-rule" { - source = "../single-port-sg" - security_group_id = module.tunnel-sg.id - cidr_blocks = ["0.0.0.0/0"] - port = 22 - description = "SSH from anywhere" -} - # security group rule to open egress (outbound from nodes) module "allow-open-egress" { source = "../open-egress-sg" From dd4700d071f91b893fe82e22bced0f09f52ff9a0 Mon Sep 17 00:00:00 2001 From: Kirill Zaborsky Date: Tue, 12 May 2020 15:27:52 +0300 Subject: [PATCH 8/8] Fix ec2-connect-tunnel outputs --- modules/ec2-connect-tunnel/outputs.tf | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/ec2-connect-tunnel/outputs.tf b/modules/ec2-connect-tunnel/outputs.tf index 57ccfa3b..85ceaf9f 100644 --- a/modules/ec2-connect-tunnel/outputs.tf +++ b/modules/ec2-connect-tunnel/outputs.tf @@ -9,20 +9,16 @@ output "sg_id" { } output "asg_name" { - value = module.asg.name - description = "`name` exported from the Server `aws_autoscaling_group`" + value = module.asg.asg_name + description = "name of the tunnel ASG" } output "asg_iam_role_name" { value = module.asg.asg_iam_role_name - description = "`name` exported from the Service Data `aws_iam_role`" + description = "name of ASG IAM role" } output "data_volume_name_tag" { value = module.asg.data_volume_name_tag description = "Name tag value for attached data volume." } - -output "eip_address" { - value = module.asg.eip_address -}