Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access bastion host via elb #33

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
51 changes: 20 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# tf_aws_bastion_s3_keys

A Terraform module for creating resilient bastion host using auto-scaling group (min=max=desired=1) and populate its
`~/.ssh/authorized_keys` with public keys fetched from S3 bucket.
A Terraform module for creating a resilient bastion host using auto-scaling group (min=max=desired=1) and populate its
`~/.ssh/authorized_keys` with public keys fetched from S3 bucket. Access is via a classic elastic load balancer, which makes it possible to create a Route53 Alias record pointing to the ELB.

This module can append public keys, setup cron to update them and run
additional commands at the end of setup. Note that if it is set up to
update the keys, removing a key from the bucket will also remove it from
the bastion host.
This module can append public keys, setup cron to update them and run additional commands at the end of setup. Note that if it is set up to update the keys, removing a key from the bucket will also remove it from the bastion host.

Only SSH access is allowed to the bastion host.

Expand All @@ -30,11 +27,14 @@ Only SSH access is allowed to the bastion host.
* `allowed_cidr` - A list of CIDR Networks to allow ssh access to. Defaults to "0.0.0.0/0"
* `allowed_ipv6_cidr` - A list of IPv6 CIDR Networks to allow ssh access to. Defaults to "::/0"
* `allowed_security_groups` - A list of Security Group ID's to allow access to the bastion host (useful if bastion is deployed internally) Defaults to empty list
* `ssh_port` - inbound ssh port (default, `22`)

## Outputs:

* ssh_user - SSH user to login to bastion
* security_group_id - ID of the security group the bastion host is launched in.
* dns_name - DNS name of the ELB.
* zone_id - canonical hosted zone ID of the ELB (to be used in a Route 53 Alias record).

## Example:

Expand All @@ -53,43 +53,32 @@ Basic example - In your terraform code add something like this:
additional_user_data_script = "date"
}

If you want to assign EIP to instance launched by auto-scaling group you can provide desired `eip` as module input
and then execute `additional_user_data_script` which sets EIP. This way you can use Route53 with EIP, which will always
point to existing bastion instance. You will also need to add allow_associateaddress permission to iam_instance_profile (see `samples/iam_allow_associateaddress.tf`):
By default, an external IP is allocated to the ELB instance.

If you want to create a Route53 Alias record pointing to the public DNS name of the ELB, you should use code something like this:

module "bastion" {
// see above
eip = "${aws_eip.bastion.public_ip}"
iam_instance_profile = "s3_readonly-allow_associateaddress"
additional_user_data_script = <<EOF
REGION=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | grep region | awk -F\" '{print $4}')
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
aws ec2 associate-address --region $REGION --instance-id $INSTANCE_ID --allocation-id ${aws_eip.bastion.id}
EOF
// as above
}

