SSM Bastion with no NAT Gateway

Using Systems Manager (SSM) to control access to a Bastion host has several advantages making using a Traditional Bastion host using SSH Keys pretty much obsolete.

  • No need for an external IP
  • No SSH Keys needed all access is via IAM
  • Access logged including what command are run

A working example can be found on GitHub

One of the biggest issues is that it requires access to AWS services to function so either a NAT gateway is required or VPC endpoints are needed

Image alt

The following endpoints need to be created to allow SSM to work without a NAT Gateway

  • SSM
  • SSMMessages
  • EC2
  • EC2Messages
  • KMS
  • Logs
 1module "vpc_endpoints" {
 2    source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
 3    version = "3.14.2"
 4
 5    vpc_id             = module.vpc.vpc_id
 6    security_group_ids = [data.aws_security_group.default.id]
 7
 8    endpoints = {
 9     ssm = {
10        service             = "ssm"
11        private_dns_enabled = true
12        subnet_ids          = module.vpc.private_subnets
13        security_group_ids  = [aws_security_group.vpc_tls.id]
14      },
15      ssmmessages = {
16        service             = "ssmmessages"
17        private_dns_enabled = true
18        subnet_ids          = module.vpc.private_subnets
19                security_group_ids  = [aws_security_group.vpc_tls.id]
20      },
21      ec2 = {
22        service             = "ec2"
23        private_dns_enabled = true
24        subnet_ids          = module.vpc.private_subnets
25        security_group_ids  = [data.aws_security_group.default.id]
26      },
27      ec2messages = {
28        service             = "ec2messages"
29        private_dns_enabled = true
30        subnet_ids          = module.vpc.private_subnets
31                security_group_ids  = [aws_security_group.vpc_tls.id]
32      },
33      kms = {
34        service             = "kms"
35        private_dns_enabled = true
36        subnet_ids          = module.vpc.private_subnets
37        security_group_ids  = [aws_security_group.vpc_tls.id]
38      },
39      logs = {
40        service             = "logs"
41        private_dns_enabled = true
42        subnet_ids          = module.vpc.private_subnets
43        security_group_ids  = [aws_security_group.vpc_tls.id]
44      },
45    }
46
47}

The instance deployed is just a standard AWS AL2 instance with the IAM manged role arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore attached to it via an instance profile.

The Terraform example here includes things like logging and KMS encryption which could be removed. All the command run on the bastion are recorded in Cloudwatch logs along with the name of the IAM user who ran them.

To connect to the host there is a bash script in client\ssh_terminal which will locate the bastion in AWS and start a session to it. Basically it is running the command

1aws ssm start-session --target "${INSTANCE_ID}" --region "${REGION}"

The instance can also be used for Port forwarding, this is a bit hacky but works. This will allow connection from your local machine to a remote host via the bastion.

Image alt

Step 1: Start an SSH session to the bastion and run

1socat TCP-LISTEN:4222,reuseaddr,fork TCP4:10.200.36.194:4222

Step 2: On your local machine start a port forwarding SSM Session

1 aws ssm start-session --target "${INSTANCE_ID}" \
2         --document-name AWS-StartPortForwardingSession \
3         --parameters '{"portNumber":["4222"],"localPortNumber":["9999"]}'

Step 3: Connect to port 9999 locally which will forward via the Bastion to port 4333 on 10.200.36.194

comments powered by Disqus