diff --git a/README.md b/README.md index c8cf80e..13267bd 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,34 @@ This terraform module can create a cross zone vpc. ###### Result This will create a vpc across multiple zone with a couple of subnet, routing -table, bastion instance, elb ans Managed NAT +table, bastion instance, elb and Managed NAT ### Subnet -There will be two level of subnet the front and the back and there will be -a subnet per type per zone +There will be two level of subnet the public and the private and there will be +a subnet per level per zone ### route table -two routing table are create one public and one private the public is linked to -the front subnets and the Gateway -the private to the back subnets and the Managed NAT +one routing table are create one public and one for each subnet in private +subnets. +the public is linked to the public's subnets and the Internet Gateway, the +private to the private's subnets and the Managed NAT ### bastion instance and routing -all bastion instance will be launched in an auto scaling group in the from subnets -When a new Bastion instance is spawned a command to change the EIP association -is run to make sure we always have the same IP +A bastion is launched through an auto scaling group in all public's subnets. +In each subnet an ENI and EIP is created. +When a new Bastion instance is booted a command to change the ENI association +is run to make sure he always have associated with one ENI/EIP. +Some DNS record and healh-check will route to bastion no matter with ENI is +using. ### NAT -All front subnet have one Managed NAT gateway and a routing table is create for - each back subnet with a route from the subnet to the (AZ) corresponding NAT gateway +All public's subnet have one Managed NAT gateway and a routing table is create +for each private's subnet with a route from the subnet to the (AZ) +corresponding NAT gateway ## Schema for 3 zone @@ -48,7 +53,7 @@ All front subnet have one Managed NAT gateway and a routing table is create for | .---------. .---------. .---------. | | | | | | | | | | | subnet | | subnet | | subnet | | -| | front a | | front b | | front c | | +| | pub a | | pub b | | pub c | | | | | | | | | | | | .---. | | .---. | | .---. | | | | |NAT| | | |NAT| | | |NAT| | | @@ -64,7 +69,7 @@ All front subnet have one Managed NAT gateway and a routing table is create for | .---------. .---------. .---------. | | | | | | | | | | | subnet | | subnet | | subnet | | -| | back a | | back b | | back c | | +| | priv a | | priv b | | priv c | | | | | | | | | | | | | | | | | | | | | | | | | | @@ -82,8 +87,10 @@ All front subnet have one Managed NAT gateway and a routing table is create for As input you need to specified the following variable: -region: the region you want you vpc on -azs_name: the zone you want your vps on +region: the region you want your vpc on +azs_name: the zone you want your vpc on +azs_count: number of zone you want your vpc on, must be not be superior of the +number of zone available on the region network_network: a number to determine the network prefix (10,.0.0/24) @@ -105,7 +112,7 @@ tags: all tag who should be on every resources vpc_id: id of the vpc newly created -subnets: all subnet id from the private area value +subnets: all subnet id from the private default_sg: the default SecurityGroup for the vpc diff --git a/bastion.tf b/bastion.tf new file mode 100644 index 0000000..cb8db1d --- /dev/null +++ b/bastion.tf @@ -0,0 +1,127 @@ +# bastion configuration + +resource "aws_eip" "bastion" { + count = "${var.azs_count}" + + # count = "${length(aws_network_interface.bastion.*.id)}" + network_interface = "${element(aws_network_interface.bastion.*.id,count.index)}" + vpc = true + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_network_interface" "bastion" { + count = "${var.azs_count}" + + # count = "${length(aws_subnet.public.*.id)}" + subnet_id = "${element(aws_subnet.public.*.id,count.index)}" + description = "${var.cluster_name}_bastion endpoint for ${element(aws_subnet.public.*.availability_zone,count.index)}" + security_groups = ["${aws_security_group.allow_bastion_ingress.id}"] + + tags { + Name = "${var.cluster_name}_eni-bastion-${element(aws_subnet.public.*.id,count.index)}" + cluster = "${var.cluster_name}" + product = "${var.tag_product}" + purpose = "${var.tag_purpose}" + builder = "terraform" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_autoscaling_group" "bastion" { + name = "${var.cluster_name}_bastion" + max_size = 1 + min_size = 1 + desired_capacity = 1 + + health_check_grace_period = 120 + health_check_type = "EC2" + force_delete = true + launch_configuration = "${aws_launch_configuration.bastion.name}" + vpc_zone_identifier = ["${aws_subnet.public.*.id}"] + + tag { + key = "Name" + value = "${var.cluster_name}_bastion" + propagate_at_launch = true + } + + tag { + key = "builder" + value = "terraform" + propagate_at_launch = true + } + + tag { + key = "cluster" + value = "${var.cluster_name}" + propagate_at_launch = true + } + + tag { + key = "product" + value = "${var.tag_product}" + propagate_at_launch = true + } + + tag { + key = "purpose" + value = "${var.tag_purpose}" + propagate_at_launch = true + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_launch_configuration" "bastion" { + name_prefix = "${var.cluster_name}_bastion-" + image_id = "${lookup(var.ami_bastion, var.region)}" + instance_type = "${var.instance_type_bastion}" + key_name = "${var.aws_key_name}" + associate_public_ip_address = true + iam_instance_profile = "${aws_iam_instance_profile.bastion_profile.name}" + security_groups = ["${aws_security_group.allow_bastion_ingress.id}", "${aws_security_group.bastion.id}"] + + user_data = "${template_file.launch_bastion.rendered}\n${var.user_data}" + + lifecycle { + create_before_destroy = true + } +} + +resource "template_file" "launch_bastion" { + vars { + region = "${var.region}" + enis_map = "${join(" ",template_file.enis_map.*.rendered)}" + } + + template = "${file("${path.module}/bastion_user_data.tpl")}" + + lifecycle { + create_before_destroy = true + } +} + +resource "template_file" "enis_map" { + # count = "${length(aws_network_interface.bastion.*.id)}" + + count = "${var.azs_count}" + + vars { + zone = "${replace(element(split(" ", element(aws_network_interface.bastion.*.description,count.index)),3),"${var.region}","")}" + eni = "${element(aws_network_interface.bastion.*.id,count.index)}" + } + + template = "[\"${zone}\"]=\"${eni}\"" + + lifecycle { + create_before_destroy = true + } +} diff --git a/bastion_user_data.tpl b/bastion_user_data.tpl index 294cc15..a380da6 100644 --- a/bastion_user_data.tpl +++ b/bastion_user_data.tpl @@ -1,32 +1,11 @@ #!/bin/bash -v +ZONE=$(wget -qO - http://169.254.169.254/latest/meta-data/placement/availability-zone) +INSTANCE_ID=$$(wget http://169.254.169.254/latest/meta-data/instance-id -O - -q) -EC2_URL=https://ec2.${region}.amazonaws.com +declare -A enis_map +enis_map=(${enis_map}) +eni=$${enis_map[$${ZONE: -1}]} +/usr/bin/aws ec2 attach-network-interface --network-interface-id $${eni} --instance-id $${INSTANCE_ID} --device-index 1 --region '${region}' +/bin/sleep 60 -# set fqdn pointing to instance ip -if [[ -n "${route53_zone_id}" && -n "${fqdn}" ]]; then - MYMAC=$(wget http://169.254.169.254/latest/meta-data/network/interfaces/macs/ -O - -q) - MYIP=$(wget http://169.254.169.254/latest/meta-data/network/interfaces/macs/$MYMAC/ipv4-associations -O - -q) - - cat << EOF > /tmp/route53-change.json -{ - "Comment": "Updating Bastion Host Record", - "Changes": [ - { - "Action": "UPSERT", - "ResourceRecordSet": { - "Name": "bastion-${fqdn}", - "Type": "A", - "TTL": 60, - "ResourceRecords": [ - { - "Value": "$MYIP" - } - ] - } - } - ] -} -EOF - aws route53 change-resource-record-sets --hosted-zone-id "${route53_zone_id}" --change-batch file:///tmp/route53-change.json - rm -rf /tmp/route53-change.json -fi +ec2ifscan diff --git a/dns.tf b/dns.tf new file mode 100644 index 0000000..3bde97d --- /dev/null +++ b/dns.tf @@ -0,0 +1,36 @@ +resource "aws_route53_record" "bastion" { + #count = "${length(aws_network_interface.bastion.*.private_ips)}" + count = "${var.azs_count}" + + name = "bastion-${var.cluster_name}" + zone_id = "${var.route_zone_id}" + type = "A" + set_identifier = "${count.index}" + weight = "${count.index}" + records = ["${element(aws_eip.bastion.*.public_ip,count.index)}"] + ttl = 60 + health_check_id = "${element(aws_route53_health_check.bastion_check.*.id,count.index)}" +} + +resource "aws_route53_health_check" "bastion_check" { + count = "${var.azs_count}" + + #count = "${length(aws_route53_record.bastion.*.)}" + ip_address = "${element(aws_eip.bastion.*.public_ip,count.index)}" + port = 22 + type = "TCP" + failure_threshold = "2" + request_interval = "10" + + tags { + Name = "${var.cluster_name}_bastion-check" + cluster = "${var.cluster_name}" + product = "${var.tag_product}" + purpose = "${var.tag_purpose}" + builder = "terraform" + } + + lifecycle { + create_before_destroy = true + } +} diff --git a/iam.tf b/iam.tf new file mode 100644 index 0000000..fe18f04 --- /dev/null +++ b/iam.tf @@ -0,0 +1,58 @@ +resource "aws_iam_role" "bastion_role" { + name = "${var.cluster_name}_bastion_role" + + assume_role_policy = <