Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Ec2 connect tunnel #325

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions examples/single-node-asg-tester/main.tf
Original file line number Diff line number Diff line change
@@ -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" }]
}
4 changes: 4 additions & 0 deletions modules/ec2-connect-role/README.md
Original file line number Diff line number Diff line change
@@ -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.
47 changes: 47 additions & 0 deletions modules/ec2-connect-role/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions modules/ec2-connect-role/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
output "arn" {
value = module.role.arn
}

output "name" {
value = module.role.name
}
26 changes: 26 additions & 0 deletions modules/ec2-connect-role/variables.tf
Original file line number Diff line number Diff line change
@@ -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 = ["*"]
}
6 changes: 6 additions & 0 deletions modules/ec2-connect-tunnel/README.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions modules/ec2-connect-tunnel/iam.tf
Original file line number Diff line number Diff line change
@@ -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"
}
22 changes: 22 additions & 0 deletions modules/ec2-connect-tunnel/main.tf
Original file line number Diff line number Diff line change
@@ -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 = <<END_INIT_SUFFIX
echo "Installing ec2-instance-connect"
apt install ec2-instance-connect
${var.init_suffix}
END_INIT_SUFFIX
}
28 changes: 28 additions & 0 deletions modules/ec2-connect-tunnel/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoseD92 this output already exists as public_ip

}
13 changes: 13 additions & 0 deletions modules/ec2-connect-tunnel/sg.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module "tunnel-sg" {
source = "../security-group-base"
name = "${var.name_prefix}-sg"
description = "SG for the tunnel ASG"
vpc_id = var.vpc_id
extra_tags = var.extra_tags
}

# security group rule to open egress (outbound from nodes)
module "allow-open-egress" {
source = "../open-egress-sg"
security_group_id = module.tunnel-sg.id
}
53 changes: 53 additions & 0 deletions modules/ec2-connect-tunnel/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
variable "name_prefix" {
description = "Prefix for naming resources, usually project-related"
type = string
}

variable "name_suffix" {
description = "suffix to include when naming the various resources"
type = string
default = ""
}

variable "region" {
description = "The AWS region to deploy to"
type = string
}

variable "ami" {
description = "The base AMI for each AWS instance created"
type = string
}

variable "instance_type" {
description = "The type of AWS instance (size)"
type = string
}

variable "vpc_id" {
description = "ID of VPC to associate SG with"
type = string
}

variable "subnet_id" {
description = "The ID of the subnet to use, depends on the availability zone"
type = string
}

variable "init_prefix" {
default = ""
description = "init shell to run before executing the main part of instance init"
type = string
}

variable "init_suffix" {
default = ""
description = "init shell to run after the main part of instance init"
type = string
}

variable "extra_tags" {
description = "map of name,value pairs to tag the security group (append to Name tag)"
default = {}
type = map(string)
}
10 changes: 10 additions & 0 deletions modules/init-snippet-attach-ebs-volume/instance_id.tpl
Original file line number Diff line number Diff line change
@@ -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
67 changes: 17 additions & 50 deletions modules/init-snippet-attach-ebs-volume/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <<EOF
${var.init_prefix}
${data.template_file.instance_id.rendered}
${join("\n", data.template_file.init_snippet.*.rendered)}
${var.init_suffix}
EOF
}

Loading