Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
24 changes: 24 additions & 0 deletions modules/ec2-connect-tunnel/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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.asg_name
description = "name of the tunnel ASG"
}

output "asg_iam_role_name" {
value = module.asg.asg_iam_role_name
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."
}
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