2017-06-11

How to Create an OpenBSD VM in Azure

[Thinking] Oh glory of glories. Oh heavenly testament to the eternal majesty of God's creation.

[Out loud] Holy macaroni!

— Homer takes his chances in the mystery wall

Yesterday I discovered that Microsoft Azure has officially announced that OpenBSD can run on their platform. And there was much rejoicing.

Unfortunately, the provided "guidance" document for setting it up is almost entirely correct. "Almost" here meaning that the directions look right. They seem right. But if you follow the directions, you are not going to have a working product and will have neither OpenBSD nor joy. OpenBSD is joy, joy is OpenBSD. We can do better. We must do better.

The instructions are clear enough, but the tutorial is a Dürer's Rhinoceros of steps that seem right upon first glance but were not tested, not verified for correctness, and not even reviewed by someone intimately familiar with the process.

If you attempt to run the steps in the guidance document as is, you'll get this error when you try to create the VM:

### BIG NO-NO. BAD EXAMPLE. WRONG. DO NOT DO THIS. ###
az vm create \
  --resource-group myResourceGroup \
  --name myOpenBSD61 \
  --image "https://mystorageaccount.blob.core.windows.net/vhds/OpenBSD61.vhd" \
  --os-type linux \
  --admin-username azureuser \
  --ssh-key-value ~/.ssh/id_rsa.pub

invalid usage for storage profile: create unmanaged OS disk created from generalized VHD:
  missing: --use-unmanaged-disk

We are not content to curse this darkness. Instead we shall light a candle and shine the luminous glory of truth, like a beacon, upon this ominous dusky horizon of bad documentation.

How to Actually Create an OpenBSD VM in Azure

N.B. You should be comfortable with Azure resources and with the OpenBSD operating system before continuing. This tutorial is easy, but it is not for the timid.

First, create your OpenBSD VM locally. There are bunch of ways to do this, but the end result must be a fixed-size .VHD file. Converting from one virtualization platform to another is outside the scope of this tutorial. Assuming you have a Windows Hyper-V instance, you can create a 2 GiB .VHD file easily.

From an elevated PowerShell prompt:

$size_bytes = 2 * [Math]::Pow(2,30)
$file_path  = 'C:\Users\Public\Documents\Hyper-V\Virtual hard disks\openbsd.vhd'

New-VHD -Fixed -SizeBytes $size_bytes -Path $file_path

Set your $size_bytes and $file_path accordingly.

Second, attach this .VHD file to a new VM and install OpenBSD on it. Installing OpenBSD is more difficult to do than falling over, but not by much. I like to mount installXX.iso to the virtual DVD drive of the VM and do the install manually, but you can configure an autoinstall process if you choose.

Customize your OpenBSD VM how you wish. You can partition the virtual disk to your liking, add software, or whatever you like. Be aware:

You MUST enable sshd.

You can answer "yes" to this question in the installer, or answer "no" and set up sshd differently, but you must set up SSH on this machine. If you don't do this, you won't be able to remote into your VM.

The instructions for prepping the VM in the guidance document are correct. As root:

echo dhcp > /etc/hostname.hvn0
echo stty com0 115200 >> /etc/boot.conf
echo set tty com0 >> /etc/boot.conf

# Choose a mirror from the official list: https://www.openbsd.org/ftp.html
echo https://my.favorite.mirror.here/pub/OpenBSD > /etc/installurl

pkg_add py-setuptools openssl git
ln -sf /usr/local/bin/python2.7        /usr/local/bin/python
ln -sf /usr/local/bin/python2.7-2to3   /usr/local/bin/2to3
ln -sf /usr/local/bin/python2.7-config /usr/local/bin/python-config
ln -sf /usr/local/bin/pydoc2.7         /usr/local/bin/pydoc

git clone https://github.com/Azure/WALinuxAgent
cd WALinuxAgent
python setup.py install
waagent -register-service

Take a moment here to check that waagent is running and the log exists:

ps -auxww | grep waagent
tail -f /var/log/waagent.log

There will be numerous direct-to-screen errors about failures to mount discs. Don't panic.

When the VM is configured to your wishes, prep it for running in Azure and shut it down:

waagent -deprovision+user -force
sync
halt -p

At this point, waagent has disabled your root user account, so good luck getting back into your VM without a good sudo or doas config.

Note that you do not need to prepare a user account for this VM before you deploy it in the cloud. You do not need to:

  1. Create a local super user
  2. Make the be-all, end-all of kickass root passwords
  3. Add anything to /etc/doas.conf
  4. Set up SSH keys

You can do these things if you like, but it is not strictly required.

The guidance document pushes the Azure CLI 2.0 tool, so we'll use it in this tutorial but, if you want, you have alternatives. You can:

  1. Do everything through the Azure portal website.
  2. Use the Azure PowerShell cmdlets. There's a direct link to the installer.
  3. Use Azure CLI 2.0. You can install it locally. We're not going to do that.
  4. Use Azure CLI 2.0 through Azure Cloud Shell. This is a builtin, roaming shell account you access through the portal. It will create a dedicated storage account in your subscription, so it's not exactly free-free, but it leaves your local machine alone while still being handy.

Azure Cloud Shell will eventually allow you to use bash or PowerShell as your working shell, but at the time of writing it only offers bash. This is sufficient for our purposes.

