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:
|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
Ubuntu 20.04 LTS. A standard user with
sudo privileges named
ubuntu is created by default on every new guest instance:
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
exec failed: instance "foo" is not running
The second is
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
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_rsa1 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
/root/.ssh/authorized_keys on the guest instance.
1 On a Linux host with Multipass installed via Snap the identity file is
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 (
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
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 –
The duplicate file has the same
root:wheel ownership as the original. This needs to be changed to
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 (
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 email@example.com
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
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
ssh -i ~/.ssh/multipass/id_rsa firstname.lastname@example.org
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
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
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
ssh -i ~/.ssh/multipass/id_rsa email@example.com
These same credentials can be used to configure SFTP connections to the guest instance using its
root user in Forklift and FileZilla.
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 -p -N '' -f ~/.ssh/multipass/id_rsa
The private key remains unencrypted, but its header is now
-----BEGIN OPENSSH PRIVATE KEY-----.