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
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.
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