A collection of practical advice for running the CloudSOE Bastion Host image in production, on either cloud.

A ProxyJump block beats typing -J

Put the bastion in ~/.ssh/config once and every private host becomes one command:

Host bastion
    HostName 203.0.113.10
    User ubuntu                 # your chosen admin user on Azure
    IdentityFile ~/.ssh/id_ed25519

Host 10.0.*
    ProxyJump bastion
    User ubuntu

Now ssh 10.0.1.15 and scp file 10.0.1.15: just work, tunnelled through the bastion. Your private key never touches the bastion — ProxyJump forwards a TCP stream, not your credentials.

Agent forwarding: prefer not to

ssh -A exposes your agent socket to root on the bastion; anyone who compromises the host can use your keys while you’re connected. ProxyJump makes forwarding unnecessary for the common jump case. If a workflow truly needs it, use ssh-add -c (confirm each use) or a hardware key, and forward only to hosts you’d trust with the agent.

Session recording ideas

A bastion is the natural choke point for auditing administrative access. Options in rough order of effort:

  • Baseline (already on): fail2ban logs bans, and every login/logout is in journalctl _COMM=sshd — ship the journal to CloudWatch Logs / Azure Monitor so records outlive the host.
  • Terminal capture: tools like tlog or auditd’s tty logging record keystrokes of interactive sessions to the journal.
  • ForceCommand wrappers: a ForceCommand in sshd_config can wrap sessions in script for simple transcripts.

Whatever you record, ship it off the bastion — logs on a compromised host are logs the attacker edits.

Keep the AIDE database current

AIDE only proves what changed since its baseline — a stale baseline drowns you in noise until findings get ignored. Make re-baselining part of your patch routine:

sudo apt update && sudo apt upgrade
sudo aide --config /etc/aide/aide.conf --check   # review what patching changed
sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

If first boot’s database build was interrupted, re-run it: remove /var/lib/bastion-firstboot.done and sudo systemctl restart bastion-firstboot (it runs in the background; aideinit can take a few minutes).

Tighten the blast radius in the network

  • Security group / NSG on the bastion: port 22 from your office/VPN ranges only — review the rule whenever the team’s egress IPs change.
  • Private instances should accept SSH only from the bastion (reference its security group on AWS, its subnet or private IP in an Azure NSG) — never from the internet.
  • One bastion per environment (prod/staging) keeps credentials and audit trails separate.
  • If nobody should be logging in at 3 a.m., say so: longer fail2ban bans at night, or simply stop the instance outside working hours — a stopped bastion is unhackable and nearly free.

Extend fail2ban for repeat offenders

The shipped /etc/fail2ban/jail.local includes a recidive jail definition (bans IPs that keep getting banned, for a week). Enable it by adding enabled = true under [recidive], then sudo systemctl reload fail2ban and watch it with sudo fail2ban-client status recidive. See the configuration guide for base jail tuning, and the getting-started guides (AWS, Azure) for the initial lockdown.