I’ve recently forsaken VMware Fusion in favour of Canonical’s Multipass to create and manage Ubuntu Server VMs on macOS.
There are two things I want to be able to do with Ubuntu Server VMs: establish an SSH connection as root
and navigate the file system using applications such as Forklift and FileZilla. Purists and more security-minded individuals may balk at this, but these VMs are local, non-production servers purely for testing.
NOTE: If you have both VMware Fusion and Multipass installed on the same machine, take a look at Possible Conflict between VMware Fusion and Multipass at the foot of this article.
Throughout this article the following terms are used:
Term | Definition |
---|---|
host | The machine or OS on which Multipass is installed. |
guest instance | The Multipass VM. |
client | The machine initiating a connection to another (virtual) machine. |
server | The (virtual) machine being connected to by a client. |
identity file | The file containing the private key necessary to authenticate a user on the guest instance or server. The identity file is stored on the host or client. |
host key | The public or private key used to authenticate the guest instance or server to the host or client. The public key is stored on the host or client and the private key is stored on the guest instance or server. |
To demonstrate, I’ll be using macOS as the host OS and a Multipass guest instance named foo
with an IP address of 172.16.170.4
running Ubuntu 20.04 LTS
. A standard user with sudo
privileges named ubuntu
is created by default on every new guest instance:
multipass list
Name State IPv4 Image foo Running 172.16.170.4 Ubuntu 20.04 LTS
There are two built-in commands to connect with a guest instance. The first is the exec
command which simply executes a command on the running guest instance. If it isn’t running, the exec
command fails:
multipass exec foo -- whoami
ubuntu
exec failed: instance "foo" is not running
The second is shell
or sh
which opens an interactive login shell on the running guest instance. If the guest instance isn’t running, an attempt is made to first start it:
multipass shell foo
Neither of these methods can be used to configure connections in applications like Forklift or FileZilla. These and other file transfer applications typically use SFTP to transfer files to and from the client and server. Using SFTP, an encrypted SSH connection is first established by the client to the server. Files are then transferred using FTP over the SSH connection.
When establishing an SSH connection, we need to be mindful of the authentication method used by the server. Typically this is public key authentication and Multipass guest instances are no exception. Public key authentication requires both a public and private key. Multipass generates this key pair using the RSA algorithm with a 2048-bit key length and uses the same key pair for both the ubuntu
and root
users.
The private key is stored in an identity file on the host. For macOS, this identity file is /var/root/Library/Application Support/multipassd/ssh-keys/id_rsa
1 and is created when Multipass is installed. The corresponding public key is included in the vendor-data that Multipass gives to cloud-init
when the guest instance is initialised and is written to /home/ubuntu/.ssh/authorized_keys
and /root/.ssh/authorized_keys
on the guest instance.
1 On a Linux host with Multipass installed via Snap the identity file is /var/snap/multipass/common/data/multipassd/ssh-keys/id_rsa
.
Because of its location on the host, superuser privileges are required to read the identity file. Using sudo
on the command line is not an issue although by doing so the guest instance’s public host keys (ed25519
, rsa
and ecdsa
) are added to /var/root/.ssh/known_hosts
on the host not ~/.ssh/known_hosts
. This is important to remember if attempting to remove public host keys for a given instance from the known_hosts
file:
sudo ssh-keygen -R 172.16.170.4 -f /var/root/.ssh/known_hosts
However, the requirement for using sudo
is a problem when configuring SFTP connections in Forklift or FileZilla as it’s not possible to elevate an admin user’s privileges to those of the superuser. One way to overcome this is to copy the identity file to a new location accessible to the admin user:
mkdir -p ~/.ssh/multipass && sudo cp /var/root/Library/Application\ Support/multipassd/ssh-keys/id_rsa $_
This first creates a sub-directory named multipass
in the admin user’s .ssh
directory, then copies the identity file to it. The $_
at the end of the cp
command is a special parameter which expands to the last argument – ~/.ssh/multipass
– passed to the previous command – mkdir
.
The duplicate file has the same root:wheel
ownership as the original. This needs to be changed to $USER:staff
:
sudo chown $USER:staff ~/.ssh/multipass/id_rsa
In addition, the duplicate identity file’s permissions are read-only (400
), the same as the original. When using FileZilla, you may need to change the format of the private key. See FileZilla can’t Read the Private Key at the end of this article. To do so, the permissions must first be changed to read-write (600
):
chmod 600 ~/.ssh/multipass/id_rsa
Now the private key is in the correct location, let’s try to establish an SSH connection to the guest instance named foo
using the standard user ubuntu
. The location of the identity file is specified using the -i
option. The instance must first be running or the connection will timeout:
ssh -i ~/.ssh/multipass/id_rsa ubuntu@172.16.170.4
The credentials used to successfully establish an SSH connection to the guest instance can now be used to configure an SFTP connection in Forklift or FileZilla by specifying the location of the identity file, but what if we want to connect as the guest instance’s root user.
It’s possible to open a non-login shell with root
access on a running guest instance using the built-in exec
command:
multipass exec foo -- sudo su
This is no different from executing sudo su
having first logged-in using the standard user account ubuntu
on the guest instance and as such doesn’t allow for the configuration of SFTP connections in file transfer applications.
Let’s see if it’s possible to establish an SSH connection to the guest instance using its root
user. We use the same command syntax as before, but substitute root
for ubuntu
:
ssh -i ~/.ssh/multipass/id_rsa root@172.16.170.4
Please login as the user "ubuntu" rather than the user "root". Connection to 172.16.170.4 closed.
It’s pretty clear from the output that this isn’t possible. However, let’s take a look at the /root/.ssh/authorized_keys
file on the guest instance:
sudo cat /root/.ssh/authorized_keys
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"ubuntu\" rather than the user \"root\".';echo;sleep 10;exit 142" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz3PNy+AIaX4jTgvTw0VtrAxnH53kpOjiVnbjM27fMkXFUIZN1Fa16XynrVzg6pGaUrFYUJhK5OCXzZJ6DwcWQG1QYxI1TGytraJa3osTU72EZh71vsD+7EP6+M2MzxK7B7oQDh6GTt17f4dG5GPOHEdueG0qHhRM5A9WhiWvDCYUcFHm/eFfYAGQTEz103faMl7frl5Vq5VdJQBjVEVIKfnEtfP3vRGcuUfi+IRfzYS4L1dxX5blnj5Im39YMh31aBXxl/Fo4Atb6PKOAnoaR4wHBTDik+2q4vHhmfPDXPAcX3gUyWjQ9nyBLqpjptVF6rnPIMv228/WwwnVRB/ED ubuntu@localhost
As is evident from the output, the public key is prefixed with:
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"ubuntu\" rather than the user \"root\".';echo;sleep 10;exit 142"
This string is the default value of the disable_root_opts
configuration key used by cloud-init and the highlighted code is responsible for displaying an error message, waiting 10 seconds and then exiting, effectively denying root
login.
To be able to establish an SSH connection as root
, this code has to be removed either manually by opening the file with nano
or similar editor or alternatively using sed
:
sudo sed -i.$(date +"%Y%m%d-%H%M%S") 's/,command.*exit 142"//' /root/.ssh/authorized_keys
Alternatively, if you pass user-data to cloud-init
to bootstrap Multipass guest instances you could include either the disable_root
or the disable_root_opts
configuration keys in your cloud-init configuration file to control the root user’s access.
The default value for disable_root
is true which causes the value of disable_root_opts
to be pre-pended to the public key in the authorized_keys
file. We’ve already seen what this default value is, so the first method is to provide a new value for the disable_root_opts
configuration key in your cloud-init configuration file, omitting the offending code:
#cloud-config ... disable_root_opts: no-port-forwarding,no-agent-forwarding,no-X11-forwarding ...
In the second method, we change the disable_root configuration key to false which effectively ignores the the disable_root_opts
configuration key and results in nothing being pre-pended to the public key:
#cloud-config ... disable_root: false ...
Multipass guest instances can now be created using:
multipass launch --name foo --cloud-init config.yaml
It should now be possible to establish an SSH connection to the guest instance using its root
user:
ssh -i ~/.ssh/multipass/id_rsa root@172.16.170.4
These same credentials can be used to configure SFTP connections to the guest instance using its root
user in Forklift and FileZilla.
NOTES
Possible Conflict between VMware Fusion and Multipass
If both Multipass and VMware Fusion are installed on your machine it’s worth noting that by default VMware Fusion VMs are configured to share the IP address of the Mac on the external network under Settings > Network Adapter > Share with my Mac. Before starting a VMware Fusion VM ensure that no Multipass guest instances are running by executing multipass stop --all
. If any Multipass guest instances are running, VMware Fusion displays the error Could not connect 'Ethernet0' to virtual network '/dev/vmnet8'
. The VMware Fusion VM will start, but will be unable to connect to the Internet.
A workaround to having both Multipass guest instances and VMware Fusion VMs running at the same time is to reconfigure the VMware Fusion VM’s network adapter to use Wi-Fi with Settings > Network Adapter > Wi-Fi before starting the VMware Fusion VM.
However, starting a VMware Fusion VM while a Multipass guest instance is running will most likely cause issues with Multipass as well. If this occurs, first stop and then start the Multipass daemon – multipassd
– before restarting the guest instance:
sudo launchctl unload /Library/LaunchDaemons/com.canonical.multipassd.plist && sudo launchctl load /Library/LaunchDaemons/com.canonical.multipassd.plist
FileZilla can’t Read the Private Key
FileZilla appears unable to read the private key in the identity file. It displays a Could not load key file error. The header of the private key is -----BEGIN PRIVATE KEY-----
and the Base64-encoded text begins MII...IBADAN
which identifies it as an unencrypted private key in Base64-encoded PKCS#8 format.
I couldn’t find any documentation to confirm if or explain why FileZilla doesn’t like this format, but FileZilla will accept the private key if it is first converted to the new OpenSSH format. This conversion appears to be as simple as setting a new empty passphrase – the original passphrase is also empty – using ssh-keygen
:
ssh-keygen -p -N '' -f ~/.ssh/multipass/id_rsa
The private key remains unencrypted, but its header is now -----BEGIN OPENSSH PRIVATE KEY-----
.
Simple, effective. Solved the problem – Thanks!