oxidecomputer / helios-engvm

Tools for creating and using Helios images on i86pc (classic PC) physical and virtual machines
Mozilla Public License 2.0
30 stars 1 forks source link

Add convenience script for ssh-ing into VM instance #4

Open smklein opened 3 years ago

smklein commented 3 years ago

Usage:

# Use configuration from defaults.sh, login as current user.
$ ./ssh
# Same, but login as root.
$ USER=root ./ssh
# Login to a machine setup with a different configuration (i.e., config/foobar.sh)
$ ./ssh foobar
smklein commented 3 years ago

I'm trying to create a setup where I have "one VM for development", and "one VM under test" - it's kinda a pain to try to remember which IP address is associated with which one, so this is a little shortcut to make my workflow better.

jclulow commented 3 years ago

I have a pattern I end up using a bit that might help here. I have a script, _nc_virsh, (in my ~/bin which is in my $PATH):

#!/bin/bash
# vim: set ts=8 sts=8 sw=8 noet:

fatal() {
    local rc=$1
    local msg=$2
    shift 2

    printf "ERROR: $msg\n" "$@" >&2
    exit $rc
}

DOMAIN="$1"
LOOKUPHOST="$2"
LOOKUPPORT="$3"

if [[ -z $DOMAIN || -z $LOOKUPHOST || -z $LOOKUPPORT ]]; then
    fatal 2 'missing arguments'
fi

sans_domain=$(sed "s/\\.${DOMAIN}\$//" <<< "$LOOKUPHOST")

if ! res=$(virsh domifaddr "$sans_domain"); then
    fatal 3 'could not look up libvirt domain "%s"\n' "$sans_domain"
fi

if ! ip=$(awk '$3 == "ipv4" { gsub("/.*", "", $4);
    print($4); exit(0); }' <<< "$res") || [[ -z "$ip" ]]; then
    fatal 4 'could not locate IP address for "%s"\n' "$sans_domain"
fi

printf 'translating "%s" --> %s\n' "$sans_domain" "$ip" >&2

exec nc "$ip" "$LOOKUPPORT"

I then configure an entry in ~/.ssh/config:

Host *.vm
        User jclulow
        ProxyCommand _nc_virsh vm %h %p
        ForwardAgent yes
        ServerAliveInterval 15

When ssh is figuring out how to connect, if it matches the *.vm wildcard then this entry gets used. For example, if I ssh helios.vm, it invokes the script: _nc_virsh vm helios.vm 22. The script then uses virsh (as you've done in your helper) the get the IP address and invokes nc as a ProxyCommand to start the connection:

$ ssh root@helios.vm
translating "helios" --> 192.168.122.15
The illumos Project     master-0-g8af575c0af    December 2020
root@helios:~# logout
Connection to helios.vm closed.

$ ssh helios.vm
translating "helios" --> 192.168.122.15
The illumos Project     master-0-g8af575c0af    December 2020
You have new mail.
jclulow@helios:~$ logout
Connection to helios.vm closed.

It has the advantage that it will work for other tools that leverage ssh like rsync and scp and git:

$ rsync root@helios.vm:./
translating "helios" --> 192.168.122.15
drwx------              6 2020/12/04 02:08:32 .
-rw-------             37 2020/12/04 02:08:32 .bash_history
-rw-r--r--            158 2020/12/02 11:28:33 .bashrc
-rw-r--r--            377 2020/12/02 11:28:33 .profile
drwx------              3 2020/12/04 00:19:35 .ssh

It will correctly fail to connect if the domain you specify is not running or doesn't exist:

 $ ssh tribblix.vm
error: Failed to query for interfaces addresses
error: Requested operation is not valid: domain is not running
ERROR: could not look up libvirt domain "tribblix"

What do you think?

smklein commented 3 years ago

I have a pattern I end up using a bit that might help here. I have a script, _nc_virsh, (in my ~/bin which is in my $PATH):

#!/bin/bash
# vim: set ts=8 sts=8 sw=8 noet:

fatal() {
  local rc=$1
  local msg=$2
  shift 2

  printf "ERROR: $msg\n" "$@" >&2
  exit $rc
}

DOMAIN="$1"
LOOKUPHOST="$2"
LOOKUPPORT="$3"

if [[ -z $DOMAIN || -z $LOOKUPHOST || -z $LOOKUPPORT ]]; then
  fatal 2 'missing arguments'
fi

sans_domain=$(sed "s/\\.${DOMAIN}\$//" <<< "$LOOKUPHOST")

if ! res=$(virsh domifaddr "$sans_domain"); then
  fatal 3 'could not look up libvirt domain "%s"\n' "$sans_domain"
fi

if ! ip=$(awk '$3 == "ipv4" { gsub("/.*", "", $4);
    print($4); exit(0); }' <<< "$res") || [[ -z "$ip" ]]; then
  fatal 4 'could not locate IP address for "%s"\n' "$sans_domain"
fi

printf 'translating "%s" --> %s\n' "$sans_domain" "$ip" >&2

exec nc "$ip" "$LOOKUPPORT"

I then configure an entry in ~/.ssh/config:

Host *.vm
        User jclulow
        ProxyCommand _nc_virsh vm %h %p
        ForwardAgent yes
        ServerAliveInterval 15

When ssh is figuring out how to connect, if it matches the *.vm wildcard then this entry gets used. For example, if I ssh helios.vm, it invokes the script: _nc_virsh vm helios.vm 22. The script then uses virsh (as you've done in your helper) the get the IP address and invokes nc as a ProxyCommand to start the connection:

$ ssh root@helios.vm
translating "helios" --> 192.168.122.15
The illumos Project     master-0-g8af575c0af    December 2020
root@helios:~# logout
Connection to helios.vm closed.

$ ssh helios.vm
translating "helios" --> 192.168.122.15
The illumos Project     master-0-g8af575c0af    December 2020
You have new mail.
jclulow@helios:~$ logout
Connection to helios.vm closed.

It has the advantage that it will work for other tools that leverage ssh like rsync and scp and git:

$ rsync root@helios.vm:./
translating "helios" --> 192.168.122.15
drwx------              6 2020/12/04 02:08:32 .
-rw-------             37 2020/12/04 02:08:32 .bash_history
-rw-r--r--            158 2020/12/02 11:28:33 .bashrc
-rw-r--r--            377 2020/12/02 11:28:33 .profile
drwx------              3 2020/12/04 00:19:35 .ssh

It will correctly fail to connect if the domain you specify is not running or doesn't exist:

 $ ssh tribblix.vm
error: Failed to query for interfaces addresses
error: Requested operation is not valid: domain is not running
ERROR: could not look up libvirt domain "tribblix"

What do you think?

So, first impressions: That's extremely rad, and I plan on playing with my ssh config way more now that I know it applies to this suite of commands.

The only thing that bums me out about that current implementation is that it requires some... manual intervention to make things work (custom script, custom path, custom config). I updated this PR to incorporate basically everything you did, but hopefully make it easier for folks checking out this repo for the first time.

TL;DR: