Skip to content

How-to

Bastion hosts

A bastion host is one small instance with a floating IP that you sign in to first. Your other instances have no floating IP, and accept SSH and RDP only from the bastion.

That leaves one address open to the internet, open only to your own IP address, instead of one per instance.

Before you start

  • A network with a router to the internet, for the bastion and the instances behind it: see Networks and routers.
  • A key pair: see Key pairs.
  • Your public IP address, for example from curl -4 ifconfig.me.
  • An OpenSSH client. It's built into Windows 10 and later, macOS and Linux.

Create the security groups

Two groups: bastion lets you reach the bastion, and admin-from-bastion lets the bastion reach the other instances.

  1. In the Breqwatr Cloud Portal (Strata), open Security Groups and choose New security group. Name it bastion and create it.
  2. Open bastion and choose Add rule: Protocol TCP, Open Port Port, Port 22, Source CIDR your public IP address followed by /32. Choose Add rule.
  3. Create a second group named admin-from-bastion. Leave it without rules for now: the portal's rules take an address range as the source, so its rules wait until the bastion has an address.
openstack --os-cloud <cloud-name> security group create bastion
openstack --os-cloud <cloud-name> security group rule create bastion \
  --protocol tcp --dst-port 22 --remote-ip <your-ip>/32

openstack --os-cloud <cloud-name> security group create admin-from-bastion
openstack --os-cloud <cloud-name> security group rule create admin-from-bastion \
  --protocol tcp --dst-port 22 --remote-group bastion
openstack --os-cloud <cloud-name> security group rule create admin-from-bastion \
  --protocol tcp --dst-port 3389 --remote-group bastion

--remote-group bastion allows traffic from any instance in the bastion group, whatever its address.

resource "openstack_networking_secgroup_v2" "bastion" {
  name = "bastion"
}

resource "openstack_networking_secgroup_rule_v2" "bastion_ssh" {
  security_group_id = openstack_networking_secgroup_v2.bastion.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "${var.my_ip}/32"
}

resource "openstack_networking_secgroup_v2" "admin_from_bastion" {
  name = "admin-from-bastion"
}

resource "openstack_networking_secgroup_rule_v2" "admin_from_bastion" {
  for_each          = toset(["22", "3389"])
  security_group_id = openstack_networking_secgroup_v2.admin_from_bastion.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = tonumber(each.key)
  port_range_max    = tonumber(each.key)
  remote_group_id   = openstack_networking_secgroup_v2.bastion.id
}

remote_group_id allows traffic from any instance in the bastion group, whatever its address.

Launch the bastion

A Linux instance with the smallest flavour is enough: it only forwards your connections.

  1. Open Instances and choose New instance: an Ubuntu image, Flavor v1.c1r2, your network, your SSH key, and the bastion security group. Name it bastion-01 and choose Create instance.
  2. Open IP Addresses and choose Allocate IP. On the new address, open the menu at the end of its row, choose Associate and pick bastion-01.
  3. Note bastion-01's private address from its page in Instances. Open admin-from-bastion in Security Groups and add two rules with Source CIDR that address followed by /32: one for Port 22, and one for Port 3389.
openstack --os-cloud <cloud-name> server create --flavor v1.c1r2 --network <network-name> --security-group bastion --key-name <key-name> --block-device source_type=image,uuid=<linux-image-id>,destination_type=volume,volume_size=20,boot_index=0,delete_on_termination=true --wait bastion-01

Create a floating IP and attach it. The first command prints the address:

openstack --os-cloud <cloud-name> floating ip create <external-network> -f value -c floating_ip_address
openstack --os-cloud <cloud-name> server add floating ip bastion-01 <bastion-floating-ip>

Launch the bastion as usual, with the bastion group and a floating IP:

resource "openstack_compute_instance_v2" "bastion_01" {
  name            = "bastion-01"
  flavor_name     = "v1.c1r2"
  key_pair        = var.key_pair
  security_groups = [openstack_networking_secgroup_v2.bastion.name]
  # image, block_device and network as for your other instances
}

Put instances behind it

Launch the other instances with the admin-from-bastion security group, plus any group their application needs, and no floating IP. To move an existing instance behind the bastion, add the group to it and release its floating IP:

openstack --os-cloud <cloud-name> server add security group <instance-name> admin-from-bastion
openstack --os-cloud <cloud-name> server remove floating ip <instance-name> <floating-ip>

Remove any rule that opened SSH or RDP to the instance from the internet.

Connect through the bastion

-J signs in to the bastion, and from there to the instance's private address:

ssh -J ubuntu@<bastion-floating-ip> ubuntu@<private-address>

This works the same in PowerShell on Windows, and in a terminal on macOS and Linux. Your private key stays on your computer: never copy it to the bastion.

To skip the -J, add the bastion to ~/.ssh/config (C:\Users\<you>\.ssh\config on Windows):

Host bastion
    HostName <bastion-floating-ip>
    User ubuntu

Host 10.0.0.*
    ProxyJump bastion
    User ubuntu

Use your network's address range in place of 10.0.0.*. Then ssh 10.0.0.12 goes through the bastion.

Forward a local port through the bastion to the instance's RDP port, and leave the command running:

ssh -N -L 13389:<private-address>:3389 ubuntu@<bastion-floating-ip>

Then point your RDP client at localhost:13389: Remote Desktop Connection (mstsc) on Windows, Microsoft's Windows App on macOS, or an RDP client such as Remmina on Linux. Sign in as Administrator with the password you set on the instance's console at first boot, as in Launch your first instances.

Stop the forward with Ctrl+C when you're done.

Check it worked

  • You can sign in to the instances through the bastion.
  • The instances have no floating IP: openstack --os-cloud <cloud-name> floating ip list shows only the bastion's.
  • A direct connection to an instance's port 22 or 3389 from anywhere but the bastion gets no answer.

Keep the bastion small

  • Run nothing else on it, and keep it updated.
  • Keep the bastion rule limited to your own IP address. When your address changes, change the rule.
  • Delete it when you don't need it, and launch it again when you do. The instances behind it keep running.