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 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/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 = ["*"] +} 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..d5a02a40 --- /dev/null +++ b/modules/ec2-connect-tunnel/main.tf @@ -0,0 +1,22 @@ +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_prefix = var.init_prefix + init_suffix = < /tmp/init.log -# exec 2> /tmp/init-err.log +exec 2>&1 # set -x +apt update +${module.install-awscli.init_snippet} ${var.init_prefix} +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 @@ -93,7 +137,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..e4ee1c37 100644 --- a/modules/single-node-asg/outputs.tf +++ b/modules/single-node-asg/outputs.tf @@ -9,6 +9,10 @@ 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." +} + +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..03c025aa 100644 --- a/modules/single-node-asg/variables.tf +++ b/modules/single-node-asg/variables.tf @@ -42,9 +42,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" { @@ -54,15 +54,15 @@ 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" { 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,19 @@ 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 +} + +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." +}