resource "aws_eip" "bastion" {
vpc = true
data "aws_route53_zone" "zone" {
name = "example.com"
}

resource "aws_route53_record" "bastion" {
zone_id = "..."
resource "aws_route53_record" "alias" {
zone_id = "${data.aws_route53_zone.zone.zone_id}"
name = "bastion.example.com"
type = "A"
ttl = "3600"
records = ["${aws_eip.bastion.public_ip}"]

alias {
name = "${module.bastion.dns_name}"
zone_id = "${module.bastion.zone_id}"
evaluate_target_health = "false"
}
}

After you run `terraform apply` you should be able to login to your bastion host like:

$ ssh ${module.bastion.ssh_user}@${module.bastion.instance_ip}

or:

$ ssh ${module.bastion.ssh_user}@${aws_eip.bastion.public_ip}

or even like this:

$ ssh [email protected]

PS: In some cases you may consider adding flag `-A` to ssh command to enable forwarding of the authentication agent connection.
Expand Down
90 changes: 63 additions & 27 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
resource "aws_security_group" "bastion" {
name = "${var.name}"
vpc_id = "${var.vpc_id}"
description = "Bastion security group (only SSH inbound access is allowed)"
description = "Bastion security group (elb to instance access on port 22)"

tags {
Name = "${var.name}"
}
}

resource "aws_security_group" "elb" {
name = "${var.name}-elb"
vpc_id = "${var.vpc_id}"
description = "elb security group (inbound access to configured ssh port)"

tags {
Name = "${var.name}"
Expand All @@ -10,24 +20,33 @@ resource "aws_security_group" "bastion" {

resource "aws_security_group_rule" "ssh_ingress" {
type = "ingress"
from_port = "22"
to_port = "22"
from_port = "${var.ssh_port}"
to_port = "${var.ssh_port}"
protocol = "tcp"
cidr_blocks = "${var.allowed_cidr}"
ipv6_cidr_blocks = "${var.allowed_ipv6_cidr}"
security_group_id = "${aws_security_group.bastion.id}"
security_group_id = "${aws_security_group.elb.id}"
}

resource "aws_security_group_rule" "ssh_sg_ingress" {
count = "${length(var.allowed_security_groups)}"
resource "aws_security_group_rule" "ssh_internal" {
type = "ingress"
from_port = "22"
to_port = "22"
protocol = "tcp"
source_security_group_id = "${element(var.allowed_security_groups, count.index)}"
source_security_group_id = "${aws_security_group.elb.id}"
security_group_id = "${aws_security_group.bastion.id}"
}

resource "aws_security_group_rule" "ssh_sg_ingress" {
count = "${length(var.allowed_security_groups)}"
type = "ingress"
from_port = "${var.ssh_port}"
to_port = "${var.ssh_port}"
protocol = "tcp"
source_security_group_id = "${element(var.allowed_security_groups, count.index)}"
security_group_id = "${aws_security_group.elb.id}"
}

resource "aws_security_group_rule" "bastion_all_egress" {
type = "egress"
from_port = "0"
Expand All @@ -38,6 +57,15 @@ resource "aws_security_group_rule" "bastion_all_egress" {
security_group_id = "${aws_security_group.bastion.id}"
}

resource "aws_security_group_rule" "elb_egress" {
type = "egress"
from_port = "0"
to_port = "65535"
protocol = "all"
source_security_group_id = "${aws_security_group.bastion.id}"
security_group_id = "${aws_security_group.elb.id}"
}

data "template_file" "user_data" {
template = "${file("${path.module}/${var.user_data_file}")}"

Expand All @@ -51,30 +79,15 @@ data "template_file" "user_data" {
}
}

//resource "aws_instance" "bastion" {
// ami = "${var.ami}"
// instance_type = "${var.instance_type}"
// iam_instance_profile = "${var.iam_instance_profile}"
// subnet_id = "${var.subnet_id}"
// vpc_security_group_ids = ["${aws_security_group.bastion.id}"]
// user_data = "${template_file.user_data.rendered}"
//
// count = 1
//
// tags {
// Name = "${var.name}"
// }
//}

resource "aws_launch_configuration" "bastion" {
name_prefix = "${var.name}-"
image_id = "${var.ami}"
instance_type = "${var.instance_type}"
user_data = "${data.template_file.user_data.rendered}"
name_prefix = "${var.name}-"
image_id = "${var.ami}"
instance_type = "${var.instance_type}"
user_data = "${data.template_file.user_data.rendered}"
enable_monitoring = "${var.enable_monitoring}"

security_groups = [
"${compact(concat(list(aws_security_group.bastion.id), split(",", "${var.security_group_ids}")))}",
"${aws_security_group.bastion.id}",
]

iam_instance_profile = "${var.iam_instance_profile}"
Expand Down Expand Up @@ -102,6 +115,10 @@ resource "aws_autoscaling_group" "bastion" {
wait_for_capacity_timeout = 0
launch_configuration = "${aws_launch_configuration.bastion.name}"

load_balancers = [
"${aws_elb.bastion.id}",
]

enabled_metrics = [
"GroupMinSize",
"GroupMaxSize",
Expand Down Expand Up @@ -129,3 +146,22 @@ resource "aws_autoscaling_group" "bastion" {
create_before_destroy = true
}
}

resource "aws_elb" "bastion" {
name = "${var.name}"

subnets = [
"${var.subnet_ids}",
]

listener {
instance_port = "22"
instance_protocol = "tcp"
lb_port = "${var.ssh_port}"
lb_protocol = "tcp"
}

security_groups = [
"${compact(concat(list(aws_security_group.elb.id), split(",", "${var.security_group_ids}")))}",
]
}
8 changes: 8 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ output "ssh_user" {
output "security_group_id" {
value = "${aws_security_group.bastion.id}"
}

output "dns_name" {
value = "${aws_elb.bastion.dns_name}"
}

output "zone_id" {
value = "${aws_elb.bastion.zone_id}"
}
5 changes: 5 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,8 @@ variable "associate_public_ip_address" {
variable "key_name" {
default = ""
}

variable "ssh_port" {
default = "22"
description = "Inbound ssh port"
}