Caveats about Azure Cloud Shell: It does not work equally well in all browsers. I've had luck with it in Firefox and Edge, but not in a Chromium-based browser. Your actual mileage may vary. Also, muscle memory may compel you to Ctrl-W a botched command you've typed. In bash, this deletes the word before the cursor. In Firefox, this closes the window without a warning. Sigh. Azure Cloud Shell does not maintain session state. While your Azure Cloud Shell files may be persistent, it does not provide command history between sessions and it doesn't offer tmux or GNU screen. And I have had limited luck with copying and pasting. Welcome to the future, kids.

In Azure Cloud Shell, you will check your Azure subscriptions and determine which one will keep your OpenBSD VMs. You may have more than one Azure subscription, so find and set the name or the GUID of your desired subscription:

az account list --output table
az account set --subscription mysubscription

Create a dedicated Azure resource group for your VM disk images. Think of it as a library that you can maintain and reference throughout the other groups in your subscription.

az group create \
  --name mylibrary \
  --location westus2

This tutorial uses "westus2" as the desired geographical location. Adjust this accordingly.

Create a storage account, and a blob container within that storage account to hold your .VHD files.

az storage account create \
  --name myopenbsdimagelibrary \
  --resource-group mylibrary \
  --location westus2 \
  --sku Standard_LRS
az storage container create \
  --name vhds \
  --account-name myopenbsdimagelibrary \
  --public-access off

When the storage account and container are created, upload your prepped OpenBSD .VHD file to it. You can do this in several different ways, but the way I recommend is to use a free utility called AzCopy. Uploading things to the cloud always sucks, but this tool sucks less.

Download and install AzCopy.exe, probably to "C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy\AzCopy.exe". There are a bunch of different options that AzCopy supports, but the real reason I recommend it, especially after having used it to upload terabytes of information, is that AzCopy supports restartability. When you have hundreds of gigs to upload and the connection craps out in the middle of the night, you have to restart the upload. AzCopy can keep you from having to restart all over from byte 0.

An example of how to upload your OpenBSD .VHD file to your new storage account container would be, all on one line:

AzCopy.exe
  /V
  /Y
  /BlobType:page
  /Source:"C:\Users\Public\Documents\Hyper-V\Virtual hard disks"
  /Dest:https://myopenbsdimagelibrary.blob.core.windows.net/vhds
  /DestKey:MYBIGLONGACCESSKEYGOESHERE==
  /Pattern:openbsd.vhd

The critical thing here is to set /BlobType:page. Not only does this potentially make the upload much faster (nearly 400% faster in my preliminary tests), it will allow you to create a custom Azure image from the .VHD file. Block blobs cannot be used for images. The guidance document skips this crucial imaging step entirely.

Sometimes AzCopy fails in mid-copy so I run it in an overly fancy loop until it exits 0 or I hit my max loop count. You can use my PowerShell script azcopy_upload.ps1 like so:

$azcopy_properties = @{
  'Path'        = 'C:\Users\Public\Documents\Hyper-V\Virtual hard disks';
  'KeyFile'     = 'C:\Users\myalias\Desktop\keyfile.xml';
  'Destination' = 'https://myopenbsdimagelibrary.blob.core.windows.net/vhds';
  'BlobType'    = 'page';
  'Pattern'     = 'openbsd.vhd';
}
.\azcopy_upload.ps1 @azcopy_properties

Be aware that the KeyFile value this script requires is an XML-encoded file containing the SecureString object that contains your Azure storage account key. To create this file you can run these steps:

$sec_str = ConvertTo-SecureString -AsPlainText -Force -String 'MYBIGLONGACCESSKEYGOESHERE=='
Export-CliXml -InputObject $sec_str -Force -Encoding 'UTF8' -Path 'C:\Users\myalias\Desktop\keyfile.xml'

When the .VHD file is uploaded to your storage account, create a custom Azure image from it:

az image create \
  --name myopenbsdimage \
  --resource-group mylibrary \
  --location westus2 \
  --os-type Linux \
  --source https://myopenbsdimagelibrary.blob.core.windows.net/vhds/openbsd.vhd

When the image is created, you're in business. Keep your image library pristine by making your new OpenBSD VMs in a separate resource group in the same subscription. If you screw something up in the VM, it's faster and easier to delete the entire group than to individually delete the resources within the group: virtual NICs, vnets, disks, et cetera.

az group create \
  --name openbsdvms \
  --location westus2
az vm create \
  --resource-group openbsdvms \
  --name openbsdvm1 \
  --public-ip-address-allocation dynamic \
  --size Basic_A1 \
  --storage-sku Standard_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --image $(az image show \
    --resource-group mylibrary \
    --name myopenbsdimage \
    --output tsv \
    --query id)

You can adjust your --size and --storage-sku settings depending on your performance preferences. Some VM sizes are storage settings are incompatible, so you may need to adjust these to make everyone happy. If you need a static IP, set --public-ip-address-allocation to static.

It should provision a new VM in a few minutes. Get the IP address of the newly-created VM and ssh into it directly from Azure Cloud Shell:

ssh -l azureuser $(az vm list-ip-addresses \
  --resource-group openbsdvms \
  --name openbsdvm1 \
  --output tsv \
  --query [].virtualMachine.network.publicIpAddresses[].ipAddress)

And that, dear reader, is how to really get OpenBSD running in Azure. Crackin' the whip, secure by default.

Notes

"Information for Non-Endorsed Distributions", aka, "Important Stuff for Fringe OSes": https://docs.microsoft.com/en-us/azure/virtual-machines/linux/create-upload-generic

No comments: